1. 字符设备驱动程序
本程序基于《字符设备驱动程序》的基础上进行改进。增加了按键触发点亮LED的功能,分为:
- 查询方式;
- 中断方式;
- poll/select方式;
- 异步通知方式(基于信号);
四个程序
2. 查询方式
2.1 驱动程序的编写
-
编写
init
函数,添加按键GIPO引脚的配置;设置新的file_operations结构体:
static struct file_operations s3c2440_buttons_fops = { .owner = THIS_MODULE, .open = s3c2440_buttons_open, .read = s3c2440_buttons_read, .release = s3c2440_buttons_close, };
init
函数的编写:static int __init s3c2440_buttons_init(void) { major = register_chrdev(0, DEVICE_NAME, &s3c2440_buttons_fops); if (major < 0) { printk(DEVICE_NAME " can't register major number\n"); return major; } s3c2440_buttons_class = class_create(THIS_MODULE, "s3c2440_buttons"); if (IS_ERR(s3c2440_buttons_class)) return PTR_ERR(s3c2440_buttons_class); /* 在/sys/s3c2440_leds目录下创建一个设备,设备的目录为leds * 同时触发mdev在/dev目录下生成一个名为leds的设备节点 */ s3c2440_buttons_dev = class_device_create(s3c2440_buttons_class, NULL, MKDEV(major, 0), NULL, "buttons"); if (unlikely(IS_ERR(s3c2440_buttons_dev))) return PTR_ERR(s3c2440_buttons_dev); gpfcon = (volatile unsigned long*)ioremap(S3C2440_GPFCON, 32); gpfdat = gpfcon + 1; gpfup = gpfdat + 1; gpgcon = gpfcon + ((S3C2440_GPGCON - S3C2440_GPFCON) >> 2); gpgdat = gpgcon + 1; gpgup = gpgdat + 1; printk(DEVICE_NAME " initialized!\n"); return 0; }
按键引脚配置原理图和数据手册参考第二点。
GPGCON的物理地址紧跟在GPGCON的后面,可以将它们的物理地址映射到同一块虚拟内存块中。(在我们的Linux系统的一块虚拟内存页是4K大小)
ioremap
每次映射都是以页为单位。 -
编写
open
函数,添加按键GIPO引脚的配置;GPG数据手册引脚配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iCeAtHf-1658150705843)(images\s3c2440_字符设备按键驱动程序\gpg.png)]
open函数:
static ssize_t s3c2440_buttons_open(struct inode *inode, struct file *file) { /* 配置LED:GPIO4\5\6为输出引脚 */ *gpfcon &= ~((0x3 << 8) | (0x3 << 10) | (0x3 << 12)); /* 清零 */ *gpfcon |= ((0x1 << 8) | (0x1 << 10) | (0x1 << 12)); /* 设置为输出 */ /* 配置按键:GPF0\2和GPG3 */ *gpfcon &= ~((0x3 << 0) | (0x3 << 4)); /* 为零就是输入 */ *gpfcon &= ~((0x3 << 6)); return 0; }
-
编写
read
函数,使得应用程序可以获取按键状态。static ssize_t s3c2440_buttons_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned long ret; unsigned char key_vals[3] = {0}; if (count < sizeof(key_vals)) return -EINVAL; /* GPF0\2 */ key_vals[0] = (*gpfdat & (1)) ? 1 : 0; key_vals[1] = (*gpfdat & (1 << 2)) ? 1 : 0; /* GPG3 */ key_vals[2] = (*gpgdat & (1 << 3)) ? 1 : 0; ret = copy_to_user(buf, key_vals, sizeof(key_vals)); return ret; }
2.2 测试程序的编写
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
unsigned char key_vals[3] = {0};
int fd;
fd = open("/dev/buttons", O_RDWR);
if (!fd)
{
printf("open /dev/buttons failed!\n");
return 0;
}
while (1) {
read(fd, key_vals, sizeof(key_vals));
if (!key_vals[0] || !key_vals[1] || !key_vals[2])
printf("btn1: %d, btn2: %d, btn3: %d\n",
key_vals[0], key_vals[1], key_vals[2]);
}
return 0;
}
我们的测试程序以轮询的方式不断的去读取按键的状态,这样的代价是非常消耗CPU的资源。为了解决这个问题,我们需要引入一种机制,当按键状态发送改变,可以由驱动程序来提醒我们的应用程序。
3. 中断方式
3.1 s3c2440 中断控制器分析
3.2 中断体系结构
中断体系结构原理分析
Linux内核的中断原理分析篇:Linux中断体系结构分析
注册 irqaction
要往 irq_desc 数组上添加 irqaction 结构,我们需要使用 request_irq 函数进行注册,其中 dev_id 可以视为该 irqaction 的标识符,在卸载时需要用到。该函数的定义如下:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
/* ...省略... */
/* 动态申请一个irqaction数据结构 */
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
if (!action)
return -ENOMEM;
/* 进行填充 */
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
select_smp_affinity(irq);
/* 往数组的irqaction链表新添一个action */
retval = setup_irq(irq, action);
/* ...省略... */
}
setup_irq 函数的定义如下:
int setup_irq(unsigned int irq, struct irqaction *new)
{
struct irq_desc *desc = irq_desc + irq;
struct irqaction *old, **p;
const char *old_name = NULL;
unsigned long flags;
int shared = 0;
if (irq >= NR_IRQS) /* 如果所请求注册的中断号超出系统中断数,返回错误 */
return -EINVAL;
if (desc->chip == &no_irq_chip) /* 如果所请求注册的中断号的硬件操作未设置 */
return -ENOSYS;
/* ...省略... */
p = &desc->action;
old = *p;
if (old) {
/* 如果该中断号已经设置过action了,就需要判断是否为共享中断
* 同时还需要判断,是否是相同类型的中断,即
* 是否都为共享中断,中断的触发方式是否相同,中断的优先级是否相同等,
* 只有以上都满足了,才能够在一个已有action的中断号上新添一个新的aciton
*/
/*
* Can't share interrupts unless both agree to and are
* the same type (level, edge, polarity). So both flag
* fields must have IRQF_SHARED set and the bits which
* set the trigger type must match.
*/
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
}
/* add new interrupt at end of irq queue */
do {
p = &old->next;
old = *p;
} while (old);
shared = 1;
}
/* ...省略... */
/* 如果是第一次设置irqaction */
if (!shared) {
irq_chip_set_defaults(desc->chip);
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
if (desc->chip && desc->chip->set_type)
/* 设置引脚的类型,将引脚配置为中断引脚 */
desc->chip->set_type(irq,
new->flags & IRQF_TRIGGER_MASK);
} else
compat_irq_chip_set_default_handler(desc);
/* 设置desc的状态 */
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS);
if (!(desc->status & IRQ_NOAUTOEN)) {
desc->depth = 0;
desc->status &= ~IRQ_DISABLED;
if (desc->chip->startup)
desc->chip->startup(irq); /* 配置引脚为中断引脚 */
else
desc->chip->enable(irq); /* 配置引脚为中断引脚 */
} else
/* Undo nested disables: */
desc->depth = 1;
}
}
通过分析 setup_irq 函数,可以知道其完成的任务如下:
-
由中断号 irq,从全局中断数组中获取中断数据结构 irq_desc;
-
判断 irq_desc 是否已经设置了 irqaction。
若已经设置,则进行 irqaction 兼容性检测,即是中断方式(边沿或者电平触发)、中断优先级、是否为共享中断等;
若没有设置,则为第一次设置 irqaction。需要对设置 irq_chip 的操作函数进行设置,对于没有被指定的操作函数,会设置为内核中已经设置好了的默认操作函数。同时需要配置硬件引脚,使其变为中断引脚;
-
调用 set_type 函数设置配置引脚,将引脚设置为中断引脚,同时设置中断的触发模式;
-
最后将 irqaction 添加到 irq_desc 的 irqaciton 链表上;
set_type 函数的定义:
s3c_irqext_type(unsigned int irq, unsigned int type) { void __iomem *extint_reg; void __iomem *gpcon_reg; unsigned long gpcon_offset, extint_offset; unsigned long newvalue = 0, value; if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3)) { gpcon_reg = S3C2410_GPFCON; extint_reg = S3C24XX_EXTINT0; gpcon_offset = (irq - IRQ_EINT0) * 2; extint_offset = (irq - IRQ_EINT0) * 4; } else if ((irq >= IRQ_EINT4) && (irq <= IRQ_EINT7)) { gpcon_reg = S3C2410_GPFCON; extint_reg = S3C24XX_EXTINT0; gpcon_offset = (irq - (EXTINT_OFF)) * 2; extint_offset = (irq - (EXTINT_OFF)) * 4; } else if ((irq >= IRQ_EINT8) && (irq <= IRQ_EINT15)) { gpcon_reg = S3C2410_GPGCON; extint_reg = S3C24XX_EXTINT1; gpcon_offset = (irq - IRQ_EINT8) * 2; extint_offset = (irq - IRQ_EINT8) * 4; } else if ((irq >= IRQ_EINT16) && (irq <= IRQ_EINT23)) { gpcon_reg = S3C2410_GPGCON; extint_reg = S3C24XX_EXTINT2; gpcon_offset = (irq - IRQ_EINT8) * 2; extint_offset = (irq - IRQ_EINT16) * 4; } else return -1; /* Set the GPIO to external interrupt mode */ value = __raw_readl(gpcon_reg); value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset); __raw_writel(value, gpcon_reg); /* Set the external interrupt to pointed trigger type */ switch (type) { case IRQT_NOEDGE: printk(KERN_WARNING "No edge setting!\n"); break; case IRQT_RISING: newvalue = S3C2410_EXTINT_RISEEDGE; break; case IRQT_FALLING: newvalue = S3C2410_EXTINT_FALLEDGE; break; case IRQT_BOTHEDGE: newvalue = S3C2410_EXTINT_BOTHEDGE; break; case IRQT_LOW: newvalue = S3C2410_EXTINT_LOWLEV; break; case IRQT_HIGH: newvalue = S3C2410_EXTINT_HILEV; break; default: printk(KERN_ERR "No such irq type %d", type); return -1; } value = __raw_readl(extint_reg); value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset); __raw_writel(value, extint_reg); return 0; }
卸载 irqation
当不需要中断了,可以通过 free_irq 函数卸载掉注册在 irq_desc 中的 irqaction。该函数的定义如下:
void free_irq(unsigned int irq, void *dev_id)
{
/* ...省略... */
desc = irq_desc + irq;
p = &desc->action;
for (;;) {
struct irqaction *action = *p;
if (action) {
struct irqaction **pp = p;
p = &action->next;
/* 如果dev_id相同就说明找到了 */
if (action->dev_id != dev_id)
continue;
/* Found it - now remove it from the list of entries */
*pp = action->next;
/* Currently used only by UML, might disappear one day.*/
if (!desc->action) { /* 如果链表已经空了 */
desc->status |= IRQ_DISABLED;
if (desc->chip->shutdown)
desc->chip->shutdown(irq); /* 关闭硬件引脚的中断 */
else
desc->chip->disable(irq); /* 关闭硬件引脚的中断 */
}
unregister_handler_proc(irq, action); /* 从irq_desc移除irqaction */
if (action->flags & IRQF_SHARED)
handler = action->handler;
kfree(action); /* 释放irqaction */
return;
}
/* ...省略... */
}
/* ...省略... */
}
从函数的接口可以知道,这个需要两个参数一个的中断号,另一个是 dev_id,
3.3 中断驱动程序
-
open 函数:注册 irqaction 的 irq_desc 中
定义 pins_desc 数据结构,该数据结构内定义了按键对应的值:
struct pin_desc { unsigned int pin; unsigned char key_val; }; /* 按键按下的值: 0x01 0x02 0x03 0x04 * 按键松开的值: 0x81 0x82 0x83 0x84 */ static struct pin_desc pins_desc[4] = { {S3C2410_GPF0, 0x01}, {S3C2410_GPF2, 0x02}, {S3C2410_GPG3, 0x03}, {S3C2410_GPG11, 0x04}, };
open 函数:
static ssize_t s3c2440_buttons_irq_open(struct inode *inode, struct file *file) { /* 配置按键:GPF0\2和GPG3的引脚为中断引脚 * 注册irqaction处理函数 */ request_irq(IRQ_EINT0, s3c2440_buttons_irq_handler, IRQT_BOTHEDGE, "s2", &pins_desc[0]); request_irq(IRQ_EINT2, s3c2440_buttons_irq_handler, IRQT_BOTHEDGE, "s3", &pins_desc[1]); request_irq(IRQ_EINT11, s3c2440_buttons_irq_handler, IRQT_BOTHEDGE, "s4", &pins_desc[2]); request_irq(IRQ_EINT19, s3c2440_buttons_irq_handler, IRQT_BOTHEDGE, "s5", &pins_desc[3]); return 0; }
s3c2440_buttons_irq_handler 函数:
static irqreturn_t s3c2440_buttons_irq_handler(int irq, void *dev_id) { struct pin_desc *pindesc = (struct pin_desc *)dev_id; unsigned char pinval; pinval = s3c2410_gpio_getpin(pindesc->pin); /* 读出引脚值 */ if (pinval) { /* 按键松开 */ key_val = 0x80 | pindesc->key_val; } else { /* 按键按下 */ key_val = pindesc->key_val; } ev_press = 1; wake_up_interruptible(&button_waitq); return IRQ_HANDLED; }
s3c2410_gpio_getpin 函数:内核中实现的一个用于读取 s3c24xx 芯片引脚的一个函数,简化了用户对引脚进行的操作;
该函数将引脚的状态读入,存储在全局变量 key_val 中。如果 s2 按键被按下,key_val 的值就为0x01,如果 s2 被松开,key_val 的值就为0x81。s3、s4、s5 不赘述。
wake_up_interruptible(&button_waitq);
会在后面进行说明 -
release 函数:卸载 irqaction
static ssize_t s3c2440_buttons_irq_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT0, &pins_desc[0]); free_irq(IRQ_EINT2, &pins_desc[1]); free_irq(IRQ_EINT11, &pins_desc[2]); free_irq(IRQ_EINT19, &pins_desc[3]); return 0; }
-
read 函数:负责读取引脚的状态值,然后将状态返回给应用程序
当中断触发时,会先调用 irq_desc 中的 handle 函数,也就是 s3c2440_buttons_irq_handler 将值存储在 key_val;
而当应用程序调用 read 函数时,如果此时没有中断产生,应用程序应该进入阻塞状态,否则就会使得 while 循环不断调用 read 去获取按键值(查询方式的驱动程序)。
为了解决这个消耗 CPU 资源的行为,我们需要使 read 函数会阻塞。
声明一个等待队列 button_waitq:
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /* 中断事件标志,中断服务程序将它设置为1,read函数内将它清0 */ static unsigned long ev_press = 0;
Linux内核等待队列详细说明:Linux 内核等待队列
read 函数:
static ssize_t s3c2440_buttons_irq_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned long ret; if (count < sizeof(key_val)) return -EINVAL; /* 如果没有按键按下,就休眠 */ wait_event_interruptible(button_waitq, ev_press); /* 如果有按键被按下,从休眠状态唤醒 */ ret = copy_to_user(buf, &key_val, sizeof(key_val)); ev_press = 0; return ret; }
wait_event_interruptible(button_waitq, ev_press);
判断条件 ev_press 是否成立(为true成立,false不成立),若不成立,使当前程序进程被挂载到阻塞队列 button_waitq 上。当有按键按下时,由 s3c2440_buttons_irq_handler 负责唤醒 button_waitq 阻塞队列上的进程。
static irqreturn_t s3c2440_buttons_irq_handler(int irq, void *dev_id) { /* ...省略... */ ev_press = 1; wake_up_interruptible(&button_waitq); /* 唤醒进程 */ return IRQ_HANDLED; }
3.4 测试程序
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
unsigned long key_val = 0;
int fd;
fd = open("/dev/buttons_irq", O_RDWR);
if (!fd)
{
printf("open /dev/buttons failed!\n");
return 0;
}
while (1) {
read(fd, &key_val, sizeof(key_val));
printf("key_val = 0x%x\n", key_val);
}
return 0;
}
4. poll机制的驱动程序
4.1 poll 机制分析
4.2 驱动程序
在中断驱动程序的基础上进行修改:
-
添加 file operation 中的 poll 机制:
static unsigned s3c2440_buttons_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN | POLLRDBAND; return mask; }
4.3 应用程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
/* forthdrvtest
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];
fd = open("/dev/buttons_poll", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
return 0;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{
ret = poll(fds, 1, 5000);
if (ret == 0)
{
printf("time out\n");
}
else
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}
5. 异步通知
使用信号的方式,通知应用程序,当前的驱动可操作。应用程序再调用 read/write 等操作函数去读或者写应用程序。
5.1 驱动程序
为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:
-
支持 F_SETOWN 命令,能在这个控制命令处理中设置 filp->f_owner 为对应进程 ID。
不过此项工作已由内核来完成,设备驱动无需处理。
-
支持 F_SETFL 命令的处理,每当 FASYNC 标志改变时,驱动程序中的 fasync() 函数将得以执行。
驱动中应该实现 fasync() 函数。
-
在设备资源可获得时,调用 kill_fasync() 函数激发相应的信号。
在 poll 驱动程序的基础上,修改驱动程序:
-
定义全局变量 s3c2440_buttons_async_queue
#define DEVICE_NAME "s3c2440_buttons_poll" static unsigned int major; static struct class *s3c2440_buttons_class; static struct class_device *s3c2440_buttons_dev; static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static struct fasync_struct *s3c2440_buttons_async_queue; /* 新添加定义 */
-
添加 fasync 函数:
int s3c2440_buttons_fasync(int fd, struct file * file, int on) { return fasync_helper(fd, file, on, s3c2440_buttons_async_queue); } static struct file_operations s3c2440_buttons_fops = { .owner = THIS_MODULE, .open = s3c2440_buttons_open, .read = s3c2440_buttons_read, .write = s3c2440_buttons_write, .release = s3c2440_buttons_close, .poll = s3c2440_buttons_poll, .fasync = s3c2440_buttons_fasync, /* 新添加 */ };
-
在中断处理函数(s3c2440_buttons_handler )内添加发送信号的代码
static irqreturn_t s3c2440_buttons_handler(int irq, void *dev_id) { struct pin_desc *pindesc = (struct pin_desc *)dev_id; unsigned char pinval; pinval = s3c2410_gpio_getpin(pindesc->pin); /* 读出引脚值 */ if (pinval) { key_val = 0x80 | pindesc->key_val; /* 按键松开 */ } else { key_val = pindesc->key_val; /* 按键按下 */ } ev_press = 1; wake_up_interruptible(&button_waitq); /* 发送信号 */ kill_fasync(s3c2440_buttons_async_queue, SIGIO, POLL_IN); return IRQ_HANDLED; }
5.2 中断机制
在驱动程序的 s3c2440_buttons_handler 中断处理函数内,我们通过 kill_fasync 来向应用程序发送信号,那么应用程序是如何获取到信号的呢?
fasync_struct:我们新定义了一个 s3c2440_buttons_async_queue 类型的队列。
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
s3c2440_buttons_fasync:在我们的驱动程序中,该函数的作用是初始化 s3c2440_buttons_async_queue 这个数据结构,通过调用 fasync_helper(fd, file, on, s3c2440_buttons_async_queue)
来进行初始化,fasync_helper 的源码如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
struct fasync_struct *fa, **fp;
struct fasync_struct *new = NULL;
int result = 0;
if (on) {
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
if (!new)
return -ENOMEM;
}
write_lock_irq(&fasync_lock);
/* 释放原有的s3c2440_buttons_async_queue中的内容 */
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file == filp) {
if(on) {
fa->fa_fd = fd;
kmem_cache_free(fasync_cache, new);
} else {
*fp = fa->fa_next;
kmem_cache_free(fasync_cache, fa);
result = 1;
}
goto out;
}
}
/* 重新设置s3c2440_buttons_async_queue
* 或者初始化s3c2440_buttons_async_queue */
if (on) {
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
result = 1;
}
out:
write_unlock_irq(&fasync_lock);
return result;
}
FASYNC_MAGIC:用于标识该对象特定用于 fasync 时使用。可见这个数据结构是内核中一个通用的数据结构,通过这个 magic 进行唯一标识其具体的用途;
向应用程序发送信号时调用 kill_fasync(s3c2440_buttons_async_queue, SIGIO, POLL_IN)
这个函数进行发送,kill_fasync 调用 __kill_fasync 进行操作。__kill_fasync 的源码如下:
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct * fown;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
fown = &fa->fa_file->f_owner;
/* Don't send SIGURG to processes which have not set a
queued signum: SIGURG has its own default signalling
mechanism. */
if (!(sig == SIGURG && fown->signum == 0))
send_sigio(fown, fa->fa_fd, band);
fa = fa->fa_next;
}
}
-
fown = &fa->fa_file->f_owner
获取了当前驱动的所有者。其定义如下:struct fown_struct { rwlock_t lock; /* protects pid, uid, euid fields */ struct pid *pid; /* pid or -pgrp where SIGIO should be sent */ enum pid_type pid_type; /* Kind of process group SIGIO should be sent to */ uid_t uid, euid; /* uid/euid of process setting the owner */ int signum; /* posix.1b rt signal to be delivered on IO */ };
-
接着调用
send_sigio(fown, fa->fa_fd, band)
发送信号;
send_sigio 的定义如下:
void send_sigio(struct fown_struct *fown, int fd, int band)
{
struct task_struct *p;
enum pid_type type;
struct pid *pid;
read_lock(&fown->lock);
type = fown->pid_type; /* */
pid = fown->pid; /* 获取应用程序的进程ID */
if (!pid)
goto out_unlock_fown;
read_lock(&tasklist_lock);
do_each_pid_task(pid, type, p) { /* 对驱动程序的应用程序“们”, 都发送信号 */
send_sigio_to_task(p, fown, fd, band);
} while_each_pid_task(pid, type, p);
read_unlock(&tasklist_lock);
out_unlock_fown:
read_unlock(&fown->lock);
}
- do_each_pid_task:会对 fown 文件所有者们,都调用 send_sigio_to_task 发送信号给这些所有者。
- 这些所有者接收到信号后就会触发信号的 handler 函数被执行。
那么 fown 是如何设置进驱动程序的呢?
- 由应用程序通过
fcntl(fd, F_SETOWN, getpid())
的方式,将当前进程注册进驱动程序的所有者(f_owner)项上; - 驱动程序通过 file 结构体的 f_owner 获取到所有者;
fcntl 会调用 sys_fcntl 函数,其定义如下:
asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
struct file *filp;
/* ...省略... */
filp = fget(fd);
/* ...省略... */
err = do_fcntl(fd, cmd, arg, filp);
/* ...省略... */
}
核心功能在 do_fcntl 函数中完成。do_fcntl 定义如下:(简略版本)
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
{
/* ...省略... */
case F_SETOWN:
err = f_setown(filp, arg, 1);
break;
/* ...省略... */
}
通过调用 f_setown 函数将进程设置进去,此时的 arg 为应用程序 getpid() 时获取到的进程 ID 号。
int f_setown(struct file *filp, unsigned long arg, int force)
{
enum pid_type type;
struct pid *pid;
/* ...省略... */
pid = find_pid(who);
result = __f_setown(filp, pid, type, force);
/* ...省略... */
}
-
find_pid(who)
:通过进程的进程号,获取该进程的一个 pid 数据结构。struct pid 的定义如下:struct pid { atomic_t count; /* Try to keep pid_chain in the same cacheline as nr for find_pid */ int nr; struct hlist_node pid_chain; /* lists of tasks that use this pid */ struct hlist_head tasks[PIDTYPE_MAX]; struct rcu_head rcu; };
-
__f_setown(filp, pid, type, force)
:设置文件(驱动程序)的所有者。int __f_setown(struct file *filp, struct pid *pid, enum pid_type type, int force) { /* ...省略... */ f_modown(filp, pid, type, current->uid, current->euid, force); return 0; }
f_modown 函数的定义如下:由于我们的 force 在 f_setown(filp, arg, 1)
时指定为了1,因此会强制设置驱动设备的所有者为发出 fcntl 的应用程序。
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
uid_t uid, uid_t euid, int force)
{
write_lock_irq(&filp->f_owner.lock);
if (force || !filp->f_owner.pid) {
put_pid(filp->f_owner.pid);
filp->f_owner.pid = get_pid(pid);
filp->f_owner.pid_type = type;
filp->f_owner.uid = uid;
filp->f_owner.euid = euid;
}
write_unlock_irq(&filp->f_owner.lock);
}
这样就将应用程序设置为了驱动设备的所有者。
然后应用层再通过 fcntl(fd, F_SETFL, oflags | FASYNC)
调用到应用程序的 s3c2440_buttons_fasync 函数。
int s3c2440_buttons_fasync(int fd, struct file * file, int on)
{
return fasync_helper(fd, file, on, s3c2440_buttons_async_queue);
}
再在 fasync_helper 函数内获取到这个 owner 设置进 s3c2440_buttons_async_queue 这个结构体中,从而在后续调用 kill_fasync 命令时通过 s3c2440_buttons_async_queue 这个结构体向应用程序发送信号。