《驱动学习 - 异步通知》
1.异步通知
前面介绍的几种按键相关:
- 应用程序中使用while(1),一直去轮询查按键的状态。(这样会导致应用程序一直占用cpu资源)
- 使用中断的方式,在应用程序中通过read,然后进入驱动程序中,使应用程序进入休眠。然后发生中断的时候,在中断服务函数中将进程唤醒,返回按键值。(导致应用程序一直在休眠)
- 使用poll机制,如果在规定的时间内没用中断发生,则超时返回。
以上三种都是针对应用程序。
接下来介绍一种异步通知机制:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。
kill -9 pid是我们经常使用用来结束某个进程得命令。其实这个就是一种异步通知。
kill是一个进程,用作发送信号
9是一个信号
pid是另一个进程,是接收信号
当pid这个进程接收到9这个信号,就会调用对应得函数。默认状态就是结束进程。
举例:
#include <signal.h> void my_signal_test(int signum) { static int cnt = 0; printf("signal = %d,%d times\n",signum,++cnt); } int main(int argc,char **argv) { signal(SIGUSR1, my_signal_test);//建立一个信号函数,接收的信号是SIGUSR1表示用户可用的信号值 while(1) { sleep(1000); } return 0; }
signal(SIGUSR1, my_signal_test);就是如果接收到SIGUSR1信号,就会调用my_signal_test函数。然后在函数中就打印信息。
这样就可以在命令行中先运行./signal &,通过ps查看signal进程得pid,然后kill USR1 pid。就可以查看到打印信息。
2.异步通知驱动编写
static struct file_operations sencod_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = fifth_drv_open, .read = fifth_drv_read, .release = fifth_drv_close, .poll = fifth_drv_poll, .fasync = fifth_drv_fasync, };
首先在file_operations结构体中定义好,
fasync函数:
static struct fasync_struct *button_async;
static int fifth_drv_fasync (int fd, struct file *filp, int on) { printk("driver: fifth_drv_fasync\n"); return fasync_helper (fd, filp, on, &button_async); }
定义一个fasync_struct结构体指针,然后通过fasync_helper对这个指针变量进行初始化。
fasync_helper函数:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { struct fasync_struct *new = NULL; if (on) { new = kmem_cache_alloc(fasync_cache, GFP_KERNEL); if (!new) return -ENOMEM; } if (on) { new->magic = FASYNC_MAGIC; new->fa_file = filp; new->fa_fd = fd; new->fa_next = *fapp; *fapp = new; result = 1; } }
可以从源码上看出,就是将button_async申请一块内存,然后进行一系列初始化。
中断服务函数:
static irqreturn_t buttons_irq(int irq, void *dev_id) { /*略*/ kill_fasync (&button_async, SIGIO, POLL_IN); }
通过kill_fasync来发送SIGIO信号到应用程序中。
3.分析fcntl源码
在应用程序中调用fcntl函数,就会调用SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)。(在旧内核是sys_fcntl)
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg) { struct file *filp; long err = -EBADF; filp = fget(fd); //通过文件描述符获得对应关联的文件指针 if (!filp) goto out; /*略*/ err = do_fcntl(fd, cmd, arg, filp); //调用do_fcntl函数 fput(filp); out: return err; }
do_fcntl函数:
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { long err = -EINVAL; switch (cmd) { case F_DUPFD: case F_DUPFD_CLOEXEC: if (arg >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) break; err = alloc_fd(arg, cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0); if (err >= 0) { get_file(filp); fd_install(err, filp); } break; case F_GETFD: err = get_close_on_exec(fd) ? FD_CLOEXEC : 0; break; case F_SETFD: err = 0; set_close_on_exec(fd, arg & FD_CLOEXEC); break; case F_GETFL: err = filp->f_flags; break; case F_SETFL: err = setfl(fd, filp, arg); break; case F_GETLK: err = fcntl_getlk(filp, (struct flock __user *) arg); break; case F_SETLK: case F_SETLKW: err = fcntl_setlk(fd, filp, cmd, (struct flock __user *) arg); break; case F_GETOWN: /* * XXX If f_owner is a process group, the * negative return value will get converted * into an error. Oops. If we keep the * current syscall conventions, the only way * to fix this will be in libc. */ err = f_getown(filp); force_successful_syscall_return(); break; case F_SETOWN: err = f_setown(filp, arg, 1); break; case F_GETOWN_EX: err = f_getown_ex(filp, arg); break; case F_SETOWN_EX: err = f_setown_ex(filp, arg); break; case F_GETSIG: err = filp->f_owner.signum; break; case F_SETSIG: /* arg == 0 restores default behaviour. */ if (!valid_signal(arg)) { break; } err = 0; filp->f_owner.signum = arg; break; case F_GETLEASE: err = fcntl_getlease(filp); break; case F_SETLEASE: err = fcntl_setlease(fd, filp, arg); break; case F_NOTIFY: err = fcntl_dirnotify(fd, filp, arg); break; default: break; } return err; }
do_fcntl函数里面就是一个switch,根据传进来的cmd执行相对应的函数。
其中主要注意两个F_SETOWN和F_SETFL
F_SETOWN:
最后主要调用:
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type, int force) { write_lock_irq(&filp->f_owner.lock); if (force || !filp->f_owner.pid) { put_pid(filp->f_owner.pid); filp->f_owner.pid = get_pid(pid); filp->f_owner.pid_type = type; if (pid) { const struct cred *cred = current_cred(); filp->f_owner.uid = cred->uid; filp->f_owner.euid = cred->euid; } } write_unlock_irq(&filp->f_owner.lock); }
F_SETOWN被调用时对filp->f_owner赋值,此外什么也不做;
F_SETFL:
static int setfl(int fd, struct file * filp, unsigned long arg) {
/*略*/ if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op &&filp->f_op->fasync)
{ error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); if (error < 0) goto out; if (error > 0) error = 0; }
/*略*/ }
在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_flags中的FASYNC标识发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其能正确响应。文件打开时,FASYNC标志被默认为是清除的。
Linux的这种通用方法基于一个数据结构和两个函数:
struct fasync_struct { int magic; int fa_fd;//文件描述符 struct fasync_struct *fa_next; /* singly linked list *///异步通知队列 struct file *fa_file;//文件指针 }; /* SMP safe fasync helpers: */ extern int fasync_helper(int, struct file *, int, struct fasync_struct **); /* can be called from interrupts */ extern void kill_fasync(struct fasync_struct **, int, int);
当一个打开的文件的FASYNC标志被修改时,调用fasync_helper函数以便从相关的进程表中添加或删除文件。
当数据到达时,可使用kill_fasync函数通知所有的相关进程。(在UNIX语义中,kill这个词常用来向进程发送信号,而不是杀死某个进程,raise则用于向自身发送信号)。
4.应用程序的编写
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <poll.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> /* fifthdrvtest */ int fd; void my_signal_fun(int signum) { unsigned char key_val; read(fd, &key_val, 1); printf("key_val: 0x%x\n", key_val); } int main(int argc, char **argv) { unsigned char key_val; int ret; int Oflags; signal(SIGIO, my_signal_fun); fd = open("/dev/buttons", O_RDWR); if (fd < 0) { printf("can't open!\n"); } fcntl(fd, F_SETOWN, getpid()); Oflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, Oflags | FASYNC); while (1) { sleep(1000); } return 0; }
signal(SIGIO, my_signal_fun);
让my_signal_fun去处理SIGIO信号
fcntl(fd, F_SETOWN, getpid())
为了启动文件的异步通知机制,用户程序必须执行两个步骤。首先,她们指定一个进程作为文件的“属主(owner)”。当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中。这一步是必需的,目的是为了让内核知道应该通知哪个进程。
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
然后为了真正启动异步通知机制,用户程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的。
执行完这两个步骤之后,输入文件就可以在新数据到达时请求发送一个SIGIO信号。该信号被发送到存放在filp->f_owner中的进程(如果是负值就是进程组)。
https://blog.csdn.net/wenqian1991/article/details/50333655