欢迎光临散文网 会员登陆 & 注册

一文讲解字符设备驱动之常用内核函数

2022-09-27 15:41 作者:补给站Linux内核  | 我要投稿

字符设备驱动含有open、read、write、ioctl等函数,用于用户层和内核之间的通信,所以当用户要获得内核驱动的一些数据或者发送一些控制命令,就需要使用设备驱动了。对于一些中断类型的驱动,比如输入子系统,用户层不需要对其进行操作,可以不使用设备驱动。

1.register_chrdev

说起字符设备驱动程序,肯定少不了register_chrdev函数,它在内存中分配了一块区域用于字符设备驱动的热拔插。这个区域里面的驱动拥有相同的主设备号,不同的次设备号,但它们都指向同一个file_operations结构体。就好比USB接口,一台电脑上有好几个USB接口,它们的属性都是一样的,鼠标接上去后左键右键功能都是相同的,这是因为它们有相同的usb_device结构体。

对于linux2.x的内核一般都是采用这种方式注册字符设备驱动,但这种方式的缺点是同一结构体下分配了过多的字符设备驱动的接口。所以linux3.x的内核对其进行了改进,register_chrdev函数可以拆分成3给独立的函数来对字符设备驱动进行注册。register_chrdev = register_chrdev_region/alloc_chrdev_region + cdev_init + cdev_add。

如果主设备号已经指定了,就是定义了major = ?,就使用register_chrdev_region来开辟驱动的接口;如果让内核自动分配major的值,则使用alloc_chrdev_region。具体的函数形式如下:

这里要说一下cdev_alloc()这个函数,如果是static struct cdev *cdev这么定义的,则使用到cdev_alloc,如果定义的是结构体而不是指针(static struct cdev cdev),就不需要使用cdev_alloc这个函数了。宏定义MAXPIN表示对该file_operations结构体可以支持的设备驱动个数。

2. class_create


【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)    

3. request_irq

具体形式如下:

error = request_irq(IRQ_EINT0,key_handler,IRQ_TYPE_EDGE_BOTH,"key1",NULL);

这里要注意的是第一个参数是中断的类型,像上面直接写的IRQ_EINT0,这样就启动了外部中断0,在Linux内核里面就不需要再像之前的裸板程序一样配置相关引脚为中断模式了,这里内核已经帮我们做好了;然后第二个参数是中断服务函数,发生中断之后进入这个函数。第三给参数很重要,它是中断的触发类型,这里我用的是双边沿触发。如果只有一个中断,最后一个参数写NULL就可以了,如果有多个中断,最好是分配一个结构体,里面记录每个中断的信息,这些中断可以使用相同的中断服务函数,根据中断信息就可以知道是哪个中断发生了。

而发生中断后,进入static irqreturn_t key_irq(int irq, void *dev_id),它的dev_id正是request_irq的最后一个参数&key[i],通过(&key[i])->pin我们就能知道到底是GPF0,还是GPF2或者是GPG3发生了中断,也就是外部中断0,2或者11处发生了中断。

4 .时间函数

首先定义一个timer_list结构体static struct timer_list key_time。

再初始化函数里加上 init_timer(&key_time); key_time.function = key_time_function; add_timer(&key_time);

然后中断服务函数里面加上 mod_timer(&key_time,jiffies + HZ/100); //延迟10ms,延迟过程中断仍然可以被触发 原先在中断服务函数里面实现的代码放到时间函数key_time_function里面。

对于 mod_timer,+HZ表示延时1秒,/100就是10ms了。

时间函数可以用于中断,去按键抖动。中断里启动时间函数后中断函数就结束了,它不管时间函数是否执行完成。这样在延时10ms的过程中中断又可以被触发,这就去了按键抖动,这是它与传统的delay函数的区别。它只要告诉内核多长时间后启动时间函数,mod_timer这个函数就执行完了,中断服务函数就执行完了,剩下的就等过完这段时间后内核启动时间函数key_time_function,然后执行里面的内容就可以了。


5. 睡眠唤醒

5.1 声明这个待唤醒事件

static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);

5.2 休眠

wait_event_interruptible(dma_waitq, ev_dma);

5.3 唤醒

wake_up_interruptible(&dma_waitq);

5.4 过程分析

从if条件语句可以看出,要进入休眠首先condition要等于0,其次再看宏__wait_event_interruptible:


do...while(0)实际上也只是执行了一次循环,但却有重要意义:它使得__wait_event_interruptible可以像函数一样使用(实际上是宏),在程序中__wait_event_interruptible();就可以使用它。那为什么不直接将它定义为函数呢?因为它位于wait.h头文件,定义为函数,被不同的程序引用会造成重复定义。当然也可以像上面一样打一个()。

然后进入for(;;)循环,当condition == 1时break,然后执行finish_wait(&wq, &__wait),接着

而wake_up_interruptible(&dma_waitq)做的事情是:

正好与上面的finish_wait(&wq, &__wait)对应,至于里面自旋锁的操作,我也不懂,我明白的是:

  1. 要休眠就得让condition = 0,然后wait_event_interruptible(dma_waitq, ev_dma);

  2. 要唤醒进程,就得先让condition = 1,然后wake_up_interruptible(&dma_waitq)

6 ioctl函数

ioctl像read、write一样读写:



一文讲解字符设备驱动之常用内核函数的评论 (共 条)

分享到微博请遵守国家法律