您的位置:首页 > 运维架构 > Linux

Linux驱动程序之阻塞和非阻塞IO

2017-06-08 08:51 176 查看
所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回。也就是说在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足

所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。可是使用Select就可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常

以串口的应用程序为例:

阻塞地都取串口一个字符

char buf;
fd = open("/dev/ttys",O_RDWR);
.. ..
res = read(fd,&buf,1); //当串口上有输入时才返回
if(res == 1)
{
printf("%c\n",buf);
}


非阻塞地都取串口一个字符

char buf;
fd = open("/dev/ttys",O_RDWR | O_NONBLOCK);
.. ..
while( read(fd,&buf,1) !=1); //当串口上无输入也返回,所
continue;                                                //以要循环尝试读取串口
printf("%c\n",buf);


也就是非阻塞一定会返回一个数值 就算你read没有读取到数据 也会返回一个-EAGAIN 所以非阻塞时要循环读取串口 而阻塞并不会 因为阻塞一旦返回数值 说明读取串口数据成功 没读取到不返回

驱动程序往往要提供这样的能力:当应用程序进行read(),write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的xxx_read(),xxx_write()等操作将进程阻塞直到资源可以获取,此后应用程序的read(),write()等调用才返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read(),xxx_write()等操作立即返回,应用程序收到-EAGAIN返回值

现在我们有了阻塞的方式读取,那么阻塞的进程因为没有获得资源会进入休眠状态,现在就要聊聊有关唤醒的事了。

在Linux设备驱动中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒.等待队列能够用于实现内核中的异步事件通知机制。

Linux提供了有关等待队列的操作:

1)wait_queue_head_t my_queue; //定义等待队列头

2) init_waitqueue_head(&my_queue); //初始化队列头

如果觉得上边两步来的麻烦,可以直接使用

DECLARE_WAIT_QUEUE_HEAD(name) //定义并初始化

3) DECLARE_WAITQUEUE(name,tsk); //定义等待队列元素

4) void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

分别用于将等待队列wait添加(add)或者移除(remove)到等待队列头q指向的等待队列链表中 。

5) wait_event(queue, conditon);

wait_event_interruptible(queue, condition); //可以被信号打断

wait_event_timeout(queue, condition, timeout);

wait_event_interruptible_timeout(queue, condition, timeout); //不能被信号打断

queue:作为等待队列头的等待队列被唤醒

conditon:必须满足,否则阻塞

timeout和conditon相比,有更高优先级

6) void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

上述操作会唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。

7) sleep_on(wait_queue_head_t *q);

interruptible_sleep_on(wait_queue_head_t *q);

sleep_on作用是把目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把他附属到等待队列头q,直到资源可用,q引导的等待队列被唤醒。interruptible_sleep_on作用是一样的, 只不过它把进程状态置为TASK_INTERRUPTIBLE.

这两个函数的流程是首先,定义并初始化等待队列,把进程的状态置成TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,并将对待队列添加到等待队列头。

然后通过schedule(放弃CPU,调度其他进程执行。最后,当进程被其他地方唤醒,将等待队列移除等待队列头。

在Linux内核中,使用set_current_state()和__add_wait_queue()函数来实现目前进程状态的改变,直接使用current->state = TASK_UNINTERRUPTIBLE

类似的语句也是可以的。

因此我们有时也可能在许多驱动中看到,它并不调用sleep_on或interruptible_sleep_on(),而是亲自进行进程的状态改变和切换。

话不多说上代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

/*for spinlock and semaphore*/
#include <asm/spinlock.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>

/*for task management*/
#include <linux/wait.h>
#include <linux/sched.h>

/*
* char device driver with blocked testing
*/

MODULE_LICENSE("GPL");

static int MAJOR_NUM=0;
static struct semaphore sem;
static int global_var = 0;
static int global_var_count = 0;
static spinlock_t spin;
/*waiting queue for kernel*/
static wait_queue_head_t wqueue;
static int flag = 0;

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*node, struct file* fp);
static int globalvar_release(struct inode*node, struct file* fp);

/*init the file_operation structure*/
static struct file_operations globalvar_fpos={
.read = globalvar_read,
.write = globalvar_write,
.open = globalvar_open,
.release = globalvar_release,
};

static int __init globalvar_init(void)
{
int ret;

printk("register globalvar:[blocked testing]");
/*register device drivre*/
ret = register_chrdev(MAJOR_NUM,"globalvar",&globalvar_fpos);
if(ret < 0){
printk("globalvar reg failed!\n");
}else{
spin_lock_init(&spin);
}

if(MAJOR_NUM == 0){
MAJOR_NUM = ret;
}

sema_init(&sem,1);
init_waitqueue_head(&wqueue);
return ret;
}

static void __exit globalvar_exit()
{
unregister_chrdev(MAJOR_NUM,"globalvar");
}

static ssize_t globalvar_read(struct file* fp, char* buf, size_t len, loff_t* off)
{
/*wait until condition become true*/
if( wait_event_interruptible(wqueue,flag!=0) ){
return -ERESTARTSYS;
}

/*get semaphore*/
if(down_interruptible(&sem)){
return -ERESTARTSYS;
}

/*copy from kernel to user space*/
if(copy_to_user(buf,&global_var,sizeof(int)) != 0){
/*release semaphore*/
up(&sem);
return -EFAULT;
}
/*data unaccessible flag*/
flag = 0;

/*release semaphore*/
up(&sem);
return sizeof(int);
}

static ssize_t globalvar_write(struct file* fs,const char* buf, size_t len, loff_t* off)
{
/*get semaphore*/
if(down_interruptible(&sem)){
return -ERESTARTSYS;
}

printk("down_interruptible ok!\n");
if(copy_from_user(&global_var,buf,sizeof(int) != 0)){
/*release semaphore*/
up(&sem);
return -EFAULT;
}

/*release semaphore*/
up(&sem);

/*data ready*/
flag = 1;
/*wake up the waiting task*/
wake_up_interruptible(&wqueue);
return sizeof(int);
}

/*
* open device with checking busy.
* if busy,count++;else return 0
*/
static int globalvar_open(struct inode*node, struct file* fp)
{
/*get spinlock*/
//spin_lock(&spin);

/*reach criticle section*/
//if(global_var_count){
//spin_unlock(&spin);
//printk("[debug]:globalvar open fialed!\n");
//return -EBUSY;
//}

/*release spinlock*/
global_var_count++;

//spin_unlock(&spin);
return 0;
}

static int globalvar_release(struct inode*node, struct file* fp)
{
//spin_lock(&spin);
global_var_count--;
//spin_unlock(&spin);
return 0;
}

/*module setting*/
module_init(globalvar_init);
module_exit(globalvar_exit);


每写一个数据 就会唤醒队列读取数据

下面是测试用的应用程序,包括了读写两个分支。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

/*
* this is a test for char device "globalvar"
*/

int main(int argc, char** args) {
int fd, num;

if (argc >= 2) {
if (strcmp(args[1], "0") == 0) {
printf("mode read!\n");
/*opemn device*/
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != -1) {
while (1) {
read(fd, &num, sizeof(int));
printf("globalvar=%d\n",num);
if (num == 0) {
close(fd);
break;
}
}//while
} else {
printf("error:device open error!\n");
}
} else if (strcmp(args[1], "1") == 0) {
printf("mode write!\n");

/*opemn device*/
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != -1) {
while (1) {
/*writing test*/
printf("print number to write: ");
scanf("%d", &num);
write(fd, &num, sizeof(int));
if (num == 0) {
close(fd);
break;
}
}
} else {
printf("error:device open error!\n");
}
}
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux驱动