最全Linux驱动开发八股文(十)

你好,我是拉依达。

这是我的Linux驱动开发八股文详细解析系列

本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。

全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料

现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询

———————————————————————————————————————————————————

十一、设备控制接口(ioctl)

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。有些命令是实在找不到对应的操作函数, 拓展一些file_operations给出的接口中没有的自定义功能,则需要使用到ioctl()函数。一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。

alt

11.1 应用层

需要规定一些命令码,这些命令码在应用程序和驱动程序中需要保持一致。应用程序只需向驱动程序下发一条指令码,用来通知它执行哪条命令。

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, (...)arg);
  • fd:文件描述符

  • request:命令码,应用程序通过下发命令码来控制驱动程序完成对应操作。

  • (...)arg:可变参数arg,一些情况下应用程序需要向驱动程序传参,参数就通过arg来传递。只能传递一个参数,但内核不会检查这个参数的类型。那么就有两种传参方式:只传一个整数,传递一个指针

  • 返回值:如果ioctl执行成功,它的返回值就是驱动程序中ioctl接口给的返回值,驱动程序可以通过返回值向用户程序传参。但驱动程序最好返回一个非负数,因为用户程序中的ioctl运行失败时一定会返回-1并设置全局变量errorno。

11.2 驱动层

驱动程序的ioctl函数体中,实现了一个switch-case结构,每一个case对应一个命令码,case内部是驱动程序实现该命令的相关操作。

#include <linux/ioctl.h>
long (*unlocked_ioctl) (struct file * fp, unsigned int request, unsigned long args);
long (*compat_ioctl) (struct file * fp, unsigned int request, unsigned long args);
  • inode和fp用来确定被操作的设备

  • request就是用户程序下发的命令

  • args就是用户程序在必要时传递的参数

  • 返回值:可以在函数体中随意定义返回值,这个返回值也会被直接返回到用户程序中。通常使用非负数表示正确的返回,而返回一个负数系统会判定为ioctl调用失败。

  • unlocked_ioctl在无大内核锁(BKL)的情况下调用。64位用户程序运行在64位的kernel,或32位的用户程序运行在32位的kernel上,都是调用unlocked_ioctl函数。

  • compat_ioctl是64位系统提供32位ioctl的兼容方法,也在无大内核锁的情况下调用。即如果是32位的用户程序调用64位的kernel,则会调用compat_ioctl。如果驱动程序没有实现compat_ioctl,则用户程序在执行ioctl时会返回错误Not a typewriter。

  • 在字符设备驱动开发中,一般情况下只要实现 unlocked_ioctl 函数即可,因为在 vfs 层的代码是直接调用 unlocked_ioctl 函数

12.2 ioctr应用和驱动的协议

ioctl函数的第二个参数 cmd 为用户与驱动的协议理论上可以为任意 int 型数据,,但是为了确保该协议的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:

alt

  • dir(direction):ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据

  • type(device type):设备类型,占据 8 bit,也称为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识

  • nr(number):命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增

  • size:与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。

在内核中,提供了宏接口以生成上述格式的 ioctl 命令

#include/uapi/asm-generic/ioctl.h
 
#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \
     ((type) << _IOC_TYPESHIFT) | \
     ((nr)   << _IOC_NRSHIFT) | \
     ((size) << _IOC_SIZESHIFT))

宏 _IOC() 衍生的接口来直接定义 ioctl 命令

#include/uapi/asm-generic/ioctl.h
 
/* used to create numbers */
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
  • _IO(type, nr):用来定义不带参数的ioctl命令

  • _IOR(type,nr,size):用来定义用户程序向驱动程序写参数的ioctl命令

  • _IOW(type,nr,size):用来定义用户程序从驱动程序读参数的ioctl命令

  • _IOWR(type,nr,size):用来定义带读写参数的驱动命令

内核还提供了反向解析 ioctl 命令的宏接口:

// include/uapi/asm-generic/ioctl.h
/* used to decode ioctl numbers */
#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)     (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)       (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
  • _IOC_DIR(nr) :提取方向

  • _IOC_TYPE(nr) :提取幻数

  • _IOC_NR(nr) :提取序数

  • _IOC_SIZE(nr) :提取数据大小

十二、中断机制

中断是指 CPU 在执行程序的过程中, 出现了某些突发事件急待处理, CPU 必须暂停当前程序的执行转去处理突发事件, 处理完毕后又返回原程序被中断的位置继续执行。

12.1 中断API函数

  1. 获取中断号函数 每个中断都有一个中断号,通过中断号即可区分不同的中断。在 Linux 内核中使用一个 int 变量表示中断号

    或者中断号, 中断信息一般写到了设备树里面, 可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号。

    unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
    
    • dev: 设备节点
    • index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
    • 返回值:中断号

    使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号

    int gpio_to_irq(unsigned int gpio)
    
    • gpio: 要获取的 GPIO 编号
    • 返回值: GPIO 对应的中断号
  2. 申请中断函数 Linux 内核中要想使用某个中断是需要申请的, request_irq 函数用于申请中断, request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。 request_irq 函数会激活(使能)中断,所以不需要手动去使能中断

    int request_irq(unsigned int irq,
                    irq_handler_t handler,
                    unsigned long flags,
                    const char *name,
                    void *dev)
    
  • irq:要申请中断的中断号

  • handler:中断处理函数,当中断发生会执行此中断处理函数

  • flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志

  • name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字

  • dev: 如果将 flags 设置为 IRQF_SHAREDdev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

  • 返回值: 0 中断申请成功,负值中断申请失败,如果返回-EBUSY 表示中断已经被申请了

    中断标志

    alt

  1. 中断释放函数 中断使用完成以后就要通过 free_irq 函数释放掉相应的中断如果中断不是共享的,free_irq 会删除中断处理函数并且禁止中断

    void free_irq(unsigned int irq,void *dev)
    
    • irq: 要释放的中断号
    • dev:如果中断设置为共享(IRQF_SHARED),此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉
  2. 中断处理函数

    使用 request_irq 函数申请中断的时候需要设置中断处理函数

    irqreturn_t (*irq_handler_t) (int, void *)
    
    • 第一个参数:要中断处理函数要相应的中断号

    • 第二个参数:一个指向 void 的指针,是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构

    • 返回值:irqreturn_t 类型

      irqreturn_t 类型定义如下所示:

      enum irqreturn {
          IRQ_NONE = (0 << 0),
          IRQ_HANDLED = (1 << 0),
          IRQ_WAKE_THREAD = (1 << 1),
      };
      typedef enum irqreturn irqreturn_t;
      

      irqreturn_t 是个枚举类型, 一共有三种返回值。 一般中断服务函数返回值使用如下形式

      return IRQ_RETVAL(IRQ_HANDLED)
      
  3. 中断使能和禁止函数

    enable_irq 和 disable_irq 用于使能和禁止指定的中断。

    void enable_irq(unsigned int irq)
    void disable_irq(unsigned int irq)
    void disable_irq_nosync(unsigned int irq)
    
    • irq:要禁止的中断号

    • disable_irq 函数要等到当前正在执行的中断处理函数执行完才返回, 因此需要保证不会产生新的中断, 并且确保所有已经开始执行的中断处理程序已经全部退出。

    • disable_irq_nosync 函数调用以后立即返回, 不会等待当前中断处理程序执行完毕。

    使能/关闭全局中断

    local_irq_enable()
    local_irq_disable()
    
    • local_irq_enable 用于使能当前处理器中断系统,
    • local_irq_disable 用于禁止当前处理器中断系统。
    • 一般不能直接简单粗暴的通过这两个函数来打开或者关闭全局中断,这样会使系统崩溃。

    在打开或者关闭全局中断时,要考虑到别的任务的感受,要保存中断状态,处理完后要将中断状态恢复到以前的状态

    local_irq_save(flags)
    local_irq_restore(flags)
    
    • local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
    • local_irq_restore 用于恢复中断,将中断到 flags 状态。
#嵌入式##校招##八股文##Linux##linux驱动#
拉依达的Linux驱动八股文 文章被收录于专栏

你好,我是拉依达。 这是我的Linux驱动开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。 全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

头像
11-09 17:30
门头沟学院 Java
TYUT太摆金星:我也是,好几个华为的社招找我了
点赞 评论 收藏
分享
拒绝无效加班的小师弟很中意你:求职意向没有,年龄、课程冗余信息可以删掉,需要提升项目经历。排版需要修改。
点赞 评论 收藏
分享
1 4 评论
分享
牛客网
牛客企业服务