一、fsync
用来同步设备的写入操作,考虑把一块设局写入到硬盘的操作,如果使用write函数,函数返回后只能保证数据被写入到驱动程序或者内核管理的数据缓存中,而无法保证数据被真正写入到硬盘的存储块里。但是fync可以做到这一点,函数在数据块没有真正写道硬盘的存储卡里时不会返回,若返回则意味着要么设备在写入过程发生错误,要么数据已经写入到了设备的存储块。
显然大量的工作都在驱动程序这边,驱动程序需要针对不同的设备实现wirte和fsync例程,以满足应用程序的需求。
对于字符设备而言,大部分驱动程序都没有实现这个例程,只是简单将它们的struct file_operations对象的fsync指针赋值为NULL;对于块设备而言,则需要实现fsync。
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
二、fasync(异步通知)
在异步非阻塞型I/O(AIO)中,基于驱动程序poll例程之上的应用层的三个函数poll、select、epoll,它们在与驱动程序沟通数据是否就绪时,本质是采用了轮询的方式:应用程序在一组由设备文件描述符的集合上调用poll,由此获得设备可否进行无阻塞操作的信息。
其实除了轮询的方式,应用程序与设备驱动的沟通还有一种类似中断的方式;当设备中的数据就绪时,作为通知的方式,驱动程序给应用程序发送一个signal。
异步通知使用了系统调用的signal()和sigcation()函数。signal()函数会让一个信号和一个函数对应,每当接收到这个信号时就会调用相应的函数来处理。异步通知处理过程中用户空间和设备驱动的交互如下:
应用层的处理
1)设置信号处理函数:
我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数。在应用程序中使用 signal 函数来设置指定信号的处理函数:
//signum:要设置处理函数的信号。
//handler: 信号的处理函数。
//返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。
sighandler_t signal(int signum, sighandler_t handler);
2)fcntl函数需要做二件事:
将本应用程序的进程号告诉给内核使用:
/*通过fcntl函数的F_SETOWN命令将进程的ID告诉驱动程序,这样当驱动发现设备的数据就绪时才
直到要通知哪个进程*/
fcntl(fd, F_SETOWN, getpid());
使用如下两行程序开启异步通知:
/*获取当前的进程状态*/
flags = fcntl(fd, F_GETFL);
/*
通过F_SETFL命令设置FASYNC标志让驱动程序启动异步通知机制(开启当前进程异步通知功能),
经过这一步,驱动程序中的 fasync 函数就会执行。
*/
fcntl(fd, F_SETFL, flags | FASYNC);
下面是内核的系统调用接口,从中可以看到会调用驱动程序提供的fasync例程:
static int setfl(int fd, struct file * filp, unsigned long arg)
{
...
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
...
}
...
}
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
{
long err = -EINVAL;
...
switch (cmd) {
...
case F_SETFL:
/*会在内部直接调用驱动程序提供的fasync例程*/
err = setfl(fd, filp, arg);
break;
...
case F_SETOWN:
/*将要通知进程的ID相关信息记录在filp->f_owner中,驱动程序无需对此做出回应*/
err = f_setown(filp, arg, 1);
break;
...
default:
break;
}
return err;
}
内核层的处理
在设备驱动和应用程序的异步通知交互中, 仅仅在应用程序端捕获信号是不够的, 因为信号的源头在设备驱动端。 因此, 应该在合适的时机让设备驱动释放信号, 在设备驱动程序中增加信号释放的相关代码。
设备驱动中异步通知编程比较简单, 主要用到一项数据结构和两个函数。使用时,首先需要定义一个struct fasync_struct类型的指针,当用户程序调用fcntl用F_SETFL命令来设置或者清除FASYNC标志时,驱动程序应该在其fasync例程中调用内核提供的fasync_helper函数在struct fasync_struct指针所指向的链表中增加或者删除一个节点,每个节点代表一个需要通知的进程。其次,当进程所需的数据就绪或者关注的某个事件发生时,驱动程序使用内核提供的kill_fasync函数负责向维护的struct fasync_struct链表中的每个进程发送通知信号。
数据结构是fasync_struct结构体:
支持异步通知的设备结构体模板如下:
struct fasync_struct {
rwlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};
struct xxx_dev {
struct cdev cdev;
...
struct fasync_struct *fasync_queue //异步结构体指针
}
两个函数分别是:
1)需要在设备驱动中实现 file_operations 操作集中的 fasync 函数:
int (*fasync) (int, struct file *, int);
该函数一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct结构体指针。fasync_helper主要将当前要通知的进程加入一个链表或者从链表中移除,这取决于应用程序调用fcntl时是否设置了FASYNC标志。
/*
1)第二个参数on:上面提到的setfl()函数中,传递给它的是一个条件表达式(arg & FASYNC) != 0,
这意味着如果应用程序在调用fcntl时,对于F_SETFL命令使用的参数arg设置了FASYNC,那么
(arg & FASYNC) != 0结果为1,所以参数on将为1,这表明应用程序正在启用驱动程序的异步通知
机制;反之,当对fcntl函数使用F_SETFL命令时清除了FASYNC标志,将导致驱动程序的fasync例程
关闭异步通知特性。
2)所以fasync_helper的主要作用是维护一个需要通知的进程链表fapp,如果应用程序需要获得异步通知
的能力,那么需要通过fcntl的F_SETFL命令设置FASYNC标志,如果设置了该标志,驱动程序的fasync例程
在调用fasync_helper时将用fasync_add_entry将需要通知的进程加入到驱动程序维护的一个链表中,
否则使用fasync_remove_entry将其从链表中移除。
3)链表的类型为struct fasync_struct,链表中的每个节点对象代表着一个需要通知的进程,进程ID
信息存放再节点对象的fa_file->f_owner中。例如当有二个用户进程需要得到设备驱动程序的异步通知时,
内核在实现fasync_helper函数时,对于一个新加入的struct fasync_struct对象节点,会将其放到
链表的头部,这就意味着应用层后调用fcntl(fd, F_SETFL, FASYNC)的应用程序反而先得到通知。
*/
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函数中, 只需要简单地将该函数的3个参数以及fasync_struct结构体指针的指针作为第4个参数传入fasync_helper函数即可。
//在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放fasync_struct
2)kill_fasync则在设备中的某一事件发生时负责通知链表中的所有相关的进程。
现在,驱动程序已经将需要通知的进程所在的节点加入了fasync链表。当需要的条件满足时,比如进程所请求的数据已经就绪,驱动程序需要使用kill_fasync来向fasync链表中的每个等待通知的进程发送通知信号。
/*通过while循环遍历fasync链表,定义每个进程调用send_sigio来向其发送信号SIGIO以通知进程*/
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct *fown;
unsigned long flags;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
read_lock_irqsave(&fa->fa_lock, flags);
if (fa->fa_file) {
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);
}
read_unlock_irqrestore(&fa->fa_lock, flags);
fa = rcu_dereference(fa->fa_next);
}
}
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
if (*fp) {
rcu_read_lock();
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
rcu_read_unlock();
}
}
使用模板: