【嵌入式环境下linux内核及驱动学习笔记-(6-内核 I/O)-阻塞与非阻塞】

news2024/7/6 19:44:15

目录

  • 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 它的功能是:

  1. 唤醒wq等待队列上的第一个未设置WQ_FLAG_EXCLUSIVE标志的进程(WQ_FLAG_EXCLUSIVE表示此进程独占资源,只有当它唤醒时才唤醒后续等候进程)。
  2. 处理WQ_FLAG_ONESHOT类型的进程(表示只唤醒一次),将它移出等待队列。
  3. 重复步骤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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue动态添加多组数据添加正则限制

如图新增多条数据&#xff0c;如果删除其中一条正则校验失败的数据&#xff0c;提示不会随之删除&#xff0c;若想提示删除并不清空数据 delete (item, index) {this.applicationForm.reserveInfo.forEach((v, i) > {if (i index) {this.$refs.formValidate.fields.forEac…

UFT——操作模块

示例一 创建一个可重复利用的登录测试更改Action的名称。使用本地数据表。创建一个主调用测试。建立测试迭代。处理缺失的Action。 分析&#xff1a;就是创建一个只有登录的测试起名为login&#xff0c;然后在创建一个主测试起名字比如main&#xff0c;在main中&#xff0c;调用…

微信小程序定义模板

微信小程序提供模板&#xff08;template&#xff09;功能&#xff0c;把一些可以共用的&#xff0c;复用的代码在模板中定义为代码片段&#xff0c;然后在不同的地方调用&#xff0c;可以实现一次编写&#xff0c;多次引用的效果。 首先我们看一下官网是如何操作的 一般的情…

笔记:对多维torch进行任意维度的多“行”操作

如何取出多维torch指定维度的指定“行” 从二维torch开始新建torch取出某一行取出某一列一次性取出多行取出连续的多行取出不连续的多行 一次取出多列取出连续的多列取出不连续的多列 考虑三维torch取出三维torch的任意两行&#xff08;means 在dim0上操作&#xff09;取出连续…

( 字符串) 9. 回文数 ——【Leetcode每日一题】

❓9. 回文数 难度&#xff1a;简单 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 例如…

Git的安装与使用+Git在IDEA中的使用

文章目录 一、Git概述1、版本控制器的方式2、Git的工作流程图 二、Git的安装与常用命令1、Git环境安装2、Git环境基本配置3、获取本地仓库4、基础操作指令 三、分支1、常用指令2、解决合并冲突 四、Git远程仓库1、创建远程仓库2、远程操作仓库3、冲突处理 四、IDEA中使用Git1、…

数据结构——二叉树

二叉树 1 二叉树的种类 1.1 满二叉树 节点数量为 2^k - 1 (k是树的深度&#xff0c;底层的叶子节点都是满的&#xff09; 1.2 完全二叉树 完全二叉树是指除了下面一层外&#xff0c;其余层的节点都是满的&#xff1b; 且最下面一层的叶子节点是从左到右连续的。 下面这个…

pci总线协议学习笔记——PCI总线基本概念

1、pci总线概述 (1)PCI&#xff0c;外设组件互连标准(Peripheral Component Interconnection)&#xff0c;是一种由英特尔&#xff08;Intel&#xff09;公司1991年推出的用于定义局部总线的标准; (2)最早提出的PCI总线工作在33MHz频率之下&#xff0c;传输带宽达到133MB/s(33M…

【LeetCode】236. 二叉树的最近公共祖先

1.问题 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是…

1992-2022年31省GDP、第一产业增加值、第二产业增加值 第三产业增加值

1992-2022年31省GDP、第一产业增加值、第二产业增加值 第三产业增加值 1、时间&#xff1a;1992-2022年 2、范围&#xff1a;包括31省 3、指标&#xff1a;省GDP、省第一产业增加值、省第二产业增加值、省第三产业增加值 4、缺失情况说明&#xff1a;无缺失 5、来源&#…

【python知识】__init__.py的来龙去脉

一、说明 我们常见__init__.py文件&#xff0c;但说不清楚它的用途&#xff0c;在本文&#xff0c;我将首先把它的来龙去脉说清楚&#xff0c;然后告诉大家&#xff0c;如何编制python工程&#xff0c;培养全局的编程格局。 二、包-模块-函数结构 在Python工程里&#xff0c;当…

playwright连接已有浏览器操作

文章目录 playwright连接已有浏览器操作前置准备打开本地已有缓存的Chrome&#xff08;理解&#xff09;指定端口打开浏览器连接指定端口已启动浏览器&#xff08;推荐&#xff09; playwright连接已有浏览器操作 前置准备 pip install playwright # 安装playwright的python…

红黑树数据结构

现在JAVASE中HashMap中底层源码是由数组链表红黑树进行设计的&#xff0c;然后很多地方也是用到红黑树&#xff0c;这里单独对红黑树数据结构进行简单的介绍。 目录 红黑树概念 红黑树的性质 自平衡规则 代码 红黑树概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;…

linux常用命令大全(保姆及入门)

linux常用命令大全 一、文件处理命令1、目录处理命令&#xff1a;ls2、目录处理命令2.1 mkdir2.2 pwd2.3 rmdir2.4 cp2.5 mv2.6 rm 3.文件处理命令3.1 touch3.2 cat3.3 tac3.4 more3.5 less3.6 head3.7 tail 4.链接命令4.1 ln 二、权限管理命令2.1 chmod2.2 chown2.3 chgrp 2.4…

VRPTW:新雀优化算法NOA求解带时间窗的车辆路径问题

一、新雀优化算法NOA求解带时间窗的车辆路径问题 1.1VRPTW模型如下&#xff1a; 带时间窗的车辆路径问题(Vehicle Routing Problem with Time Windows, VRPTW) 1.2新雀优化算法NOA求解VRPTW close all clear clc SearchAgents_no30; % 种群大小 Function_nameF1; Max_ite…

准备“开黑”,电脑却“告退”?游戏闪退的解决方法

游戏玩家近期可能会发现&#xff0c;不少大作陆陆续续登录PC市场&#xff0c;比如《死亡岛 2》、《无畏契约》等。但也有不少游戏用户会发现&#xff0c;电脑玩游戏时容易出现闪退的情况。特别是在进行高负荷运算的时候&#xff0c;有一些游戏更为容易出现这种情况&#xff0c;…

[架构之路-176]-《软考-系统分析师》-17-嵌入式系统分析与设计 -1- 实时性(任务切换时间、中断延迟时间、中断响应时间)、可靠性、功耗、体积、成本

目录 前言&#xff1a; 1 7 . 1 嵌 入 式 系 统 概 述 1 . 嵌入式系统的特点 (1) 系统专用性强。 (2) 系统实时性强。 (3) 软硬件依赖性强 (4) 处理器专用。 ( 5 ) 多种技术紧密结合。 (6) 系统透明性。 (7) 系统资源受限。 2 . 嵌入式系统的组成 1 7 . 3 嵌入式实…

拷贝构造函数和赋值重载函数详解

1.拷贝构造函数 1.1拷贝构造函数的概念 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用(一般常用const修饰)&#xff0c;在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数也是特殊的成员函数&#xff0c;其特征如下&#…

Golang每日一练(leetDay0051)

目录 151. 颠倒字符串中的单词 Reverse Words In A String &#x1f31f;&#x1f31f; 152. 乘积最大子数组 Maximum Product Sub-Array &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一…