驱动程序学习(四)并发控制(3):阻塞与非阻塞

来源:互联网 发布:二分法查找c语言 编辑:程序博客网 时间:2024/06/07 02:51

阻塞是指在执行设备操作的同时,若不能获得资源,则进程挂起直到满足可操作的条件在进行操作。

非阻塞操作是在进程不能执行设备操作时并不挂起。被挂起的程序进入休眠状态,一直等待条件满足

后才被执行,非阻塞模式则不会进入休眠状态,具体处理方式根据编程者的编程意图,所以一般情况

下,非阻塞的逻辑要比阻塞的逻辑复杂些,但是效率较高。

在linux驱动程序中,我们长使用等待队列来实现阻塞操作。wait queue

接着我们来说说,这次实验中要用到的两个函数

wait_event_interruptible();

wait_event_interruptible()。该函数修改task的状态为TASK_INTERRUPTIBLE,意味着该进程将不会继续运行直到被唤醒,然后被添加到等待队列中。    在wait_event_interruptible()中首先判断condition是不是已经满足,如果是则直接返回0,否则调用__wait_event_interruptible(),并用__ret来存放返回值---------------------------------------------------------------#define wait_event_interruptible(wq, condition)          \({                                                       \    int __ret = 0;                                       \    if (!(condition))                                    \        __wait_event_interruptible(wq, condition, __ret);\    __ret;                                               \})

wake_up_interruptible();


wake_up_interruptible负责唤醒状态为TASK_INTERRUPTIBLE的进程。

#define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

void fastcall __wake_up(wait_queue_head_t     *q, 
                        unsigned int         mode,
                        int                 nr_exclusive, 
                        void                 *key)
{
    unsigned long flags;
    spin_lock_irqsave(&q->lock, flags);
    __wake_up_common(q, mode, nr_exclusive, 0, key);
    spin_unlock_irqrestore(&q->lock, flags);
两个函数成对使用,他们之间的直接联系就是相同的任务队列,接着我们在源代码中说明一下。

chardev_block.c

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




#include<linux/sched.h>
/*解决TASK_INTERRUPTIBLE’未声明(在此函数内第一次使用)错误,该文件是最重要的几个文件之一,该文件包含驱动程序使用的大部分内核api的定义,包括睡眠函数以及各种变量。*/


MODULE_LICENSE("GPL");


#define MAJOR_NUM 111


static ssize_t chardev_read(struct file*, char *,size_t,loff_t *);
static ssize_t chardev_write(struct file*,const char *,size_t,loff_t *);


struct file_operations chardev_fops=
{
.owner = THIS_MODULE,
.read  = chardev_read,
.write = chardev_write,
};


static int chardev_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq;
static int flag = 0;


static int __init chardev_init(void)
{
int ret;
ret = register_chrdev(MAJOR_NUM,"chardev",&chardev_fops);
if(ret)
{
printk("chardev register failure");
}
else
{
printk("chardev register failure");
init_MUTEX(&sem);
init_waitqueue_head(&outq);
}
return ret;
}


static void __exit chardev_exit(void)
{

unregister_chrdev(MAJOR_NUM,"chardev");

}


static ssize_t chardev_read(struct file *filp,char *buf,size_t len,loff_t *off)
{
if(wait_event_interruptible(outq,flag!=0))
{
return -ERESTARTSYS;
}

if(down_interruptible(&sem))
{
return -ERESTARTSYS;
}
flag =0;
if(copy_to_user(buf,&chardev_var,sizeof(int)))
{
up(&sem);
return -EFAULT;
}
up(&sem);
return sizeof(int);
}


static ssize_t chardev_write(struct file *filp,const char *buf,size_t len,loff_t *off)
{
if(down_interruptible(&sem))
{
return -ERESTARTSYS;
}
if(copy_from_user(&chardev_var,buf,sizeof(int)))
{
up(&sem);
return -EFAULT;
}
up(&sem);
flag = 1;

wake_up_interruptible(&outq);
return sizeof(int);
}


module_init(chardev_init);
module_exit(chardev_exit);

在这个文件中我们现在前面声明了一个任务队列

static wait_queue_head_t outq;

接着我们在read函数中进行调用

if(wait_event_interruptible(outq,flag!=0))
{
return -ERESTARTSYS;
}

修改task的状态为TASK_INTERRUPTIBLE,该进程将不会继续运行直到被唤醒,然后被添加到等待队列中。
最后我们在write函数中将该进程唤醒
wake_up_interruptible(&outq);
两个测试的应用程序代码如下

charedevtest_read.c

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


main()
{
int fd,num;
fd = open("/dev/chardev",O_RDWR,S_IRUSR | S_IWUSR);//后面两个参数的意思是只有根用户才允许读取和写入,S_IRUGO 和S_IWUGO 是任何用户都可以读取和写入。
if(fd != -1)
{
while(1)
{
read(fd,&num,sizeof(int));
printf("The chardev is %d]n",num);
goto out1;
if(num==0)
{
close(fd);
break;
}
}
}
else 
{
printf("device open failure\n");
}
out1:
return 0;
}

charedevtest_write.c

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


main()
{
int fd,num;

fd = open("/dev/chardev",O_RDWR,S_IRUSR | S_IWUSR);
if(fd != -1 )
{
while(1)
{
printf("Please input the chardev:\n");
scanf("%d",&num);
write(fd,&num,sizeof(int));
goto out1;
if(num==0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure\n");
}
out1:
return 0;
}

首先创建设备节点

mknod /dev/chardev c 111 0

然后加载驱动模块

接着打开两个终端,在终端中执行上面两个应用程序。

先执行读取程序

会发现程序进入休眠状态。

接着执行写入程序,在字符写入时,同时读取函数被唤醒。

这里有两个需要注意的点

static int chardev_var = 0;

我们定义的驱动程序中存储数据的变量是 int型,所以我们在输入数据的时候务必输入数字,输入字母的话就会发生错误,返回一个很大的随机数据。

第二我们在读应用程序的时候会发现这样两句代码

goto out1;

out1:
return 0;

这是用来跳出循环的在执行程序一次后。

原本做测试的时候是没有这两句代码的后来发现他就如了死循环,不断的打印输出信息,

看来编写书籍的同学也不是很严谨,哈!