前言
驱动程序运行在内核空间中,应用程序运行在用户空间中,两者是不能直接通信的。但在实际应用中,在设备已经准备好的时候,我们希望通知用户程序设备已经ok,用户程序可以读取了,这样应用程序就不需要一直查询该设备的状态,从而节约了资源,这就是异步通知。好,那下一个问题就来了,这个过程如何实现呢?简单,两方面的工作。
一 驱动方面
- 在设备抽象的数据结构中增加一个struct fasync_struct的指针
- 实现设备操作中的fasync函数,这个函数很简单,其主体就是调用内核的fasync_helper函数。
- 在需要向用户空间通知的地方(例如中断中)调用内核的kill_fasync函数。
- 在驱动的release方法中调用前面定义的fasync函数
其中fasync_helper和kill_fasync都是内核函数,我们只需要调用就可以了。在1中定义的指针是一个重要参数,fasync_helper和kill_fasync会使用这个参数。
二 应用层方面
- 利用signal或者sigaction设置SIGIO信号的处理函数
- fcntl的F_SETOWN指令设置当前进程为设备文件owner
- fcntl的F_SETFL指令设置FASYNC标志
完成了以上的工作的话,当内核执行到kill_fasync函数,用户空间SIGIO函数的处理函数就会被调用了。
三代码方面
1.增加异步通知后的globalfifo设备结构体
struct globalfifo_dev
{
struct cdev cdev; //cdev结构体
unsigned int current_len; //fifo有效数据长度
unsigned char mem[GLOBALFIFO_SIZE]; //全局内存
struct mutex mutex; //互斥锁
//struct semaphore sem; //并发控制用的信号量
wait_queue_head_t r_wait; //阻塞读用的等待队列头
wait_queue_head_t w_wait; //阻塞写用的等待队列头
struct fasync_struct *async_queue; //异步结构体指针,用于读
};
2.支持异步通知的globalfifo设备驱动fasync()函数
static int globalfifo_fasync(int fd,struct file *filp,int mode)
{
struct globalfifo_dev *dev=filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
3支持异步通知的globalfifo设备驱动写函数
static ssize_t globalfifo_write(struct file *filp,const char _user *buf,size_t count,loff_t *ppos)
{
struct globalfifo_dev *dev=filp->private_data; //获得设备结构体指针
int ret;
DECLARE_WAITQUEUE(wait,current); //定义等待队列
mutex_ lock(& dev-> mutex); //取得互斥锁
//down(&dev->sem); //取得信号量
add_wait_queue(&dev->w_wait,&wait); //进入写等待队列
//等待fifo未满
while(dev->current_len==GLOBALFIFO_SIZE){
if(filp->f_flags&O_NOBLOCK){
//如果是非阻塞访问
ret=-EAGAIN;
goto out;
}
_set_current_state(TASK_INTERRUPTIBLE) //改变进程状态为睡眠
mutex_unlock(& dev-> mutex);
//up(&dev->sem);
schedule();
if(signal_pending(current){
ret=-ERESTARTSYS;
goto out2;
}
mutex_lock(& dev-> mutex);
//down(&dev->sem);
}
//从用户空间拷贝到内核空间
if(count >GLOBALFIFO_SIZE- dev->current_len)
count=GLOBALFIFO_SIZE-dev->current_len;
if(copy_form_user(dev->mem+dev->current_len,buf,count)) {
ret=-EFAULT;
goto out;
}else{
dev->current_len+=count;
printk( KERN_ INFO "written %d bytes( s), current_ len:% d\ n", count, 38 dev-> current_ len);
wake_up_interruptible(&dev->r_wait); //唤醒读等待队列
//产生异步读信号
if(dev->async_queue){
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
printk( KERN_ DEBUG "%s kill SIGIO\ n", __func__);
}
ret=count;
}
out:mutex_unlock(& dev-> mutex);//up(&dev->sem);
out2:remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
4用globalfifo_fasync()函数将文件从异步通知列表中删除
static int globalfifo_release(struct inode *inode,struct file *filp)
{
globalfifo_fasync(-1,filp,0);
return 0;
}
四验证结果
监控 globalfifo 异步 通知 信号 的 应用 程序
static void signalio_handler(int signum)
{
printf("receive a signal from globalfifo, signalnum:%d\n",signum);
}
void main( void)
{
int fd, oflags;
fd = open("/ dev/ globalfifo", O_ RDWR, S_ IRUSR | S_ IWUSR);
if (fd != -1){
//启动信号驱动机制,将SIGIO信号同input_handler函数关联起来,一旦产生SIGIO信号,就会执行input_handler
signal(SIGIO, input_handler);
//STDIN_FILENO是打开的设备文件描述符,F_SETOWN用来决定操作是干什么的,getpid()是个系统调用,
//功能是返回当前进程的进程号,整个函数的功能是STDIN_FILENO设置这个设备文件的拥有者为当前进程。
fcntl(STDIN_FILENO, F_SETOWN, getpid());
//得到打开文件描述符的状态
oflags = fcntl(STDIN_FILENO, F_GETFL);
//设置文件描述符的状态为oflags | FASYNC属性,一旦文件描述符被设置成具有FASYNC属性的状态,
//也就是将设备文件切换到异步操作模式。这时系统就会自动调用驱动程序的fasync方法。
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
while (1){
sleep( 100);
} else {
printf(" device open failure\ n");
}
}
(注:上面有一行解释为:这时系统就会自动调用驱动程序的fasync方法,我的理解是:这个时候我们联系一下后面的驱动,我们会发现在驱动层,是在file_operation中对应的fasync的函数调用,这是,会首先调fasync对应的my_fasync函数,而后者会进行 将该设备登记到fasync_queue队列中去,为后续做准备)
以下是几点说明:
1 两个函数的原型
int fasync_helper(struct inode *inode, struct file *filp, int mode, struct fasync_struct **fa);
一个”帮忙者”, 来实现 fasync 设备方法. mode 参数是传递给方法的相同的值, 而 fa 指针指向一个设
备特定的 fasync_struct *
void kill_fasync(struct fasync_struct *fa, int sig, int band);
如果这个驱动支持异步通知, 这个函数可用来发送一个信号到登记在 fa 中的进程.
2.fasync_helper 用来向等待异步信号的设备链表中添加或者删除设备文件, kill_fasync被用来通知拥有相关设备的进程. 它的参数是被传递的信号(常常是 SIGIO)和 band, 这几乎都是 POLL_IN[25](但是这可用来发送”紧急”或者带外数据, 在网络代码里).