4.Linux设备驱动编程之阻塞与非阻塞
来源:百度文库 编辑:神马文学网 时间:2024/04/28 00:34:39
4.Linux设备驱动编程之阻塞与非阻塞
阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满足。
在Linux驱动程序中,我们可以使用等待队列(wait queue)来实现阻塞操作。wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。等待队列可以用来同步对系统资源的访问,上节中所讲述Linux信号量在内核中也是由等待队列来实现的。
下面我们重新定义设备"globalvar",它可以被多个进程打开,但是每次只有当一个进程写入了一个数据之后本进程或其它进程才可以读取该数据,否则一直阻塞。
#include
#include
#include
#include
#include
#include
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 =
{
write: globalvar_write,
};
static int global_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq; //等待队列头在/include/linux/wait.h中
static int flag = 0; struct_wait_queue_head{
spinlock_t lock;
static int __init globalvar_init(void) struct list_head task_list;
{ }
int ret; typedef struct_wait_queue_head wait_queue_head_t;
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); //初始化等待对列头
} void init_waitqueue_head (wait_queue_head_t *q){
return ret; spin_lock_init (&q->lock);
} INIT_LIST_HEAD (&q->task_list);
}
static void __exit globalvar_exit(void) static inline void INIT_LIST_HEAD (struct list_head *list)
{ {
int ret; list->next =list;
ret = unregister_chrdev(MAJOR_NUM, "globalvar"); list->prev=list
if (ret) }将整个队列设置为“未上锁的状态”,
{ 并将链表指针prev和next指向它自身。
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)) //wait_event_interruptible()函数调用时,在等待过程
{ 中当前进程会被设置为TASK_UNINTERRUPTIBLE
return - ERESTARTSYS; 状态,在每次唤醒的时候,首先检查condition是否为
} 真,如果为真则返回,否则检查如果进程是被信号唤
醒,会返回- ERESTARTSYS;错误码,如果condition
if (down_interruptible(&sem)) 为真,则返回0
{ 源码#define _wait_event_interruptible(outq,codition,ret)
return - ERESTARTSYS; do{
} DEFINE_WAIT(_wait);
for(;;){
flag = 0; prepare_to_wait(&outq,&_wait,TASK_INTERRUPTIBLE);
if (copy_to_user(buf, &global_var, sizeof(int))) if(condition)
{ break;
up(&sem); if(!signal_pending(current)){ //检查当前是否
return - EFAULT; schedule(); 有信号处理,返回非0表示
} continue; 有信号处理,返回
up(&sem); } - ERESTARTSYS表示信号
return sizeof(int); break;} 函数处理完毕后重新执行信
} finish_wait(&outq,&_wait); 号函数前的某个系
}while(0) 统调用。
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; //flag就是为了判断condition用的
//通知数据可获得
wake_up_interruptible(&outq); //只能唤醒TASK_INTERRUPTIBLE状态的
return sizeof(int); 进程,与wait_event_interruptible()成对使用
}
module_init(globalvar_init);
module_exit(globalvar_exit);
读的程序为:
#include
#include
#include
#include
main()
{
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
#include
#include
#include
main()
{
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");
}
}
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);
}
+还有一个与设备阻塞与非阻塞访问息息相关的论题,即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;
}
我们编写一个用户态应用程序来测试改写后的驱动。程序中要用到BSD Unix中引入的select函数,其原型为:
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 */
};
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
#include
#include
#include
#include
#include
#include
main()
{
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");
}
}