Linux字符驱动之异步通知
1. 写在前面
在前面文章实现的驱动代码中,都是应用程序主动来读取(或写入)驱动数据信息;阻塞情况下,如果驱动资源不可用,则休眠进行;poll机制下还需要进程轮询驱动文件描述符以判断是否可读。理想的情况应该是这样的,主进程open某驱动fd后,继续执行其他程序任务,当改驱动资源可读时,产生一个通知信号给当前进程,然后进程再进行读取/写入操作,这样能够大大提高程序的效率。我们利用Linux内核“异步通知机制”实现该功能。
2. 异步通知
Linux系统中,异步通知机制是通过“信号”实现的,在进程通信或者Linux系统使用过程都有在用异步通知(信号),例如在终端执行一个应用程序,应用程序执行完之前,可以通过“Ctrl+C”发出一个终止信号(SIGINT)强制停止该应用。
2.1 异步通知构成要素
【1】通知信号发起者(fd):进程、用户终端、驱动等
【2】信号类型(SIGIO):读、写、其他
【3】信号接受者(pid):一般为用户进程
【4】信号回调处理函数(sig_handler):收到该信号的动作
例如:用户在终端键入“Ctrl+C”发起一个终止信号给当前应用进程,停止该进程(应用)。
2.2 异步通知基本过程
【1】主进程指定接收信号回调处理函数,通过signal()\sigaction()函数实现
signal(SIGIO, sig_handler);
【2】“绑定”信号接收进程,通过fcntl 函数执行F_SETOWN命令实现,主进程的ID号就会保存在“filp->f_owner”中,内核收到信号后传递给该ID号的进程
fcntl(fd, F_SET_OWNER, getpid());
【3】设置“FASYNC”,即是使得该进程具备异步通知属性,通过fcntl 函数执行F_SETFL实现
【4】进程可以继续执行其他任务,通知信号到来则执行信号回调函数
f_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC);
2.3 异步通知不足
若有多个设备文件发送异步通知信号给同一个进程时,此时进程无法知道具体发送文件描述符,必须借助IO复用机制(select/poll/epoll)。
3. 异步通知实现
3.1 驱动实现
驱动作为异步信号的发出方,那么在需要通知应用程序的时候产生一个通知信号即可。
【1】异步通知链表加入内核队列——实现“struct file_operations”结构体的“fasync”函数实体
static int memory_fasync(int fd, struct file *pfile, int on)
{
unsigned int mask = 0;
struct memory_device *p;
p = pfile->private_data;
return fasync_helper(fd, pfile, on, &p->r_fasync);
}
应用程序通过fnctl函数执行F_SETFL命令启用FASYNC时,最终会调用驱动程序的fasync函数实体,驱动调用“fasync_helper()”初始化异步通知相关属性,并将异步通知加入内核队列中。
“fasync_helper()”函数由内核提供,其内部实现和数据结构维护由内核实现,在驱动开发时无需深入了解。通过其“struct fasync_struct”结构体,可以初步得知,异步通知功能也是以“链表队列”形式由内核维护。
int fasnyc_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
struct fasync_struct
{
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
};
【2】通知信号产生——在数据可读时通过“kill_fasync”发出“可读”信号(POLL_IN)到内核,内核再传递到指定进程,进程在信号回调处理函数执行读操作
static ssize_t memory_write(struct file * pfile, const char __user *buffer, size_t size, loff_t *offset)
{
unsigned long of = 0;
struct memory_device *p;
p = pfile->private_data;
of = *offset;
if(of > p->mem_size)
{
return 0;
}
if (size > (p->mem_size - of))
{
size = p->mem_size -of;
}
if (copy_from_user(p->mem_buf+of, buffer, size))
{
printk("write memory falied.\n");
return -EFAULT;
}
else
{
*offset += size;
}
p->r_en = true;
wake_up_interruptible(&(p->r_queue)); /* 唤醒休眠进程(读) */
/* 通知进程数据可读
* SIGIO:信号类型
* POLL_IN:普通数据可读
*/
kill_fasync(&p->r_fasync, SIGIO, POLL_IN);
return size;
}
【3】资源释放——驱动退出时应从队列中释放异步通知功能
static ssize_t memory_close(struct inode * inode , struct file * pfile)
{
int state = 0;
struct memory_device *p;
p = pfile->private_data;
if(p->mem_buf)
{
kfree(p->mem_buf);
p->mem_size = 0;
}
memory_fasync(-1, pfile, 0);
return state;
}
3.2 应用实现
应用实现即是“2.2节”的过程,不作赘述。信号处理回调函如下:
void sig_handle(int sig)
{
char buf[16] = {0};
printf("read sig [%d]\n", sig);
lseek(fd, 0, SEEK_SET);
read(fd, buf, 15);
printf("read mem:%s\n", buf);
}
4. 源码及测试
【1】https://github.com/Prry/linux-drivers/tree/master/devmem_signal
5. 参考
【1】https://blog.csdn.net/caoshunxin01/article/details/79355388