目录
- 1、阻塞与非阻塞
- 1.1 以对recvfrom函数的调用及执行过程来说明阻塞的操作。
- 1.2 以对recvfrom函数的不断轮询调用为例,说明非阻塞时进程的行为。
- 1.3 简单介绍内核链表及等待队列
- 1.4 等待队列
- 1.4.1 定义等待队列头部(wait_queue_head_t)
- 1.4.2 初始化等待队列头部(init_waitqueue_head())
- 1.4.3 简化的队列头元素的定义与初始化
- 1.4.4 定义等待队列元素(DECLARE_WAITQUEUE())
- 1.4.5 添加/移除等待队列(add_wait_queue / remove_wait_queue)
- 1.4.6 等待事件,进入阻塞(wait_event() / wait_event_interruptible)
- 1.4.7 唤醒 (wake_up() / wake_up_interruptible())
- 1.4.8 内核函数open的阻塞与非阻塞
- 1.4.9 wait_event 与 add_wait_queue 函数用法的区别
- 1.4.10 __set_current_state 与 set_current_state函数
- 1.4.11 两个实例
linux在内核层面实现对外设的访问控制共有五种I/O模型,分别如下:
1、阻塞 : 当某进程在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。
2、非阻塞 : 当进程不能进行设备操作时,并不挂 起,而由开发者决定是放弃还是不停轮询,直到可以进行操作为止。
3、多路复用 :I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。如select,poll,epoll 。
4、信号驱动的异步通知:在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
5、异步I/O :异步I/O,和信号驱动有点类似,也是发出I/O请求后就不再等待或阻塞,可以去完成其它工作。不同的是,内核去负责该请求工作,并在内核完成数据的访问,只把结果提交到请求者。而信号驱动还需要由请求者自已完成I/O工作。
由于内容较多,将会分几个篇幅完成这五种I/O的解读。
1、阻塞与非阻塞
阻塞是指在某进程在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被系统从调度器的运行队列移到等待队列,直到等待的条件被满足。
而非阻塞操作的进程在不能进行设备操作时,并不挂起,由开发者决定是放弃还是不停查询,直至可以进行操作为止。
因此,在内核层面,驱动通常要提供这样的能力:当应用程序进行read() write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动 xxx_read()、xxx_write()等操作中将进程阻塞直到资源可以获取,此后,应用程序的read()、write()等调用才返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;
若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read()、xxx_write()等操作应立即返回,read()、write()等系统调用也随即被返回,应用程序收到-EAGAIN返回值。
1.1 以对recvfrom函数的调用及执行过程来说明阻塞的操作。
从应用层的角度看。当recvfrom发出后,不能及时得到数据,则进程实际是进入阻塞,也即进入了睡眠态,这时进程是不占用cpu的时间片的。
1.2 以对recvfrom函数的不断轮询调用为例,说明非阻塞时进程的行为。
非阻塞:不能操作就返回错误。应用层应根据出错提示而采用不断的轮询来询问内核是否有数据了。非阻塞方式使调用程序不断被调度,占用了CPU的时间片
1.3 简单介绍内核链表及等待队列
在linux内核中,链表是一个非常重要的数据结构。与通常做法中的链表不同,Linux内核方式与众不同,它不是将数据结构塞入链表,而是将链表节点塞入数据结构。这样链表就可以链接在任意的数据结构上。
linux链表的基本结构为一个有着双各指针的链表节点数据结构list_head,其定义如下:
struct list_head{
struct list_head *prev,*next;
}
该节点结构可以被与入任意的数据结构内,当成该数据结构的一个元素,如下:
struct data{
int a;
struct spinlock lock;
char *c;
sturct list_head head;
};
list_head节点之间前后串联,形成一个数据链表,如示意图,示意中只出现了单向循环。
这样,当取得一个list_head的地址后,就可以通过container_of()宏而取得整个数据结构的地址。具体是通过list_entry()宏取得的。
#define list_entry( ptr , type , member) container_of(ptr , type , member)
以上简单介绍完了linux内核链表机制,而linux内核的等待队列就是链表机制的一个应用。
1.4 等待队列
linux五种状态(运行态、浅睡眠态、深睡眠态、暂停态、僵死态)的调度,靠的是链表队列实现在,具体从运行态到睡眠态的调度示意如下:
linux 通过结构体task_struct维护所有运行的线程,进程。 不同状态的任务,会由不同的队列进行维护, schedule()函数就负责根据这些状态的变化调度这些任务。
因此,需要阻塞的任务或进程,需要创建一个等待队列,并调用内核函数把这个等待队列放入系统调度的等待队列池内,再调用schedule()函数使任务或进程进入睡眠态。具体步骤如下:
1.4.1 定义等待队列头部(wait_queue_head_t)
头文件
include/linux/wait.h
原码定义:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
定义变量例:
wait_queue_head_t my_queue;
1.4.2 初始化等待队列头部(init_waitqueue_head())
头文件:
include/linux/wait.h
原码定义:
#include <include/linux/wait.h>
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
__init_waitqueue_head((q), #q, &__key); \
} while (0)
定义文件 kernel/sched/wait.c
void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
{
spin_lock_init(&q->lock); //初始化等待队列头的自旋锁
lockdep_set_class_and_name(&q->lock, key, name);
INIT_LIST_HEAD(&q->task_list); //初始化列表头,使列表头指针指向了自已
}
使用:
init_waitqueue_head(&my_queue)
1.4.3 简化的队列头元素的定义与初始化
宏DECLARE_WAIT_QUEUE_HEAD(name)可以同时完成wait_queue_head_t等待队列头的创建以及初始化。因此会更便捷一些。其中的参数name为等待队列头变量名。
参考原码如下:
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
1.4.4 定义等待队列元素(DECLARE_WAITQUEUE())
头文件:
#include <linux/wait.h>
wait_queue_t数据结构参考原码:
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
flags: 可以设置为 WQ_FLAG_EXCLUSIVE,表示等待的进程应该独占资源(解决惊群现象)。
private: 一般用于保存等待进程的进程描述符 task_struct。
func: 唤醒函数,一般设置为 default_wake_function() 函数,当然也可以设置为自定义的唤醒函数。
task_list: 用于连接其他等待资源的进程。
DECLARE_WAITQUEUE原码参考:
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL }
}
其中,
name:是要生成的wait_queue_t 等待队列元素项的变量名。
tsk:等待进程的进程描述符 task_struct。
使用:
- 补充:wait_queue_head_t 与 wait_queue_t的区别及关系
1.4.5 添加/移除等待队列(add_wait_queue / remove_wait_queue)
参考原码:
定义文件 kernel/sched/wait.c
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue);
#include <linux/wait.h>
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(remove_wait_queue);
#include <linux/wait.h
static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
{
list_del(&old->task_list);
}
1.4.6 等待事件,进入阻塞(wait_event() / wait_event_interruptible)
wait_event(wq,condition) //宏,把当前进程加入等待队列头,并使当前进程处于深度睡眠
wait_event_interruptible(wq,condition) //宏,把当前进程加入等待队列头,并使当前进程处于浅度睡眠
还有其它宏:
wait_event_timeout(wq , condition , timeout);
wait_event_interruptible_timeout(wq , condition , timeout);
功能:
- 将当前进程加入等待队列wq,并睡眠,直到condition条件变为真,被唤醒。这个函数常用于进程间同步和通信。默认wait_queue_t的flag为0即非独占。
参数:
- wq: 等待队列头 类型 wait_queue_head_t 进程会被加入这个等待队列中,进入睡眠状态。
- condition:C语言表达式,等待条件。当这个条件变为真时,处于等待队列中的进程会被唤醒。
- timeout : 阻塞等待的超时时间,以jiffy为计时单位。超过这个时间将不再阻塞。
返回值:
- 正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
举个例子:
wait_queue_head_t wq; // 定义等待队列
// 进程A
wait_event(wq, condition); // 等待condition成立
// 进程B
condtion = true; // 改变condition
wake_up(&wq); // 唤醒等待队列wq中的进程
进程A调用wait_event()进入睡眠,加入等待队列wq。
进程B设置condition为真,然后调用wake_up()唤醒wq中的进程。
这时,进程A被唤醒,wait_event()返回。
所以wait_event()通过等待队列实现了进程间的同步和通信。
1.4.7 唤醒 (wake_up() / wake_up_interruptible())
wake_up(wait_queue_head_t *pwq) 唤醒深度睡眠
另外,wake_up()有两个变种:
- wake_up_nr(wq, nr):唤醒wq队列上的nr个进程
- wake_up_interruptible(wait_queue_head_t *pwq) 唤醒浅度睡眠 (状态为TASK_INTERRUPTIBLE)
wake_up_all(wait_queue_head_t *wq);wake_up_all(wait_queue_head_t *wq);函数会继续唤醒wq等待队列上的进程,直到队列为空为止,所以它会唤醒全部进程。
wake_up 它的功能是:
- 唤醒wq等待队列上的第一个未设置WQ_FLAG_EXCLUSIVE标志的进程(WQ_FLAG_EXCLUSIVE表示此进程独占资源,只有当它唤醒时才唤醒后续等候进程)。
- 处理WQ_FLAG_ONESHOT类型的进程(表示只唤醒一次),将它移出等待队列。
- 重复步骤1和2,直到等待队列为空或者唤醒的进程数量达到MAX_WAKEUPS(默认20个)。
所以,wake_up()在默认情况下最多唤醒20个等待进程,这20个进程中又优先唤醒非独占且非oneshot类型的进程。它不一定唤醒等待队列上的全部进程。
1.4.8 内核函数open的阻塞与非阻塞
知道了内核层面的阻塞与非阻塞的概念和实现的函数。但从应用层角度来看,如何设定操作的阻塞与非阻塞?
应用层: open时由O_NONBLOCK指示read、write时是否阻塞
open()系统调用用于打开一个文件,它的原型是:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
flags参数中与阻塞相关的选项主要有:
- O_NONBLOCK:以非阻塞方式打开文件,对于O_RDONLY、O_WRONLY、O_RDWR有效。如果读操作遇到没有数据可读,或者写操作遇到设备满,则报错返回EAGAIN,而不是阻塞。
- O_NDELAY:与O_NONBLOCK相同,linux上O_NONBLOCK优先使用。
- O_SYNC:打开文件之后,每次写操作都会立即同步到磁盘,而不是异步缓存。这会使写操作变慢,但可以保证数据不丢失。
- O_DSYNC:只对数据同步,而不是元数据。与O_SYNC相比可以更高效,但只能保证数据完整性,不能保证元数据完整性。
- O_RSYNC:只对读操作同步,写操作仍然异步。用于需要保证读得到最新写入数据的场景。
除此之外,flags还有一些其他与阻塞无关的选项: - O_APPEND:每次写操作追加到文件尾,而不是覆盖。
- O_CREAT:如果文件不存在就创建它。
- O_EXCL:与O_CREAT一起使用,要求文件必须不存在,否则出错。用于创建锁定文件。
- O_TRUNC:如果文件存在,就把文件长度截为0。
- O_RDWR:以读写方式打开文件。
- O_WRONLY:以只写方式打开文件。
- O_RDONLY:以只读方式打开文件。
所以,通过设置不同的flags,可以实现阻塞或非阻塞的文件访问,同步或异步的数据写入等各种操作。这给应用开发提供了很大的灵活性。
应此如果不加O_NONBLOCK标志,则open后,read与write等操作是处于允许阻塞状态的,因此对应的驱动程序要对应有阻塞的代码
同时,应用层open的flag标志是通过前几章说到的struct file 结构体传递给了驱动的。
1.4.9 wait_event 与 add_wait_queue 函数用法的区别
wait_event()和add_wait_queue()都是用于进程间同步和通信的,但用法有区别:
wait_event():
- 自动将当前进程加入等待队列,并睡眠,直到条件满足被唤醒
- 使用简单,适合大多数情况
add_wait_queue(): - 只将当前进程加入等待队列,不会自动睡眠
- 需要手动调用schedule()使进程睡眠
- 需要手动检查条件,调用wake_up()唤醒进程
- 使用灵活,可以更精细地控制同步逻辑,适用于比较复杂的情况
举个例子:
// wait_event()
wait_queue_head_t wq;
// 进程A
wait_event(wq, condition);
// 进程B
condition = true;
wake_up(&wq);
// add_wait_queue()
// 进程A
add_wait_queue(&wq, &wait);
while (!condition) {
set_current_srtate(TASK_INTERRUPTIBLE);
schedule(); // 睡眠
}
// 唤醒后,condition为真,继续执行
// 进程B
condition = true;
wake_up(&wq);
所以:
- 如果你只需要简单地等待某个条件,使用wait_event()即可,更简单。
- 如果需要更复杂的等待逻辑,要自己控制进程何时睡眠和唤醒,可以使用add_wait_queue()。
总之,wait_event()是add_wait_queue()的封装,使用更简单,但灵活性稍差。适用场景不同,可以根据需要选择。
1.4.10 __set_current_state 与 set_current_state函数
/include/linux/sched.h
#define __set_current_state(state_value)
do { current->state = (state_value); } while (0)
#define set_current_state(state_value)
set_mb(current->state, (state_value))
这两个宏的区别
__set_current_state()和set_current_state()都是用于设置当前进程的状态,但有一定区别:
__set_current_state():
- 是一个宏,展开为直接赋值current->state
- 简单高效,但可能导致current->state被优化掉
- 不能在中断上下文使用,会触发编译警告
set_current_state(): - 是一个函数,通过set_mb()设置current->state
- set_mb()是一个内存屏障,可以确保当前->state的修改不被优化掉
- 可以在任何上下文使用
- 相比宏稍微低效一点
所以,这两个接口的主要区别在于: - __set_current_state()是一个宏,简单高效但在某些情况下状态修改可能被优化掉,只能在进程上下文使用。
- set_current_state()是一个函数,通过内存屏障可以确保状态修改不被优化,可以在中断/进程上下文使用,但性能略低。
在大部分场景下,这两个接口可以互换使用,功能基本相同。但是在一些特殊情况下,需要注意选择: - 如果状态修改后立即就要使用,必须使用set_current_state(),以防止被优化。
- 如果在中断上下文需要设置状态,必须使用set_current_state()。
- 如果严格要求性能,并可以确保状态修改后立即使用和修改是在进程上下文,可以使用__set_current_state()。
所以,总结如下: - 大部分场景下,二者可以互换,__set_current_state()性能略好。
- 如果状态修改后立即使用或在中断上下文,必须使用set_current_state()。
- 需要根据具体情况选择使用哪个接口。
两个接口都用于设置进程状态,但在一定程度上有交叉使用场景,需要根据实际需求选择更合适的那个。
1.4.11 两个实例
还是之前的以内存缓冲虚拟设备来实现字符驱动对设备的读写等操作。如果遇到缓冲满或空,则驱动的写与读应处于阻塞状态。使对应
采用wait_event
/*************************************************************************
> File Name:block-memory.c
驱动程序根据应用层的flag标志决定是否采用阻塞或非阻塞的工作方式
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
/*1、定义重要的变量及结构体*/
#define MEM_SIZE 500 //每个虚拟设备内存大小
#define DEV_NUM 3 //创建的设备总个数
struct mem_dev_t{
struct cdev my_dev; //cdev设备描述结构体变量
char mem[MEM_SIZE]; //fifo内存池,当成虚拟设备
int curpos; //内存当前数据最后位置指示,从0开始记
struct semaphore sem; //信号量
wait_queue_head_t write_queue; //写等待队列
wait_queue_head_t read_queue; //读等待队列
};
struct mem_dev_t *mem_dev;
/*所有驱动函数声明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
.open = open,
.release = release,
.read = read,
.write = write,
};
/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
static int cdev_setup(struct mem_dev_t *mem_dev , dev_t devno ){
int unsucc =0;
cdev_init(&mem_dev->my_dev , &fops);
mem_dev->my_dev.owner = THIS_MODULE;
/*4、注册cdev结构体到内核链表中*/
unsucc = cdev_add(&mem_dev->my_dev , devno , 1);
if (unsucc){
printk("driver : cdev add faild \n");
return -1;
}
sema_init( &mem_dev->sem,1); //初始化信号量,为1
mem_dev->curpos = 0; //初始化缓冲数据位置为0
init_waitqueue_head(&mem_dev->write_queue);
init_waitqueue_head(&mem_dev->read_queue);
return 0;
}
static int __init my_init(void){
int major , minor;
dev_t devno;
int unsucc =0;
int i=0;
mem_dev = kzalloc(sizeof(struct mem_dev_t)*DEV_NUM , GFP_KERNEL);
if (!mem_dev){
printk(" driver : allocating memory is failed");
return -1;
}
/*2、创建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "block_memory");
if (unsucc){
printk(" driver : creating devno is failed\n");
return -1;
}else{
major = MAJOR(devno);
minor = MINOR(devno);
printk("diver : major = %d ; minor = %d\n",major,minor);
}
/*3、 初始化cdev结构体,并联cdev结构体与file_operations.*/
/*4、注册cdev结构体到内核链表中*/
for (i=0;i<DEV_NUM;i++){
devno = MKDEV(major , i);
if (cdev_setup(mem_dev+i , devno) == 0){
printk("deiver : the driver block_memory[%d] initalization completed\n", i);
} else
printk("deiver : the driver block_memory[%d] initalization failed\n", i);
}
return 0;
}
static void __exit my_exit(void)
{
int i=0;
dev_t devno;
devno = mem_dev->my_dev.dev;
for (i=0 ; i<DEV_NUM ; i++){
cdev_del(&(mem_dev+i)->my_dev);
}
unregister_chrdev_region(devno , DEV_NUM);
printk("***************the driver operate_memory exit************\n");
}
/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){
int minor = MINOR(pnode->i_rdev);
int major = MAJOR(pnode->i_rdev);
struct mem_dev_t *p = container_of(pnode->i_cdev , struct mem_dev_t , my_dev);
pf->private_data = p; //把全局变量指针放入到struct file结构体里
if (pf->f_flags & O_NONBLOCK){ //非阻塞
printk("driver : block_memory[%d , %d] is opened by nonblock mode\n",major , minor);
}else{
printk("driver : block_memory[%d , %d] is opened by block mode\n",major,minor);
}
return 0;
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
printk("block_memory is closed \n");
return 0;
}
/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
//本例中,因为是fifo,所以ppos参数不用。
struct mem_dev_t *pdev = pf->private_data;
int count = 0; //存储读到多少数据
down(&pdev->sem);
if (pdev->curpos == 0){
up(&pdev->sem); //退出前释放信号量,V操作
if ((pf->f_flags & O_NONBLOCK) == 0){
wait_event_interruptible(pdev->read_queue , pdev->curpos > 0);
//当前没有数据,进入阻塞睡眠
}else{
return 0;
}
}else{
up(&pdev->sem);
}
down(&pdev->sem); //信号量P操作
//判断能够读到的字节数量
if (size > pdev->curpos){
count = pdev->curpos;
}else{
count = size;
}
//copy_from_user返回值大于0失败
if ( copy_to_user(buf , &pdev->mem , count )){ //读取失败
up(&pdev->sem); //退出前释放信号量,V操作
return 0;
}else{ //成功读取
memcpy(&pdev->mem , &pdev->mem[count] , pdev->curpos-count);
pdev->curpos -= count;
up(&pdev->sem); //退出前释放信号量,V操作
wake_up_interruptible(&pdev->write_queue); //唤醒可能睡眠的write
return count;
}
}
/*file_operations结构全成员函数.write的具体实现*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){
struct mem_dev_t *pdev = pf -> private_data;
int count =0;
down(&pdev->sem);
if (pdev->curpos == (MEM_SIZE -1)){
up(&pdev->sem);
if ((pf->f_flags & O_NONBLOCK) == 0){
wait_event_interruptible(pdev->write_queue , pdev->curpos < (MEM_SIZE -1));
}else{
return 0;
}
}else{
up(&pdev->sem);
}
down(&pdev->sem);
if (size > (MEM_SIZE-pdev->curpos)){
count = MEM_SIZE-pdev->curpos;
}else{
count = size;
}
if (copy_from_user(&pdev->mem[pdev->curpos],buf,count)){
up(&pdev->sem);
return 0;
}else{
pdev->curpos +=count;
up(&pdev->sem);
wake_up_interruptible(&pdev->read_queue);
return count;
}
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
采用add_wait_queue
/*************************************************************************
> File Name:block-memory-1.c
驱动程序根据应用层的flag标志决定是否采用阻塞或非阻塞的工作方式
本例用的是add_wait_queue函数
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
/*1、定义重要的变量及结构体*/
#define MEM_SIZE 500 //每个虚拟设备内存大小
#define DEV_NUM 3 //创建的设备总个数
struct mem_dev_t{
struct cdev my_dev; //cdev设备描述结构体变量
char mem[MEM_SIZE]; //fifo内存池,当成虚拟设备
int curpos; //内存当前数据最后位置指示,从0开始记
struct semaphore sem; //信号量
wait_queue_head_t write_queue; //写等待队列
wait_queue_head_t read_queue; //读等待队列
};
struct mem_dev_t *mem_dev;
/*所有驱动函数声明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
.open = open,
.release = release,
.read = read,
.write = write,
};
/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
static int cdev_setup(struct mem_dev_t *mem_dev , dev_t devno ){
int unsucc =0;
cdev_init(&mem_dev->my_dev , &fops);
mem_dev->my_dev.owner = THIS_MODULE;
/*4、注册cdev结构体到内核链表中*/
unsucc = cdev_add(&mem_dev->my_dev , devno , 1);
if (unsucc){
printk("driver : cdev add faild \n");
return -1;
}
sema_init( &mem_dev->sem,1); //初始化信号量,为1
mem_dev->curpos = 0; //初始化缓冲数据位置为0
init_waitqueue_head(&mem_dev->write_queue);
init_waitqueue_head(&mem_dev->read_queue);
return 0;
}
static int __init my_init(void){
int major , minor;
dev_t devno;
int unsucc =0;
int i=0;
mem_dev = kzalloc(sizeof(struct mem_dev_t)*DEV_NUM , GFP_KERNEL);
if (!mem_dev){
printk(" driver : allocating memory is failed");
return -1;
}
/*2、创建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "block_memory");
if (unsucc){
printk(" driver : creating devno is failed\n");
return -1;
}else{
major = MAJOR(devno);
minor = MINOR(devno);
printk("diver : major = %d ; minor = %d\n",major,minor);
}
/*3、 初始化cdev结构体,并联cdev结构体与file_operations.*/
/*4、注册cdev结构体到内核链表中*/
for (i=0;i<DEV_NUM;i++){
devno = MKDEV(major , i);
if (cdev_setup(mem_dev+i , devno) == 0){
printk("deiver : the driver block_memory[%d] initalization completed\n", i);
} else
printk("deiver : the driver block_memory[%d] initalization failed\n", i);
}
return 0;
}
static void __exit my_exit(void)
{
int i=0;
dev_t devno;
devno = mem_dev->my_dev.dev;
for (i=0 ; i<DEV_NUM ; i++){
cdev_del(&(mem_dev+i)->my_dev);
}
unregister_chrdev_region(devno , DEV_NUM);
printk("***************the driver operate_memory exit************\n");
}
/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){
int minor = MINOR(pnode->i_rdev);
int major = MAJOR(pnode->i_rdev);
struct mem_dev_t *p = container_of(pnode->i_cdev , struct mem_dev_t , my_dev);
pf->private_data = p; //把全局变量指针放入到struct file结构体里
if (pf->f_flags & O_NONBLOCK){ //非阻塞
printk("driver : block_memory[%d , %d] is opened by nonblock mode\n",major , minor);
}else{
printk("driver : block_memory[%d , %d] is opened by block mode\n",major,minor);
}
return 0;
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
printk("block_memory is closed \n");
return 0;
}
/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
//本例中,因为是fifo,所以ppos参数不用。
struct mem_dev_t *pdev = pf->private_data;
int count = 0; //存储读到多少数据
int ret = 0;
/*******************************************************/
DECLARE_WAITQUEUE(wait , current); //定义等待队列项目元素
/*******************************************************/
down(&pdev->sem);
/*******************************************************/
add_wait_queue(&pdev->read_queue , &wait); //把元素加入读等待队列
/*******************************************************/
while (pdev->curpos == 0){
if ((pf->f_flags & O_NONBLOCK) == 0){
//当前没有数据,进入阻塞睡眠
/*******************************************************/
set_current_state(TASK_INTERRUPTIBLE); //设置当前进程为可中断睡眠态
up(&pdev->sem); //退出前释放信号量,V操作
schedule(); //调度程序
/*******************************************************/
}else{
ret = 0;
goto out;
}
down(&pdev->sem);
}
//判断能够读到的字节数量
if (size > pdev->curpos){
count = pdev->curpos;
}else{
count = size;
}
//copy_from_user返回值大于0失败
if ( copy_to_user(buf , &pdev->mem , count )){ //读取失败
ret = 0;
goto out;
}else{ //成功读取
memcpy(&pdev->mem , &pdev->mem[count] , pdev->curpos-count);
pdev->curpos -= count;
up(&pdev->sem); //退出前释放信号量,V操作
/*******************************************************/
wake_up_interruptible(&pdev->write_queue); //唤醒可能睡眠的write
/*******************************************************/
ret = count;
}
out:
up(&pdev->sem); //退出前释放信号量,V操作
/********************************************************/
remove_wait_queue(&pdev->read_queue , &wait);
set_current_state(TASK_RUNNING);
/*******************************************************/
return ret;
}
/*file_operations结构全成员函数.write的具体实现*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){
struct mem_dev_t *pdev = pf -> private_data;
int count =0;
int ret = 0;
/*******************************************************/
DECLARE_WAITQUEUE(wait , current); //定义等待队列项目元素
/*******************************************************/
down(&pdev->sem);
/*******************************************************/
add_wait_queue(&pdev->write_queue , &wait); //把元素加入读等待队列
/*******************************************************/
while (pdev->curpos == (MEM_SIZE -1)){
if ((pf->f_flags & O_NONBLOCK) == 0){
/*******************************************************/
set_current_state(TASK_INTERRUPTIBLE);
up(&pdev->sem);
schedule();
/*******************************************************/
}else{
ret = 0;
goto out;
}
down(&pdev->sem);
}
if (size > (MEM_SIZE-pdev->curpos)){
count = MEM_SIZE-pdev->curpos;
}else{
count = size;
}
if (copy_from_user(&pdev->mem[pdev->curpos],buf,count)){
ret = 0;
goto out;
}else{
pdev->curpos +=count;
/*******************************************************/
wake_up_interruptible(&pdev->read_queue);
/*******************************************************/
ret = count;
}
out:
up(&pdev->sem);
/*******************************************************/
remove_wait_queue(&pdev->write_queue, &wait);
set_current_state(TASK_RUNNING);
/*******************************************************/
return ret;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
测试过程:
0、在ubuntu上打开一个终端
1、编译make
2、加载驱动 sudo insmod block-memory.ko
3、加载时可以查看分配的主设备号 cat /proc/devices | grep block-memory
4、创建设备文件,其中主设备号是第3步里查看到的 sudo mknod /dev/block-memory-0 c 主设备号 0
5、chmod 777 /dev/block-memoruy-0
6、cat /dev/block-memory-0 这时会出现阻塞
7、打开ubuntu另一个终端,输入命令 echo “hello” > /dev/block-memory-0
8、这时,上一个终端阻塞状态会解除,并输出hello