1.异步通知机制的实现理论:
最基本的read()和write()函数实现的数据最基本的交互.等待队列实现了读写阻塞策略,针对一些比较长时间才可以获取资源的设备进程进行休眠处理进而优化了系统资源的利用率;poll机制可以实现设备的监听,可以进一步提高读写机制的命中时机.而异步通知则类似于硬件上的中断,当设备资源准备就绪,向指定进程发送信号,从而唤醒进程对此信号进行处理.poll的方式是用户主动式,当用户侦听到设备信息的情况从而裁决下一步的动作,而异步通知是设备主动式,当设备准备就绪的情况下,会自动通知用户.
总而言之,读写是必须的核心动作,而wait_queue、poll和fasync都是这个核心动作的优化,从而使系统资源得以高效利用.
因此,从异步通知的实质机制的理论上讲,要实现异步通知需要实现下面三步:
1).要有信号;
2).绑定信号;
3).处理信号.
2.实现异步通知的细则:
根据异步通知机制可知,信号源是由驱动发出,用户进程负责捕捉接收由驱动发出的信号而裁决下一步的动作.但是内核有很多处可能发出信号,而上层也有可能有多个进程在等待信号.为了让特定的进程接收到特定的设备的信号,这过程需要一个"绑定"的过程.下面从用户空间到底层看一下如何实现这个过程.
2-1.处理信号:
和纯粹的用户空间操作一样,向一个进程发送指定的信号,当此进程接收到此信号时,引发对此信号的处理.示意代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
void sigterm_handler(int signo)
{
printf("Have Caught Sig %d\n",signo);
}
int main(int argc,char **argv)
{
signal(SIGINT,sigterm_handler);
while(1);
return 0;
}
执行结果如下:
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# gcc signal.c -o signal
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# ./signal
查看signal进程:
root@seven-laptop:~# ps -e | grep signal
23570 pts/1 00:00:29 signal
向进程signal发送SIGINT(2)信号:
root@seven-laptop:~# kill -s 2 23570 进程接收到SIGINT信号的表现:
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# ./signal
Have Caught Sig 2
所以,用户空间必须得实现捕捉信号并处理信号这一步.只不过这里的信号纯粹的用户空间发起的而不是由驱动发起的.
2-2.绑定信号:
如果信号的发源地是设备驱动的时候,为了进程唯一地能处理到此设备驱动的信号,需要一个绑定的过程.实现步骤如下:
1).通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程;F:表示设备文件;SETOWN:设置拥有者(OWN);
2).通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式;
3).当然也是必须的,捕捉并处理信号的例程函数. 下面的代码来自宋宝华<<LINUX设备驱动开发详解第二版>>.
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
len = read(STDIN_FILENO,&data,MAX_LEN);
data[len] = 0;
printf("input available:%s\n",data);
}
int main(int argc,char **argv)
{
int oflags;
signal(SIGIO,input_handler);
fcntl(STDIN_FILENO,F_SETOWN,getpid());
oflags = fcntl(STDIN_FILENO,F_GETFL);
fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
while(1);
return 0;
}
执行结果如下:
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# gcc syncsignal.c -o syncsignal
root@seven-laptop:~/learn/LddDemo/linuxdriver_code_tool/09# ./syncsignal
Do you think you are sth?
input available:Do you think you are sth?
No,You are nothing!
input available:No,You are nothing!
这里的设备文件为标准的输入流STDIN_FILENO.下面是关于函数fcntl()的简要分析.
[附:]
fcntl()函数针对文件描述符提供控制.其原型如下:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
各参数意义如下:
fd:文件描述词;
cmd:操作命令;
arg:供命令使用的参数;
lock:同上. 在这里用到的fcntl()参数说明如下:
fd:
标准输入终端--STDIN_FILENO;
cmd: F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识,这将决定第三个参数的形式,如getpid();
F_GETFL:读取文件状态标志;
F_SETFL:设置文件状态标志.其中O_RDONLY,O_WRONLY,O_RDWR,O_CREAT, O_EXCL,O_NOCTTY 和O_TRUNC不受影响,可以更改的标志有 O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME 和 O_NONBLOCK.此参数影响第三个参数.如oflags | FASYNC.
下面语句:
fcntl(STDIN_FILENO,F_SETFL,oflags | FASYNC); 对设备设置了FASYNC标志,启用异步通知机制.
2-3.设备驱动发出信号
异步通知的信号源来自底层设备驱动.要实现底层设备驱动向指定的进程发送信号,主要操作的对象为一个数据结构和两个函数.
其中,数据结构是:
struct fasync_struct;
两个函数是:
int fasync_helper(int fd,struct file *filp,int mode,struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa,int sig,int band);
其中第一个函数是从相关进程列表增删文件.是文件操作集域fasync的核心组成部分.如下:
static int scull_p_fasync(int fd,struct file *filp,int mode)
{
struct scull_pipe *dev = filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
前三个参数均由用户空间的open动作加工而成,最后一个参数则由具体驱动程序提供.因此,驱动要实现异步通知,则必须实现这样的一个结构体,这类似于等待队列要实现一个队列头一样,然后辅助函数fasync_helper()进行相应的关联.
而第二个函数则是负责发出信号.举例如下:
if(dev->async_queue)
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
第一个参数是具体的驱动定义的,第二个是信号源;第三个参数表示数据可读.
[注:]POLL_IN、POLL_OUT是在用户空间的角度看的.IN表示数据可读,OUT表示可写.
当用户进行最后次的close()时,异步通知还需要调用下面的函数来实现异步通知资源的释放:
scull_p_fasync(-1,filp,0);
3.实例:
UserSpace:
/*======================================================================
A test program to access /dev/second
This example is to help understand async IO
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
void input_handler(int signum)
{
printf("receive a signal from globalfifo,signalnum:%d\n",signum);
}
main()
{
int fd, oflags;
fd = open("/dev/globalfifo", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
signal(SIGIO, input_handler);
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
while(1)
{
sleep(100);
}
}
else
{
printf("device open failure\n");
}
}
KernelSpace:
/*======================================================================
A globalfifo driver as an example of char device drivers
This example is to introduce asynchronous notifier
The initial developer of the original code is Baohua Song
<author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#define GLOBALFIFO_SIZE 0x1000 /*全局fifo最大4K字节*/
#define FIFO_CLEAR 0x1 /*清0全局内存的长度*/
#define GLOBALFIFO_MAJOR 251 /*预设的globalfifo的主设备号*/
static int globalfifo_major = GLOBALFIFO_MAJOR;
/*globalfifo设备结构体*/
struct globalfifo_dev
{
struct cdev cdev; /*cdev结构体*/
unsigned int current_len; /*fifo有效数据长度*/
unsigned char mem[GLOBALFIFO_SIZE]; /*全局内存*/
struct semaphore sem; /*并发控制用的信号量*/
wait_queue_head_t r_wait; /*阻塞读用的等待队列头*/
wait_queue_head_t w_wait; /*阻塞写用的等待队列头*/
struct fasync_struct *async_queue; /* 异步结构体指针,用于读 */
};
struct globalfifo_dev *globalfifo_devp; /*设备结构体指针*/
/*文件打开函数*/
int globalfifo_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = globalfifo_devp;
return 0;
}
/*文件释放函数*/
int globalfifo_release(struct inode *inode, struct file *filp)
{
/* 将文件从异步通知列表中删除 */
globalfifo_fasync(-1, filp, 0);
return 0;
}
/* ioctl设备控制函数 */
static int globalfifo_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case FIFO_CLEAR:
down(&dev->sem); //获得信号量
dev->current_len = 0;
memset(dev->mem,0,GLOBALFIFO_SIZE);
up(&dev->sem); //释放信号量
printk(KERN_INFO "globalfifo is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data; /*获得设备结构体指针*/
down(&dev->sem);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
/*fifo非空*/
if (dev->current_len != 0)
{
mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
}
/*fifo非满*/
if (dev->current_len != GLOBALFIFO_SIZE)
{
mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
}
up(&dev->sem);
return mask;
}
/* 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);
}
/*globalfifo读函数*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data; //获得设备结构体指针
DECLARE_WAITQUEUE(wait, current); //定义等待队列
down(&dev->sem); //获得信号量
add_wait_queue(&dev->r_wait, &wait); //进入读等待队列头
/* 等待FIFO非空 */
if (dev->current_len == 0)
{
if (filp->f_flags &O_NONBLOCK)
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为睡眠
up(&dev->sem);
schedule(); //调度其他进程执行
if (signal_pending(current))
//如果是因为信号唤醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
/* 拷贝到用户空间 */
if (count > dev->current_len)
count = dev->current_len;
if (copy_to_user(buf, dev->mem, count))
{
ret = - EFAULT;
goto out;
}
else
{
memcpy(dev->mem, dev->mem + count, dev->current_len - count); //fifo数据前移
dev->current_len -= count; //有效数据长度减少
printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait); //唤醒写等待队列
ret = count;
}
out: up(&dev->sem); //释放信号量
out2:remove_wait_queue(&dev->w_wait, &wait); //从附属的等待队列头移除
set_current_state(TASK_RUNNING);
return ret;
}
/*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); //定义等待队列
down(&dev->sem); //获取信号量
add_wait_queue(&dev->w_wait, &wait); //进入写等待队列头
/* 等待FIFO非满 */
if (dev->current_len == GLOBALFIFO_SIZE)
{
if (filp->f_flags &O_NONBLOCK)
//如果是非阻塞访问
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为睡眠
up(&dev->sem);
schedule(); //调度其他进程执行
if (signal_pending(current))
//如果是因为信号唤醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem); //获得信号量
}
/*从用户空间拷贝到内核空间*/
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
if (copy_from_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, dev
->current_len);
wake_up_interruptible(&dev->r_wait); //唤醒读等待队列
/* 产生异步读信号 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
ret = count;
}
out: up(&dev->sem); //释放信号量
out2:remove_wait_queue(&dev->w_wait, &wait); //从附属的等待队列头移除
set_current_state(TASK_RUNNING);
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalfifo_fops =
{
.owner = THIS_MODULE,
.read = globalfifo_read,
.write = globalfifo_write,
.ioctl = globalfifo_ioctl,
.poll = globalfifo_poll,
.open = globalfifo_open,
.release = globalfifo_release,
.fasync = globalfifo_fasync,
};
/*初始化并注册cdev*/
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalfifo_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
/* 申请设备号*/
if (globalfifo_major)
ret = register_chrdev_region(devno, 1, "globalfifo");
else /* 动态申请设备号 */
{
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
globalfifo_major = MAJOR(devno);
}
if (ret < 0)
return ret;
/* 动态申请设备结构体的内存*/
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if (!globalfifo_devp) /*申请失败*/
{
ret = - ENOMEM;
goto fail_malloc;
}
memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
globalfifo_setup_cdev(globalfifo_devp, 0);
init_MUTEX(&globalfifo_devp->sem); /*初始化信号量*/
init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化读等待队列头*/
init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化写等待队列头*/
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return ret;
}
/*模块卸载函数*/
void globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev); /*注销cdev*/
kfree(globalfifo_devp); /*释放设备结构体内存*/
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*释放设备号*/
}
MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");
module_param(globalfifo_major, int, S_IRUGO);
module_init(globalfifo_init);
module_exit(globalfifo_exit);
4.疑惑
在异步通知机制中,我们只定义了一个数据结构和两个函数,但是内核却可以实现向驱动对应的用户进程发送信号.这过程和等待队列机制一样,只定义了一个队列头和操作一些相关的关键函数,却实现了进程的休眠.逻辑上感觉有点"虚无缥缈".为了保持逻辑上的连贯性.下面对异步通知机制的两个关键函数进行宏观的逻辑分析.
4-1.int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) 此函数承担了操作集fops里面fasync域的核心工作.其中,参数fapp是我们具体驱动定义的,其余的均为根据用户空间的信息构建起来的:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
从字面上理解,此函数是完成fasync队列的增/删.
/*
* Add a fasync entry. Return negative on error, positive if
* added, and zero if did nothing but change an existing one.
*
* NOTE! It is very important that the FASYNC flag always
* match the state "is the filp on a fasync list".
*/
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *new, *fa, **fp;
int result = 0;
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
if (!new)
return -ENOMEM;
spin_lock(&filp->f_lock);
write_lock_irq(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file != filp)
continue;
fa->fa_fd = fd;
kmem_cache_free(fasync_cache, new);
goto out;
}
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
result = 1;
filp->f_flags |= FASYNC;
out:
write_unlock_irq(&fasync_lock);
spin_unlock(&filp->f_lock);
return result;
}
其中语句:
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
和等待队列一样(动态生成队列),这里动态生成(从slab池摘取)了一个new的fasync_struct结构体,并把我们具体驱动实现的结构体指针fapp挂在它后面的节点,而我们的fapp又回指向new,形成一个双向链表.后续的异步通知的机制里面,new变成了其核心流窜的数据体.
4-2.void kill_fasync(struct fasync_struct **fp, int sig, int band) 此函数完成信号源的发出:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp) {
read_lock(&fasync_lock);
/* reread *fp after obtaining the lock */
__kill_fasync(*fp, sig, band);
read_unlock(&fasync_lock);
}
}
其绝大部分工作由函数__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;
}
}
其中,fa便是我们具体驱动实现的并被函数fasync_helper()初始化的.下面语句便是获取对应进程的接收者:
fown = &fa->fa_file->f_owner;
然后向对应进程发送信号:
send_sigio(fown, fa->fa_fd, band);
这里还有一个疑问,我们传递进来的信号在这里只是用来作判断是不是SIGURG信号,它并没有发送到上层进程.难道是遍历把所有信号都发一遍?
|