当进程以阻塞的方式打开设备文件时(默认方式),如果资源不可用,那么进程阻塞,也可以说进程休眠,其的具体过程就是主动将自己的状态设置为TASK_UNINTERRUPTIBLE或者TASK_INTERRUNTIBLE,然后将自己加入一个等待队列中,调用schedule主动放弃CPU,操作系统将之从运行队列上移除,并调度就绪队列上的第一个进程运行。
对比非阻塞I/O,当资源不可用时,它不占用CPU时间,但它也有缺点,就是在阻塞(休眠)期间,再也不能做其他事情了。
既然有休眠,就应该有对应的唤醒操作,否则进程就会一直休眠下去。驱动程序应该在资源可用时进行唤醒操作,比如我们在《字符设备驱动(5)-非阻塞I/O》里面的例子,若读数据时内核缓存里面没有数据,则阻塞等待,直到有进程或者线程往内核缓存写数据,唤醒设备,但在真实设备中,可能收到了数据,产生中断,然后在中断处理程序中读出数据,唤醒设备。另外,我们也可以指定进程的最长休眠时间,超时后进程自动苏醒,判断资源是否可用,若可用就往后进行操作,若不可用,就继续休眠。
最简单的驱动程序实现如下:
static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
//判断设备缓存是否为空
while (0 == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
//如果是阻塞操作,休眠1s,再判断
sleep(1);
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
//读出后,把对应的设备数据清空,保留未读出数据
memcpy(kbuf, kbuf + len, MAX_LEN - len);
memset(kbuf + MAX_LEN - len, 0, len);
//计算设备缓存长度
if (kbuf_len > len)
kbuf_len -= len;
else
kbuf_len = 0;
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
//判断是否会写超过
if (MAX_LEN < len + kbuf_len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
while (MAX_LEN == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
//如果是阻塞操作,休眠1s,再判断
sleep(1)
}
ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
kbuf_len += len;
return len;
}
不过linux内核中,提供了一个等待队列,等待队列通过一个等待队列头进行管理,其数据类型是wait_queue_head_t,队列节点为wait_queue_t,定义在<linux/wait.h>中。我们可以通过静态方法定义并初始化队列:
DECLARE_WAIT_QUEUE_HEAD(name)
或使用动态方法定义并初始化队列:
wait_queue_head_t name;
init_wait_queue_head(&name);
相关宏如下:
wait_event(wq_head, condition)
wait_event_timeout(wq_head, condition, timeout)
wake_up(x)
wait_event_interruptible(wq_head, condition)
wait_event_interruptible_timeout(wq_head, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wake_up_interruptible(x)
wait_event是在条件condition不成立情况下将当前进行放入到等待队列并休眠的基本操作。加上time_out表示有超时时间限制,interruptible表示进程在休眠时可以通过信号来唤醒,exclusive表示进程有排他性,在默认情况下,唤醒操作将唤醒等待队列中的所有进程,但是如果一个进程以排他的方式休眠,那么唤醒操作在唤醒这个进程后,不会继续唤醒其他进程。
等待宏如果不带timeout,返回0表示被成功唤醒,返回-ERESTARTSYS表示被信号唤醒;如果带timeout,返回0表示超时,返回大于0的值表示被成功唤醒,离超时还剩余的时间。对唤醒宏来说,有对应关系。
上面的例子改成:
static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
DECLARE_WAIT_QUEUE_HEAD(my_read_queue);
DECLARE_WAIT_QUEUE_HEAD(my_write_queue);
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
//判断设备缓存是否为空
if (0 == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
//读出后,把对应的设备数据清空,保留未读出数据
memcpy(kbuf, kbuf + len, MAX_LEN - len);
memset(kbuf + MAX_LEN - len, 0, len);
//计算设备缓存长度
if (kbuf_len > len)
kbuf_len -= len;
else
kbuf_len = 0;
//当缓存长度不满时,唤醒写队列
if (MAX_LEN != kbuf_len)
wake_up_interruptible(&my_write_queue);
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
//判断是否会写超过
if (MAX_LEN < len + kbuf_len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
if (MAX_LEN == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
kbuf_len += len;
//当缓存长度不为空时,唤醒读队列
if (0 != kbuf_len)
wake_up_interruptible(&my_read_queue);
return len;
}
注意:
1、等待函数的第一个参数是非指针变量,wake_up的参数是指针变量。
2、调用唤醒操作时,需要在condition变化后再调用,不要在之前调用,否则会错过唤醒机会,比如上面的例子中,写操作调用wake_up_interruptible(&my_read_queue);要在kbuf_len+=len语句之后再调用,不能在其之前调用。
用户态程序
app.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#define FILE_NAME "/dev/mydev"
#define MAX_LEN 64
int fd;
void *func(void *arg)
{
char buf[MAX_LEN] = {0};
int rlen;
rlen = read(fd, buf, MAX_LEN - 1);
if (rlen < 0)
{
perror("Read");
return NULL;
}
printf("Read buf %s len %d success.\n", buf, rlen);
}
int main(void)
{
int wlen;
char buf[MAX_LEN] = "abcdefg";
pthread_t td;
//注意以非阻塞方式打开文件
fd = open(FILE_NAME, O_RDWR);
if (0 > fd)
{
printf("Open failed.\n");
return -1;
}
pthread_create(&td, NULL, func, NULL);
sleep(5);
wlen = write(fd, buf, strlen(buf));
if (wlen < 0)
{
perror("Write error");
return wlen;
}
printf("Write buf len %d success.\n", wlen);
while(1)
{
}
return 0;
}
完整驱动程序
cdev.c
//head
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/wait.h>
#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN 64
static int my_open(struct inode *pnode, struct file *pfile)
{
printk("Open cdev.\n");
return 0;
}
static int my_close(struct inode *pnode, struct file *pfile)
{
printk("Close cdev.\n");
return 0;
}
static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
DECLARE_WAIT_QUEUE_HEAD(my_read_queue);
DECLARE_WAIT_QUEUE_HEAD(my_write_queue);
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
//判断设备缓存是否为空
if (0 == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
//读出后,把对应的设备数据清空,保留未读出数据
memcpy(kbuf, kbuf + len, MAX_LEN - len);
memset(kbuf + MAX_LEN - len, 0, len);
//计算设备缓存长度
if (kbuf_len > len)
kbuf_len -= len;
else
kbuf_len = 0;
//当缓存长度不满时,唤醒写队列
if (MAX_LEN != kbuf_len)
wake_up_interruptible(&my_write_queue);
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
//判断是否会写超过
if (MAX_LEN < len + kbuf_len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
if (MAX_LEN == kbuf_len)
{
//如果是非阻塞操作,返回-EAGAIN,用户态轮询
if (pf->f_flags& O_NONBLOCK)
return -EAGAIN;
else
{
//如果是阻塞操作,等待
if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
}
}
ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
kbuf_len += len;
//当缓存长度不为空时,唤醒读队列
if (0 != kbuf_len)
wake_up_interruptible(&my_read_queue);
return len;
}
struct cdev cdevice;
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
};
//加载
static int hello_init(void)
{
dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
int ret = -1;
printk(KERN_ALERT "Hello World.\n");
//up kernel
//1、注册设备号
ret = register_chrdev_region(devno, 1, "hello");
if (0 != ret)
{
printk("Register char device failed.\n");
return ret;
}
//2、初始化字符设备结构体
cdev_init(&cdevice, &cdev_ops);
cdevice.owner = THIS_MODULE;
//3、添加字符设备结构体给内核
ret = cdev_add(&cdevice,devno , 1);
if (0 != ret)
{
//注意释放设备号
unregister_chrdev_region(devno,1);
printk("Unregister char device.\n");
return ret;
}
printk("Register char device success.\n");
//down hardware
return 0;
}
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
printk(KERN_ALERT "Goodbye World.\n");
// down hardware
// up kernel
//1、从内核中删除字符设备结构体
cdev_del(&cdevice);
//2、注销设备号
unregister_chrdev_region(devno, 1);
}
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
//license(必须)
MODULE_LICENSE("GPL");
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");
运行结果
$ sudo mknod /dev/mydev c 100 0
$ sudo insmod cdev.ko
$ sudo ./a.out
Write buf len 7 success.
Read buf abcdefg len 63 success.