首先,先来了解一下设备的阻塞与非阻塞操作以及实现阻塞操作的方法:
1.设备的阻塞与非阻塞操作:
阻塞操作是指,在执行设备操作时,若不能获得资源,则进程被挂起直到满足可操作的条件再进行操作。非阻塞操作是指,当进程不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止。
2.实现阻塞操作的方法:
在linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞访问。
一,glob字符设备驱动程序的编写,把文件名命名为glob.c,源代码如下:
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/wait.h> //有关等待队列的头文件 #include <linux/semaphore.h> //有关信号量的头文件 #include <linux/sched.h> MODULE_LICENSE("GPL"); #define MAJOR_NUM 1400 #define DEVICE_NAME "glob" static int glob_var = 0; static struct semaphore sem; //定义信号量 static wait_queue_head_t outq; //定义一个等待队列头 static int flag = 0; //*******************定义read方法**************************** static ssize_t glob_read(struct file *filp, char *buf, ssize_t len, loff_t *off) { //等待数据可获得 //wait_event_interruptible的返回一个整数值,非零值表示休眠被某个信号中断 //wait_event_interruptible中第一个参数是等待队列头,第二个参数是一个布尔表达式,在条件为真之前,进程会保持休眠 if (wait_event_interruptible(outq, flag != 0)) { return - ERESTARTSYS; } //down_interruptible 函数返回非零值,表示操作被中断,调用者拥有信号量失败 if (down_interruptible(&sem)) { return - ERESTARTSYS; } flag = 0; //将内核空间中的数据移动到用户空间 if (copy_to_user(buf, &glob_var, sizeof(int))) { up(&sem); //移动数据的操作不完全成功也需要释放信号量 return - EFAULT; } up(&sem);//移动数据成功,释放信号量 return sizeof(int); } //************************定义write方法****************************** //glob_write函数中,flip是文件指针,buf是指向用户空间的缓冲区,len表示请求传输数据的长度, //off指向一个长偏移量类型对象的指针,这个对象指明用户在文件中进行存储操作的位置 static ssize_t glob_write(struct file *filp, const char *buf, ssize_t len,loff_t *off) { if (down_interruptible(&sem)) { return - ERESTARTSYS; } //将用户空间的数据移动到内核空间 if (copy_from_user(&glob_var, buf, sizeof(int))) { up(&sem); //移动数据不完全成功也需要释放信号量 return - EFAULT; } up(&sem); //移动数据成功,释放信号量 flag = 1; //通知数据可获得 wake_up_interruptible(&outq); //唤醒休眠进程 return sizeof(int); } //************初始化file_operations结构体************* struct file_operations glob_fops = { .owner = THIS_MODULE, .read = glob_read, .write = glob_write, }; //*******模块初始化函数********* static int __init glob_init(void) { int ret; ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &glob_fops); if (ret) { printk("glob register failure"); } else { printk("glob register success"); //init_MUTEX(&sem); sema_init(&sem,1); //初始化一个互斥锁,把信号量sem的值设置为1 init_waitqueue_head(&outq); //初始化等候队列头 } return ret; } //************模块卸载函数************** static void __exit glob_exit(void) { unregister_chrdev(MAJOR_NUM, DEVICE_NAME); printk("glob unregister success!\n"); } module_init(glob_init); module_exit(glob_exit);
二,Makefile文件的编写,源代码如下:
obj-m:=glob.o default: $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
三,编译模块:
把上面的glob.c和Makefile两个文件放在同一个文件夹下,我这里的文件夹是“glob阻塞操作实验”,然后进入文件夹,打开终端,登录root,输入指令make,便开始进行模块的编译了,遇到编译错误,多百度,积累经验。
四,加载模块:
在root权限下,在终端输入指令 insmod glob.ko 加载模块,没有任何错误提示的话,加载就成功了,如果遇到设备正忙的错误提示,请更改你的主设备号,然后重新加载便可。输入指令 cat /proc/devices 可以看到里面多了这么一行 :1400 glob ,这说明我们的模块确实是已经成功加载进了我们的系统内核。
五,编写测试程序测试我们的内核模块:
因为我们需要实现对glob虚拟设备的阻塞访问,所以我们这里编写两个测试程序:glob_read_test.c和glob_write_test.c 。我们最终要实现的效果是,当这两个程序同时访问我们的glob虚拟设备时,只有当成功执行glob_write_test这个进程,往设备里写入了数据,另一个进程glob_read_test,才能读取glob虚拟设备里面的数据。因为最开始glob设备里是不存在数据的,不满足执行进程glob_read_test的条件,这个进程被挂起了直到设备glob里面存在数据,glob_read_test这个进程才会被唤醒,否则一直会被挂起。
glob_read_test.c的源代码如下:
//#include <sys/types.h> //#include <sys/stat.h> #include <stdio.h> #include <fcntl.h> int main(void) { int fd, num; fd = open("/dev/glob", O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { read(fd, &num, sizeof(int)); //程序将阻塞在此语句,除非有针对 glob 的输入 printf("The glob is %d\n", num); //如果输入是 0,则退出 if (num == 0) { close(fd); break; } } } else { printf("device open failure\n"); } return 0; }
glob_write_test.c的源码如下:
//#include <sys/types.h> //#include <sys/stat.h> #include <stdio.h> #include <fcntl.h> int main(void) { int fd, num; fd = open("/dev/glob", O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { printf("Please input the glob:\n"); scanf("%d", &num); write(fd, &num, sizeof(int)); //如果输入 0,退出 if (num == 0) { close(fd); break; } } } else { printf("device open failure\n"); } return 0; }
六,编译我们前面的测试程序:
在终端输入: gcc -o glob_read_test.o glob_read_test.c
gcc -o glob_write_test.o glob_write_test.c
编译我们的测试程序。
七,创建设备文件:
在终端输入命令:mknod /dev/glob c 1400 0 ,前面第一个参数是创建的目录;第二个参数是指设备的类型,c代表字符设备;第三个参数是主设备号;第四个参数是次设备号。
八,测试设备glob:
打开一个终端 输入 ./glob_write_test.o
打开另一个终端 输入 ./glob_read_test.o
这时候你会发现,只有当执行线程glob_write_test,往glob写入数据后,另一个线程glob_read_test才能读取数据,实现了glob_read_test对字符设备glob的阻塞访问,这和我们第五步中的设想完全一致! 效果如下图: