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

【2】http://blog.chinaunix.net/uid-21714580-id-119967.html

posted @ 2019-11-02 22:37  Acuity  阅读(109)  评论(0编辑  收藏  举报