#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/wait.h> #include <asm/semaphore.h> MODULE_LICENSE("GPL"); #define MAJOR_NUM 254 static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*); static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*); struct file_operations globalvar_fops = { read: globalvar_read, write: globalvar_write, }; static int global_var = 0; static struct semaphore sem; static wait_queue_head_t outq; static int flag = 0; static int __init globalvar_init(void) { int ret; ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops); if (ret) { printk("globalvar register failure"); } else { printk("globalvar register success"); init_MUTEX(&sem); init_waitqueue_head(&outq); } return ret; } static void __exit globalvar_exit(void) { int ret; ret = unregister_chrdev(MAJOR_NUM, "globalvar"); if (ret) { printk("globalvar unregister failure"); } else { printk("globalvar unregister success"); } } static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off) { //等待数据可获得 if (wait_event_interruptible(outq, flag != 0)) { return - ERESTARTSYS; } if (down_interruptible(&sem)) { return - ERESTARTSYS; } flag = 0; if (copy_to_user(buf, &global_var, sizeof(int))) { up(&sem); return - EFAULT; } up(&sem); return sizeof(int); } static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off) { if (down_interruptible(&sem)) { return - ERESTARTSYS; } if (copy_from_user(&global_var, buf, sizeof(int))) { up(&sem); return - EFAULT; } up(&sem); flag = 1; //通知数据可获得 wake_up_interruptible(&outq); return sizeof(int); } module_init(globalvar_init); module_exit(globalvar_exit); |
编写两个用户态的程序来测试,第一个用于阻塞地读/dev/globalvar,另一个用于写/dev/globalvar。只有当后一个对/dev/globalvar进行了输入之后,前者的read才能返回。
读的程序为:
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> main() { int fd, num; fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { read(fd, &num, sizeof(int)); //程序将阻塞在此语句,除非有针对globalvar的输入 printf("The globalvar is %d\n", num); //如果输入是0,则退出 if (num == 0) { close(fd); break; } } } else { printf("device open failure\n"); } } |
写的程序为:
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> main() { int fd, num; fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { printf("Please input the globalvar:\n"); scanf("%d", &num); write(fd, &num, sizeof(int)); //如果输入0,退出 if (num == 0) { close(fd); break; } } } else { printf("device open failure\n"); } } |
打开两个终端,分别运行上述两个应用程序,发现当在第二个终端中没有输入数据时,第一个终端没有输出(阻塞),每当我们在第二个终端中给globalvar输入一个值,第一个终端就会输出这个值,如下图:
第一个终端就会输出这个值 |
关于上述例程,我们补充说一点,如果将驱动程序中的read函数改为:
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off) { //获取信号量:可能阻塞 if (down_interruptible(&sem)) { return - ERESTARTSYS; } //等待数据可获得:可能阻塞 if (wait_event_interruptible(outq, flag != 0)) { return - ERESTARTSYS; } flag = 0; //临界资源访问 if (copy_to_user(buf, &global_var, sizeof(int))) { up(&sem); return - EFAULT; } //释放信号量 up(&sem); return sizeof(int); } |
即交换wait_event_interruptible(outq, flag != 0)和down_interruptible(&sem)的顺序,这个驱动程序将变得不可运行。实际上,当两个可能要阻塞的事件同时出现时,即两个wait_event或down摆在一起的时候,将变得非常危险,死锁的可能性很大,这个时候我们要特别留意它们的出现顺序。当然,我们应该尽可能地避免这种情况的发生!
+还有一个与设备阻塞与非阻塞访问息息相关的论题,即select和poll,select和poll的本质一样,前者在BSD Unix中引入,后者在System V中引入。poll和select用于查询设备的状态,以便用户程序获知是否能对设备进行非阻塞的访问,它们都需要设备驱动程序中的poll函数支持。
驱动程序中poll函数中最主要用到的一个API是poll_wait,其原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait); |
static unsigned int globalvar_poll(struct file *filp, poll_table *wait) { unsigned int mask = 0; poll_wait(filp, &outq, wait); //数据是否可获得? if (flag != 0) { mask |= POLLIN | POLLRDNORM; //标示数据可获得 } return mask; } |
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
struct timeval { int tv_sec; /* seconds */ int tv_usec; /* microseconds */ }; |
除此之外,我们还将使用下列API:
FD_ZERO(fd_set *set)――清除一个文件描述符集;
FD_SET(int fd,fd_set *set)――将一个文件描述符加入文件描述符集中;
FD_CLR(int fd,fd_set *set)――将一个文件描述符从文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)――判断文件描述符是否被置位。
下面的用户态测试程序等待/dev/globalvar可读,但是设置了5秒的等待超时,若超过5秒仍然没有数据可读,则输出"No data within 5 seconds":
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> main() { int fd, num; fd_set rfds; struct timeval tv; fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { //查看globalvar是否有输入 FD_ZERO(&rfds); FD_SET(fd, &rfds); //设置超时时间为5s tv.tv_sec = 5; tv.tv_usec = 0; select(fd + 1, &rfds, NULL, NULL, &tv); //数据是否可获得? if (FD_ISSET(fd, &rfds)) { read(fd, &num, sizeof(int)); printf("The globalvar is %d\n", num); //输入为0,退出 if (num == 0) { close(fd); break; } } else printf("No data within 5 seconds.\n"); } } else { printf("device open failure\n"); } } |
开两个终端,分别运行程序:一个对globalvar进行写,一个用上述程序对globalvar进行读。当我们在写终端给globalvar输入一个值后,读终端立即就能输出该值,当我们连续5秒没有输入时,"No data within 5 seconds"在读终端被输出,如下图:
"No data within 5 seconds"在读终端被输出 |
上一页 [1] [2] [3] [4]