深入浅出Linux设备驱动之阻塞与非阻塞
刺猬@http://blog.youkuaiyun.com/littlehedgehog
阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满足。非阻塞操作的进程在不能进行设备操作时,并不挂起。比如socket里面的read,如果客户端不发送数据,那么服务端就要一直等下去。
在Linux驱动程序中,我们可以使用等待队列(wait queue)来实现阻塞操作。wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。等待队列可以用来同步对系统资源的访问,上节中所讲述Linux信号量在内核中也是由等待队列来实现的。
wait的API:
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
等待condition变为true,否则一直睡眠
condition可以在任何地方被改变,但改变时须wake_up 等待队列wq里面的process。
唤醒函数:
void wake_up(wait_queue_head_t *queue); //唤醒所有
void wake_up_interruptible(wait_queue_head_t *queue); //唤醒interruptible
下面我们重新定义设备"globalvar",它可以被多个进程打开,但是每次只有当一个进程写入了一个数据之后本进程或其它进程才可以读取该数据,否则一直阻塞。
- #ifndef __KERNEL__
- #define __KERNEL__
- #endif
- #ifndef MODULE
- #define MODULE
- #endif
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/fs.h>
- #include <linux/wait.h>
- #include <linux/sched.h>
- #include <asm/uaccess.h>
- #include <asm/semaphore.h>
- MODULE_LICENSE("GPL");
- #define MAJOR_NUM 250 //主设备号
- 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*);
- static int globalvar_open(struct inode *,struct file *);
- static int globalvar_release(struct inode *,struct file *);
- //初始化字符设备驱动的file_operations结构体
- struct file_operations globalvar_fops =
- {
- read:globalvar_read,
- write:globalvar_write,
- open:globalvar_open,
- release:globalvar_release,
- };
- static int global_var = 0; //"globalvar"设备的全局变量
- static int global_count=0;
- static wait_queue_head_t wait_q; //定义等待队列头部
- static int flag=0; //这个是读写标志
- static struct semaphore sem;
- static spinlock_t spin=SPIN_LOCK_UNLOCKED;
- static int __init globalvar_init(void)
- {
- int ret;
- //注册设备驱动
- ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
- if (ret)
- printk("globalvar register failure!/n");
- else
- {
- printk("globalvar register success!/n");
- init_MUTEX(&sem);
- init_waitqueue_head(&wait_q);
- }
- return ret;
- }
- static void __exit globalvar_exit(void)
- {
- printk("globalvar unregister!/n");
- unregister_chrdev(MAJOR_NUM, "globalvar");
- }
- static int globalvar_open(struct inode *inode,struct file *filep)
- {
- spin_lock(&spin); //这里就限制了只能一个进程
- if(global_count)
- {
- spin_unlock(&spin);
- return -EBUSY;
- }
- global_count++;
- spin_unlock(&spin);
- return 0;
- }
- static int globalvar_release(struct inode *inode,struct file *filep)
- {
- global_count--;
- return 0;
- }
- static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
- {
- printk("0/n"); //这是我作调试用的
- if(wait_event_interruptible(wait_q,flag!=0))//注意 这里等待的条件flag==0 只要flag!=0 执行write进程会马上唤醒这个进程
- {
- return -ERESTARTSYS;
- }
- if(down_interruptible(&sem))
- return -ERESTARTSYS;
- printk("1/n");
- if (copy_to_user(buf, &global_var, sizeof(int)))
- {
- up(&sem);
- return -EFAULT;
- }
- printk("2/n");
- flag=0;
- up(&sem);
- printk("3/n");
- 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;
- //将用户空间的数据复制到内核空间的global_var
- if (copy_from_user(&global_var, buf, sizeof(int)))
- {
- up(&sem);
- return -EFAULT;
- }
- up(&sem);
- flag=1;
- wake_up_interruptible(&wait_q);
- return sizeof(int);
- }
- module_init(globalvar_init);
- module_exit(globalvar_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("neo");
下面是测试程序,同样,我自己改了下。最好自己写,这样加深了解 [刺猬]
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <unistd.h>
- //#define DEBUG
- int main()
- {
- int fd,readnum=13,writenum=22;
- int pid=1;
- if((fd=open("/dev/test",O_RDWR, S_IRUSR | S_IWUSR))==-1)
- {
- perror("damn! failed");
- return -1;
- }
- #ifndef DEBUG
- if((pid=fork())==-1)
- return -1;
- #endif
- sleep(3);
- if(pid)
- {
- printf("I am a parent! /nMy work is to read the globalvar device/n");
- sleep(3);
- printf("now read ... until one of my children write into it/n");
- read(fd,&readnum,sizeof(int));
- printf("at last ! someone's written %d my job's done/n",readnum);
- }
- else
- {
- printf("I am a child/nMy work is to write/n");
- sleep(2);
- printf("now write!/n");
- write(fd,&writenum,sizeof(int));
- printf("done! I write %d",writenum);
- sleep(8);
- }
- return 0;
- }