当欲执行的条件不满足的时候,可以休眠进程等待,也可以返回不可操作标志。休眠等待就是阻塞IO模式,直接返回就是非阻塞IO模式。
在驱动中休眠线程可以使用互斥锁(mutex)、信号量(smaphare)、完成量(complition)。这些都是基于等待队列实现的,很多时候需要直接使用等待队列进行更加复杂的调度操作。
最基本的等待队列的操作是:
//定义等待队列
wait_queue_head_t wait_task;
//初始化等待队列
init_waitqueue_head(&wait_task);
//判断并休眠
wait_event(queue, condition) //非中断休眠。
wait_event_interruptible(queue, condition) //可以被信号中断,返回非零表示休眠被某个信号中断。
wait_event_timeout(queue, condition, timeout) //限时版本,超时返回0。
wait_event_interruptible_timeout(queue, condition, timeout) //限时版本,超时返回0。
//唤醒函数
void wake_up(wait_queue_head_t *queue); //唤醒等待在给定queue上的所有进程。
void wake_up_interruptible(wait_queue_head_t *queue); //只会唤醒那些执行可中断休眠的进程。
用等待队列简单实现一下字符设备的FIFO,使用wait_event_interruptible
可以被ctrl+c
打断
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
static int data = 1;
module_param(data, int, 0644); //声明模块参数
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
unsigned char has_data_flag;
static wait_queue_head_t wait_task;
static int open(struct inode * node, struct file * fl){
return 0;
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
return 0;
}
static ssize_t read(struct file * fl, char __user * buf, size_t len, loff_t * offset){
int ret = 0,copy_len,data_len;
wait_event_interruptible(wait_task,has_data_flag != 0);
has_data_flag = 0;
data_len = strlen(char_data)+1;
if(fl->f_pos + len > data_len)
copy_len = data_len - fl->f_pos; //超过长度,复制剩余部分
else
copy_len = len; //没超过
ret = copy_to_user(buf,char_data+fl->f_pos,copy_len);
ret = copy_len - ret;
*offset += ret; //移动文件指针
return ret;
}
static ssize_t write(struct file * fl, const char __user * buf, size_t len, loff_t * offset){
int ret = 0,copy_len,data_len = buffer_size;
if(fl->f_pos + len > data_len)
copy_len = data_len - fl->f_pos; //超过长度,复制剩余部分
else
copy_len = len; //没超过
ret = copy_from_user(char_data+fl->f_pos,buf,copy_len);
ret = copy_len - ret;
*offset += ret; //移动文件指针
has_data_flag = 1;
wake_up_interruptible(&wait_task);
return ret;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl
};
static int __init char_init(void){
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
has_data_flag = 0;
init_waitqueue_head(&wait_task);
printk("char init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit char_exit(void){
kfree(char_data);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("char exit\n");
}
module_init(char_init);
module_exit(char_exit);
大多是时候等待队列的使用并没有这么简单,配合上锁机制实现更加精细化的控制。精细化使用一般就不用wait_event
函数了,而是自己实现wait_event
,基本流程是:
schedule
函数主动放弃CPU,让系统重新调度。ctrl+c
,可以就此返回看一下wait_event
源码:
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);
if (condition)
break;
schedule();
}
finish_wait(&wq, &__wait);
} while (0)
大致就是这个过程,例子不演示了。
非阻塞IO有两种实现,一种是在用户空间打开设备文件后设置是否需要阻塞访问。驱动中直接判断文件描述符的f_flags就可以了。设置了非阻塞访问就直接返回。
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
static int data = 1;
module_param(data, int, 0644); //声明模块参数
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
unsigned char has_data_flag;
static wait_queue_head_t wait_task;
static int open(struct inode * node, struct file * fl){
return 0;
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
return 0;
}
static ssize_t read(struct file * fl, char __user * buf, size_t len, loff_t * offset){
int ret = 0,copy_len,data_len;
if (fl->f_flags & O_NONBLOCK && has_data_flag == 0)
return -EAGAIN; //直接返回
wait_event_interruptible(wait_task,has_data_flag != 0);
has_data_flag = 0;
data_len = strlen(char_data)+1;
if(fl->f_pos + len > data_len)
copy_len = data_len - fl->f_pos; //超过长度,复制剩余部分
else
copy_len = len; //没超过
ret = copy_to_user(buf,char_data+fl->f_pos,copy_len);
ret = copy_len - ret;
*offset += ret; //移动文件指针
return ret;
}
static ssize_t write(struct file * fl, const char __user * buf, size_t len, loff_t * offset){
int ret = 0,copy_len,data_len = buffer_size;
if(fl->f_pos + len > data_len)
copy_len = data_len - fl->f_pos; //超过长度,复制剩余部分
else
copy_len = len; //没超过
ret = copy_from_user(char_data+fl->f_pos,buf,copy_len);
ret = copy_len - ret;
*offset += ret; //移动文件指针
has_data_flag = 1;
wake_up_interruptible(&wait_task);
return ret;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.unlocked_ioctl = ioctl
};
static int __init char_init(void){
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
has_data_flag = 0;
init_waitqueue_head(&wait_task);
printk("char init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit char_exit(void){
kfree(char_data);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("char exit\n");
}
module_init(char_init);
module_exit(char_exit);
另外就是使用系统调用poll/select/epoll。这些系统调用可以一次性监控多个文件描述符,实现的基本原理是将当前进程加入到所有驱动的阻塞队列然后再阻塞,任何一个文件唤醒进程都会唤醒到自己。
这些系统调用监控文件描述符之后,会调用到驱动中的poll函数,实现对文件的监控。驱动中的poll函数主要完成以下几件事:
poll.h
中/* These are specified by iBCS2 */
#define POLLIN 0x0001
#define POLLPRI 0x0002
#define POLLOUT 0x0004
#define POLLERR 0x0008
#define POLLHUP 0x0010
#define POLLNVAL 0x0020
/* The rest seem to be more-or-less nonstandard. Check them! */
#define POLLRDNORM 0x0040
#define POLLRDBAND 0x0080
#ifndef POLLWRNORM
#define POLLWRNORM 0x0100
#endif
#ifndef POLLWRBAND
#define POLLWRBAND 0x0200
#endif
#ifndef POLLMSG
#define POLLMSG 0x0400
#endif
#ifndef POLLREMOVE
#define POLLREMOVE 0x1000
#endif
#ifndef POLLRDHUP
#define POLLRDHUP 0x2000
#endif
POLLIN
如果设备无阻塞的读,就返回该值。
POLLRDNORM
通常的数据已经准备好,可以读了,就返回该值。通常的做法是会返回(POLLLIN|POLLRDNORA)。
POLLRDBAND
如果可以从设备读出带外数据,就返回该值,它只可在linux内核的某些网络代码中使用,通常不用在设备驱动程序中
POLLPRI
如果可以无阻塞的读取高优先级(带外)数据,就返回该值,返回该值会导致select报告文件发生异常,以为select八带外数据当作异常处理。
POLLHUP
当读设备的进程到达文件尾时,驱动程序必须返回该值,依照select的功能描述,调用select的进程被告知进程时可读的。
POLLERR
如果设备发生错误,就返回该值。
POLLOUT
如果设备可以无阻塞地些,就返回该值。
POLLWRNORM
设备已经准备好,可以写了,就返回该值。通常地做法是(POLLOUT|POLLNORM)。
POLLWRBAND
于POLLRDBAND类似。
驱动中poll定义为unsigned int (*poll) (struct file *filep, struct poll_table_struct *wait);
跟read,write,open一样添加到file_operations就可以了。简单例子。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
static int data = 1;
module_param(data, int, 0644); //声明模块参数
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
unsigned char has_data_flag;
static wait_queue_head_t wait_task;
static int open(struct inode * node, struct file * fl){
return 0;
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
return 0;
}
static ssize_t read(struct file * fl, char __user * buf, size_t len, loff_t * offset){
int ret = 0,copy_len,data_len;
wait_event_interruptible(wait_task,has_data_flag != 0);
has_data_flag = 0;
data_len = strlen(char_data)+1;
if(fl->f_pos + len > data_len)
copy_len = data_len - fl->f_pos; //超过长度,复制剩余部分
else
copy_len = len; //没超过
ret = copy_to_user(buf,char_data+fl->f_pos,copy_len);
ret = copy_len - ret;
*offset += ret; //移动文件指针
return ret;
}
static ssize_t write(struct file * fl, const char __user * buf, size_t len, loff_t * offset){
int ret = 0,copy_len,data_len = buffer_size;
if(fl->f_pos + len > data_len)
copy_len = data_len - fl->f_pos; //超过长度,复制剩余部分
else
copy_len = len; //没超过
ret = copy_from_user(char_data+fl->f_pos,buf,copy_len);
ret = copy_len - ret;
*offset += ret; //移动文件指针
has_data_flag = 1;
wake_up_interruptible(&wait_task);
return ret;
}
unsigned int hello_poll(struct file *fl, struct poll_table_struct *wait)
{
unsigned int mask = POLLOUT | POLLWRNORM; //一直可写
poll_wait(fl, &wait_task, wait); //当前进程加入被poll阻塞的等待队列
if (has_data_flag) {
mask |= POLLIN | POLLRDNORM; //可读
}
return mask;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.poll = hello_poll,
.unlocked_ioctl = ioctl
};
static int __init char_init(void){
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
has_data_flag = 0;
init_waitqueue_head(&wait_task);
printk("char init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit char_exit(void){
kfree(char_data);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("char exit\n");
}
module_init(char_init);
module_exit(char_exit);
操作的application:
int main(int argc, char const *argv[])
{
struct pollfd fds = { 0 };
fds.fd = open(DEVICE_NAME, O_RDWR);
if (fds.fd < 0) {
printf("open failed! error:%s\n", strerror(errno));
return 0;
}
fds.events = POLLIN;
poll(&fds, 1, 5000); /* 五秒后自动返回 */
printf("fds.revent: %d\n", fds.revents);
close(fds.fd);
return 0;
}