最全Linux驱动开发八股文(十)
你好,我是拉依达。
这是我的Linux驱动开发八股文详细解析系列。
本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。
全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。
现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。
【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询
———————————————————————————————————————————————————
十一、设备控制接口(ioctl)
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。有些命令是实在找不到对应的操作函数, 拓展一些file_operations给出的接口中没有的自定义功能,则需要使用到ioctl()函数。一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。
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 型数据划分为四个位段,如下图所示:
-
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函数
-
获取中断号函数 每个中断都有一个中断号,通过中断号即可区分不同的中断。在 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 对应的中断号
-
申请中断函数 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_SHARED, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
-
返回值: 0 中断申请成功,负值中断申请失败,如果返回-EBUSY 表示中断已经被申请了
中断标志
-
中断释放函数 中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。 如果中断不是共享的,free_irq 会删除中断处理函数并且禁止中断。
void free_irq(unsigned int irq,void *dev)
- irq: 要释放的中断号
- dev:如果中断设置为共享(IRQF_SHARED),此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉
-
中断处理函数
使用 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)
-
-
中断使能和禁止函数
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驱动开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。 全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。