一、前言
在第三章中,我们已经讨论了如何实现驱动程序的read和write方法。现在谈论另一种重要问题:如何驱动程序无法立即满足要求,该如何响应?调用程序通常不会关心此类问题,程序员只会简单调用read和write,然后等待必要的工作结束后返回调用,因此,在这种情况下,我们驱动程序应该(默认)阻塞该进程,将其置入休眠状态直至请求可继续。
我么介绍过的函数可以满足许多驱动程序的休眠请求。但是在某些情况下,我们需要Linux的等待队列机制有更深的理解。复杂的锁定以及性能需求会强制驱动程序用底层的函数实现休眠。
二、阻塞IO和高级休眠
1、休眠的简单介绍
当一个进程被置入休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走。休眠中的进程会搁置在一边,等待将来的某个事件发生。
对linux设备驱动程序来讲,让一个进程进入休眠状态很容易,需牢记两个原则:
(1)永远不要在原子上下文中进入休眠。
(2)当我们被唤醒时,我们永远无法知道休眠了多长时间,或者休眠期间都发生了什么事情,因此必须检查以确保我们等待的条件真正为真。
2、简单休眠
linux内核中最简单的休眠方式为wait_event的宏,在实现休眠的同时,它也检查休眠条件。如下:
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
在 Linux 中, 一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在中:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
唤醒休眠:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);//驱动中大部分情况使用这个
3、阻塞和非阻塞操作
显示的非阻塞IO由filp->f_flags的O_NONBLOCK标志决定。这个标志在默认时要被清除,因为等待数据的进程一般是休眠。
只有read、open和write文件操作受非阻塞标志的影响。
4、高级休眠
(1)手动休眠
/* ①创建并初始化一个等待队列。通常由宏定义完成:*/
DEFINE_WAIT(my_wait);
/*其中name 是等待队列入口项的名字. 也可以用下面两个步骤来做:*/
wait_queue_t my_wait;
init_wait(&my_wait);
/*在实现休眠的循环前放置 DEFINE_WAIT 通常更容易实现*/
/* ②添加等待队列入口到队列,并设置进程状态:*/
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
/*其中,queue 和 wait 分别地是等待队列头和进程入口。state 是进程的新状态:TASK_INTERRUPTIBLE(可中断休眠,推荐)或TASK_UNINTERRUPTIBLE(不可中断休眠,不推荐)。*/
/* ③在调用pre_pare_to_wait之后,若进程仍有必要等待,则调用 schedule*/
schedule();
/* ④一旦schedule 返回,就到了清理时间*/
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
/*⑤代码测试其状态,并判断是否需要重新等待*/
(2)独占等待
当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项。它与一个正常的睡眠有 2 个重要的不同:
①当等待队列入口设置了 WQ_FLAG_EXCLUSEVE 标志,它被添加到等待队列的尾部;否则,添加到头部。
②当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止唤醒.但内核仍然每次唤醒所有的非独占等待。
执行独占等待的进程每次只会被唤醒其中一个。但是,内核每次仍然会唤醒所有非独占等待进程。
void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);
三、实例
scull_pipe.h
#ifndef _SCULL_H_
#define _SCULL_H_
#include <linux/ioctl.h>
#define DEBUG
//#undef PDEBUG
#ifdef DEBUG
//# ifdef __KERNEL__
# define PDEBUG(fmt,args...) printk(KERN_DEBUG "scull:" fmt,## args)
//# else
//# define PDEBUG(fmt,args...) fprintf(stderr,fmt,## args)
//# endif
#else
# define PDEBUG(fmt,args...) /*no printf*/
#endif
#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0
#endif
#ifndef SCULL_P_NR_DEVS
#define SCULL_P_NR_DEVS 4
#endif
#ifndef SCULL_P_BUFFER
#define SCULL_P_BUFFER 4000
#endif
#define TYPE(minor) ((minor)>>4 && 0xff)
#define NUM(minor) ((minor) & 0xf)
extern int scull_major; /*from main.c*/
extern int scull_p_buffer; /*for pipe.c*/
int scull_p_init(dev_t dev);
void scull_p_cleanup(void);
int scull_access_init(dev_t dev);
void scull_access_cleanup(void);
/* * Ioctl definitions
* */
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* * S means "Set" through a ptr,
* * T means "Tell" directly with the argument value
* * G means "Get": reply by setting through a pointer
* * Q means "Query": response is on the return value
* * X means "eXchange": switch G and S atomically
* * H means "sHift": switch T and Q atomically
* */
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
/*
* * The other entities only have "Tell" and "Query", because they're
* * not printed in the book, and there's no need to have all six.
* * (The previous stuff was only there to show different ways to do it.
* */
#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14)
/* ... more to come */
#define SCULL_IOC_MAXNR 14
#endif
scull_pipe.c
//#include <linux/config.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include "scull_pipe.h"
struct scull_pipe {
wait_queue_head_t inq,outq;//等待队列
char *buffer,*end;//缓冲起始区
int buffersize;//总长度
char *rp,*wp;//读写指针地址
int nreaders,nwriters;//读写者个数
struct fasync_struct *async_queue;
struct semaphore sem;
struct cdev cdev;
int data;
};
/*parameter*/
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
static int scull_p_nr_devs = SCULL_P_NR_DEVS;
int scull_p_buffer = SCULL_P_BUFFER;
struct class *scull_class;
dev_t scull_p_devno;
module_param(scull_p_nr_devs,int,S_IRUGO);
module_param(scull_p_buffer,int,S_IRUGO);
struct scull_pipe *scull_p_devices;
static int scull_p_fasync(int fd,struct file *filp,int mode);
static int spacefree(struct scull_pipe *dev);
/*若设备以读写方式打开,它的长度截零,未锁定信号量*/
int scull_p_open(struct inode *inode,struct file *filep)
{
struct scull_pipe *dev;
dev = container_of(inode->i_cdev,struct scull_pipe,cdev);
filep->private_data = dev;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
// if(!dev->buffer){
// /*allocate the buffer*/
// dev->buffer = kmalloc(scull_p_buffer,GFP_KERNEL);
// if(!dev->buffer){
// up(&dev->sem);
// return -ENOMEM;
// }
// }
// dev->buffersize = scull_p_buffer;
// dev->end = dev->buffer + dev->buffersize;
// dev->rp = dev->wp = dev->buffer;/*rd and wr from beginning*/
/*use f_mode,not f_flags:it's cleaner*/
if(filep->f_mode & FMODE_READ)
dev->nreaders++;
if(filep->f_mode & FMODE_WRITE)
dev->nwriters++;
up(&dev->sem);
return 0;
}/*open*/
int scull_p_release(struct inode *inode,struct file *filep)
{
struct scull_pipe *dev = filep->private_data;
scull_p_fasync(-1,filep,0);
down(&dev->sem);
if(filep->f_mode & FMODE_READ)
dev->nreaders--;
if(filep->f_mode & FMODE_WRITE)
dev->nwriters--;
//if(dev->nreaders + dev->nwriters == 0){
// kfree(dev->buffer);
// dev->buffer = NULL;
//}
up(&dev->sem);
return 0;
}/*close*/
ssize_t scull_p_read(struct file *filep,char __user *buf,size_t count,loff_t *f_pos)
{
struct scull_pipe *dev = filep->private_data;
if(down_interruptible(&dev->sem)) //信号量
return -ERESTARTSYS;
while(dev->rp == dev->wp){ /*nothing to read*/
up(&dev->sem); //释放信号量
if(filep->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" reading:going to sleep\n",current->comm);/*当前进程执行的程序名*/
if(wait_event_interruptible(dev->inq,(dev->rp != dev->wp)))
return -ERESTARTSYS;/*非零,表示信号被打断。等待读取信号,同时通知fs层做相应处理*/
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;/*否则循环,但首先获取锁*/
}
/*ok,data is these,return something*/
if(dev->wp>dev->rp) //确认需要写入的大小
count = min(count,(size_t)(dev->wp-dev->rp));
else/*写入指针回卷,返回数据dev->end*/
count = min(count,(size_t)(dev->end-dev->rp));
if(copy_to_user(buf,dev->rp,count)){
up(&dev->sem);
return -EFAULT;
}
dev->rp += count;
if(dev->rp == dev->end)
dev->rp = dev->buffer;/*restart*/
up(&dev->sem);
/*finally,sawark any write and return*/
wake_up_interruptible(&dev->outq); //唤醒写入
PDEBUG( "\"%s\"Dubug scull_read length is %ld\n",current->comm,(long)count);
return count;
}
/*wait for space for writing;caller must hold device semaphore.
* on error the semaphore will be released before returning*/
static int scull_getwritespace(struct scull_pipe *dev,struct file *filep)
{
//等待可用于写入的空间,调用者必须拥有设备信号量
while(spacefree(dev) == 0){/*full空的*/
//如果缓冲区有可用的空间,则不进入while循环,直接还回0
DEFINE_WAIT(wait);//初始化等待队列入口
up(&dev->sem);//休眠前,必须释放信号量
if(filep->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\"writing:going to sleep\n",current->comm);
prepare_to_wait(&dev->outq,&wait,TASK_INTERRUPTIBLE);//将等待队列加入,不赊账进程状态
if(spacefree(dev)==0)//重新判断是否有可用空间,若还是没有,则调用schedule,让出CPU,进入休眠
schedule();//????为什么需要进程调度,唯一的唤醒机会,避免休眠
finish_wait(&dev->outq,&wait);//一旦schedule退出,则清理等待队里
if(signal_pending(current))
return -ERESTARTSYS;/*信号,告诉fs做相应处理*/
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
return 0;
}
/*有多少空间可释放,有多少可用空间*/
static int spacefree(struct scull_pipe *dev)
{
if(dev->rp == dev->wp)//空间最大
return dev->buffersize-1;
return ((dev->rp+dev->buffersize-dev->wp)%dev->buffersize)-1;
}
ssize_t scull_p_write(struct file *filep,const char __user *buf,size_t count,loff_t *f_pos)
{
struct scull_pipe *dev = filep->private_data;
int result;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
/*make sure there'sspace to write*/
result = scull_getwritespace(dev,filep);
if(result)
return result;
/*ok,space is there accept something*/
count = min(count,(size_t)spacefree(dev));
if(dev->wp >= dev->rp)
count = min(count,(size_t)dev->end-(size_t)dev->wp);
else
count = min(count,(size_t)(dev->rp-dev->wp-1));//取能写入最小值
PDEBUG("Going to accept %li bytes to %p from %p\n",(long)count,dev->wp,buf);
if(copy_from_user(dev->wp,buf,count)){
up(&dev->sem);//读取失败
return -EFAULT;
}
dev->wp+=count;
if(dev->wp == dev->end)
dev->wp = dev->buffer;
up(&dev->sem);
/*完成,唤醒读取*/
wake_up_interruptible(&dev->inq);
/*异步读写信号*/
if(dev->async_queue)
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
PDEBUG("\"%s\"did write %ld bytes\n",current->comm,(long)count);
return count;
}/*end result*/
/*scull_llseekn法用来修改文件的当前可读位置,并将新位置作为返回值返回*/
loff_t scull_p_llseek(struct file *filep,loff_t off,int whence)
{
struct scull_pipe *dev = filep->private_data;
loff_t newpos;
switch(whence){
case 0: /*seek_set*/
newpos = off;
break;
case 1: /*seek_cur*/
newpos = filep->f_pos + off;
break;
case 2: /*seek_end*/
newpos = dev->buffersize + off;
break;
default: /*no default*/
return -EINVAL;
}
if(newpos < 0) return -EINVAL;
filep->f_pos = newpos;
return newpos;
}
long scull_p_ioctl(struct file *filep,unsigned int cmd,unsigned long arg)
{
int err=0,retval=0;
int temp;
struct scull_pipe *dev = filep->private_data;
//判断命令幻数是否匹配
if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
//判断命令序号是否非法
if(_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;
//判断空间是否可访问
if(_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,(void *)arg,_IOC_SIZE(cmd));
if(_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,(void *)arg,_IOC_SIZE(cmd));
if(err)
return -EFAULT;
switch(cmd){
case SCULL_IOCRESET://数据清零
dev->data = 0;
PDEBUG("\"%s\"SCULL_IOCRESET\n",current->comm);
break;
case SCULL_IOCSQUANTUM://指针赋值
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
retval=__get_user(dev->data,(int __user *)arg);
PDEBUG("\"%s\"SCULL_IOCSQUANTUM\n",current->comm);
break;
case SCULL_IOCTQUANTUM://数值赋值
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
dev->data=arg;
PDEBUG("\"%s\"SCULL_IOCTQUANTUM\n",current->comm);
break;
case SCULL_IOCGQUANTUM://读取指针
retval=__put_user(dev->data,(int __user *)arg);
PDEBUG("\"%s\"SCULL_IOCGQUANTUM\n",current->comm);
break;
case SCULL_IOCQQUANTUM://读取数值
PDEBUG("\"%s\"SCULL_IOCQQUANTUM\n",current->comm);
return dev->data;
case SCULL_IOCXQUANTUM://交换“s”和“g”
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
temp = dev->data;
retval = __get_user(dev->data,(int __user *)arg);
if(0 == retval)//返回成功
retval = __put_user(temp,(int __user *)arg);
PDEBUG("\"%s\"SCULL_IOCXQUANTUM\n",current->comm);
return retval;
case SCULL_IOCHQUANTUM://交换"t"和“q”
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
temp = dev->data;
dev->data = arg;
arg = temp;
PDEBUG("\"%s\"SCULL_IOCHQUANTUM\n",current->comm);
return temp;
default://其余暂时去除
PDEBUG("\"%s\"SCULL_DEFAULT\n",current->comm);
return -ENOTTY;
}
return retval;
}
static unsigned int scull_p_poll(struct file *filep,poll_table *wait)
{
struct scull_pipe *dev = filep->private_data;
unsigned int mask = 0;
/*
*The buffer is circular;its is considered full
*if "wp" is right behind "rp" and empty if the
*two are equal
* */
down(&dev->sem);
poll_wait(filep,&dev->inq,wait);
poll_wait(filep,&dev->outq,wait);
if(dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM;/*读取使能*/
if(spacefree(dev))
mask |= POLLIN | POLLWRNORM;/*写入使能*/
up(&dev->sem);
return mask;
}
static int scull_p_fasync(int fd,struct file *filep,int mode)
{
struct scull_pipe *dev = filep->private_data;
return fasync_helper(fd,filep,mode,&dev->async_queue);
}
//scull设备的文件操作
struct file_operations scull_pipe_fops = {
.owner = THIS_MODULE,
.llseek = scull_p_llseek,
.read = scull_p_read,
.write = scull_p_write,
.unlocked_ioctl = scull_p_ioctl,
.open = scull_p_open,
.release = scull_p_release,
.fasync = scull_p_fasync,
};
void scull_p_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major,scull_minor);
if(scull_p_devices){
for(i=0;i<scull_p_nr_devs;i++)
{
cdev_del(&scull_p_devices[i].cdev);
}
kfree(scull_p_devices);
}
device_destroy(scull_class,MKDEV(scull_major,0));
class_destroy(scull_class);
unregister_chrdev_region(devno,scull_p_nr_devs);
scull_p_devices = NULL;
}
static void scull_p_setup_cdev(struct scull_pipe *dev,int index)
{
int err,devno = MKDEV(scull_major,scull_minor+index);
/*从open函数移到此处,实现文件本身有数据时,打开能读取数据*/
if(!dev->buffer){
/*allocate the buffer*/
dev->buffer = kmalloc(scull_p_buffer,GFP_KERNEL);
if(!dev->buffer){
up(&dev->sem);
return -1;//-ENOMEM;
}
}
dev->buffersize = scull_p_buffer;
dev->end = dev->buffer + dev->buffersize;
dev->rp = dev->wp = dev->buffer;/*rd and wr from beginning*/
cdev_init(&dev->cdev,&scull_pipe_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_pipe_fops;
err = cdev_add(&dev->cdev,devno,1);
if(err)
printk(KERN_EMERG "Error %d addingscull %d!\n",err,index);
}
int scull_p_init_module(void)
{
int result,i;
dev_t dev = 0;
printk(KERN_EMERG "Debug is scull_init module!\n");
//获取主设备数号
if(scull_major){
dev = MKDEV(scull_major,scull_minor);
result = register_chrdev_region(dev,scull_p_nr_devs,"scullp");
}else{
result = alloc_chrdev_region(&dev,scull_minor,scull_p_nr_devs,"scullp");
scull_major = MAJOR(dev);
}
if(result < 0){
printk(KERN_EMERG "scull:can't get major %d/n",scull_major);
return result;
}
scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe),GFP_KERNEL);
if(!scull_p_devices){
result = -ENOMEM;
goto fail;
}
memset(scull_p_devices,0,scull_p_nr_devs * sizeof(struct scull_pipe));
printk(KERN_EMERG "the length of scull_dev is %ld\n",sizeof(struct scull_pipe));
for(i=0;i<scull_p_nr_devs;i++){
init_waitqueue_head(&(scull_p_devices[i].inq));
init_waitqueue_head(&(scull_p_devices[i].outq));
init_MUTEX(&scull_p_devices[i].sem);
//用于udev/mdev自动创建节点
scull_p_setup_cdev(&scull_p_devices[i],i);
}
scull_class = class_create(THIS_MODULE,"scull_pipe");
device_create(scull_class,NULL,dev,NULL,"scull_pipe");
dev=MKDEV(scull_major,scull_minor+scull_p_nr_devs);
return 0;
fail:
scull_p_cleanup_module();
return result;
}
module_init(scull_p_init_module);
module_exit(scull_p_cleanup_module);
MODULE_LICENSE("GPL");
Makefile
# Comment/uncomment the following line to disable/enable debugging
DEBUG = y
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif
#CFLAGS += $(DEBFLAGS)
#CFLAGS += -I..
ifneq ($(KERNELRELEASE),)
# call from kernel build system
obj-m := scull_pipe.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
#depend .depend dep:
# $(CC) $(CFLAGS) -M *.c > .depend
#ifeq (.depend,$(wildcard .depend))
#include .depend
#endif
四、实验步骤
1、首先先在主目录下make,编译模块,生成.ko文件。
出现如下:
# make
make -C /lib/modules/2.6.35.3-571-gcca29a0/build M=/root/BLOCK modules
make[1]: 正在进入目录 `/usr/src/linux-2.6.35.3'
CC [M] /root/BLOCK/scull_pipe.o
/root/BLOCK/scull_pipe.c: 在函数‘scull_p_setup_cdev’中:
/root/Device_Driver/Cdev_Driver/Ldd-6/BLOCK/scull_pipe.c:377:13: 警告: 在无返回值的函数中,‘return’带返回值 [默认启用]
/root/Device_Driver/Cdev_Driver/Ldd-6/BLOCK/scull_pipe.c: 在文件作用域:
/home/fengweilong/桌面/Linux_Pro/Device_Driver/Cdev_Driver/Ldd-6/BLOCK/scull_pipe.c:310:21: 警告: ‘scull_p_poll’定义后未使用 [-Wunused-function]
Building modules, stage 2.
MODPOST 1 modules
CC /root/Cdev_Driver/Ldd-6/BLOCK/scull_pipe.mod.o
LD [M] /root/Cdev_Driver/Ldd-6/BLOCK/scull_pipe.ko
make[1]:正在离开目录 `/usr/src/linux-2.6.35.3'
2、编译模块到内核,装载设备
# insmod scull_pipe.ko
3、参考第三章操作,使用两个界面来验证驱动是否可用
界面1(发送)
# echo "hello" > /dev/scull_pipe
# echo "the end" > /dev/scull_pipe
# echo "Good" > /dev/scull_pipe
界面2(显示)
# cat /dev/scull_pipe
hello
the end
Good
五、总结
以上就是《linux设备驱动程序》第六章第二部分——阻塞IO和高级休眠的主要内容。主要是实现在读取和写入时,阻塞其中一个进程,防止数据竞态,学习了休眠和唤醒方式。