【嵌入式环境下linux内核及驱动学习笔记-(7-内核 I/O)-多路复用】

news2024/12/26 10:55:24

目录

  • 2、多路复用
    • 2.1 函数select相关
      • 2.1.1 应用层select()
      • 2.1.2 FD_ZERO
      • 2.1.3 FD_SET
      • 2.1.4 FD_ISSET
    • 2.2 函数poll相关
      • 2.2.1 poll函数
    • 2.3 驱动层 函数
    • 2.4 实例

接上篇,继续内核 I/O的五种模式的解读。

2、多路复用

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

在这里插入图片描述

2.1 函数select相关

2.1.1 应用层select()

select函数是Linux中的一个非常重要的I/O多路复用函数。它可以同时监听多个文件描述符,一旦其中的一个文件描述符就绪(有数据 可以读、可以写、或者有except状况),它就会返回,这样程序就可以马上读取数据,或者写入数据。
select函数的原型是:

#include   <sys/select.h> 
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数解释:

  • n:监听的文件描述符最大值+1
  • readfds:待读文件描述符集合
  • writefds:待写文件描述符集合
  • exceptfds:等待异常文件描述符集合
  • timeout:超时时间,NULL表示无限等待

返回值:

  • 成功返回就绪文件描述符数

  • 超时返回0

  • 错误返回-1
    select返回后会把以前加入的但并无事件发生的fd清空
    与select相关的其他函数有:

  • FD_ZERO():清零文件描述符集合

  • FD_SET():在文件描述符集合中添加一个新的文件描述符

  • FD_CLR():从文件描述符集合中删除一个文件描述符

  • FD_ISSET():检查文件描述符集合中是否包含某个文件描述符

举个简单的例子:

int main() 
{
    int sockfd1, sockfd2; 
    fd_set rfds, wfds;
   
    // 创建文件描述符集合 
    FD_ZERO(&rfds); 
    FD_ZERO(&wfds);

    // 添加sockfd1到读就绪集合,sockfd2到写就绪集合
    FD_SET(sockfd1, &rfds);  
    FD_SET(sockfd2, &wfds);  

    // 监听文件描述符集合,超时时间为5秒 
    int ret = select(MAX_FD + 1, &rfds, &wfds, NULL, &tv);

    // 检查返回值,处理就绪的文件描述符
    if (ret > 0) {
       if (FD_ISSET(sockfd1, &rfds)) {
           // sockfd1可读,进行读取
       } 
       if (FD_ISSET(sockfd2, &wfds)) {
           // sockfd2可写,进行写入
       } 
    }
}

select()系统调用作为一个重要的I/O多路复用接口,它可以同时监听多个文件描述符,并在任何一个文件描述符就绪时立即返回,这避免了无谓的轮询和资源浪费。
它常用于服务器程序中,监听多个客户端Socket,一旦任何一个Socket就绪,即刻处理,提高程序的运行效率。

2.1.2 FD_ZERO

FD_ZERO()函数用于清空一个文件描述符集合。
文件描述符集合用来存储多个文件描述符,以方便select()等函数监听和操作这组文件描述符。文件描述符集合由fd_set类型表示,其定义如下:

typedef struct _fd_set {
    unsigned int fds_bits[FD_SETSIZE / ULONG_BIT];
} fd_set;

FD_SETSIZE是一个默认的大小,通常为1024。所以fd_set包含1024/32=32个unsigned int。
每个unsigned int都包含32个bit,代表32个文件描述符。所以一共可以存储32 * 32 = 1024个文件描述符。
当我们要监听某个文件描述符时,需要先将其添加到fd_set中,这时使用FD_SET()函数。而当不再监听某个文件描述符时,需要从fd_set中删除,这时使用FD_CLR()函数。
FD_ZERO()函数就是用来将fd_set中的所有bit设置为0,表示清空集合,不监听任何文件描述符。
所以,当我们要重新监听一组文件描述符时,最好先调用FD_ZERO()清空原来的集合,然后再调用FD_SET()添加需要监听的文件描述符。这样可以避免残留不需要监听的文件描述符。
一个例子:
c
fd_set rfds;

// 清空文件描述符集合
FD_ZERO(&rfds);

// 添加文件描述符0和1
FD_SET(0, &rfds);
FD_SET(1, &rfds);

// 使用select监听rfds
select(2, &rfds, NULL, NULL, NULL);

// 检查是否有就绪文件描述符
if (FD_ISSET(0, &rfds)) {
// 文件描述符0就绪
}
if (FD_ISSET(1, &rfds)) {
// 文件描述符1就绪
}

2.1.3 FD_SET

FD_SET()函数用于在文件描述符集合中添加一个文件描述符。
函数原型为:

void FD_SET(int fd, fd_set *set);
  • fd: 要添加的文件描述符
  • set: 文件描述符集合
    添加成功后,set中的对应bit会被设置为1,表示正在监听该文件描述符。
    例如,如果要添加文件描述符31 , set的内部结构大概如下:
元素313029282726252423222120191817161514131211109876543210
fds_bits[0]10000000000000000000000000000000

<从左到右从低位到高位, 第3个bit设置为1,代表文件描述符3
那么如果文件描述符大于32,就需要设置set的更高位,比如 文件描述符65,所以65描述符实际为第66位(因为从0开始算第一位)
66=2*32+2

元素313029282726252423222120191817161514131211109876543210
fds_bits[0]00000000000000000000000000000000
fds_bits[1]00000000000000000000000000000000
fds_bits[2]00000000000000000000000000000010


所以通过FD_SET()我们可以很方便地在文件描述符集合中添加和删除文件描述符,以便select()和其他函数进行监听和操作。
一个例子:

fd_set rfds;
int fd1 = 3, fd2 = 65;

// 清空文件描述符集合
FD_ZERO(&rfds);

// 添加文件描述符3和65
FD_SET(fd1, &rfds);
FD_SET(fd2, &rfds);

// 使用select监听rfds
select(66, &rfds, NULL, NULL, NULL);  

// 检查是否有就绪文件描述符
if (FD_ISSET(fd1, &rfds)) {
    // 文件描述符3就绪
}
if (FD_ISSET(fd2, &rfds)) {
    // 文件描述符65就绪
}

2.1.4 FD_ISSET

FD_ISSET()函数的源码在/usr/include/sys/select.h中,Linux内核版本4.14定义如下:

/* According to earlier standards */
static inline int FD_ISSET(int fd, fd_set *fdset)
{
    return (fdset->fds_bits[fd / FD_SETSIZE/ULONG_BIT] &
            (1UL << (fd % FD_SETSIZE/ULONG_BIT))) != 0;
}

这个实现比较简单,主要分两步:

  1. 计算fd对应在fdset的哪个unsigned long中,用fd / FD_SETSIZE/ULONG_BIT。
    FD_SETSIZE通常为1024,ULONG_BIT为32(32位系统下unsigned long的bit数),
    所以fd / FD_SETSIZE/ULONG_BIT的结果会是0-31,表示fdset->fds_bits的索引。
  2. 在得到的unsigned long中,计算fd对应哪一位,用fd % FD_SETSIZE/ULONG_BIT。
    然后检查那一位是否为1,使用"&"和"1UL << "操作。
    如果是1,则返回1,表示fd在fdset中;如果是0,则返回0,表示fd不在fdset中。

举个例子:

  • 检查文件描述符5:
    fd / FD_SETSIZE/ULONG_BIT = 5 / 32 = 0 // 对应fds_bits[0]
    fd % FD_SETSIZE/ULONG_BIT = 5 % 32 = 5 // 在fds_bits[0]中对应第5位
    fds_bits[0] & (1UL << 5) // 检查第5位是否为1
    如果第5位为1,则返回1,否则返回0
  • 检查文件描述符65:
    fd / FD_SETSIZE/ULONG_BIT = 65 / 32 = 2 // 对应fds_bits[2]
    fd % FD_SETSIZE/ULONG_BIT = 65 % 32 = 1 // 在fds_bits[2]中对应第1位
    fds_bits[2] & (1UL << 1) // 检查第1位是否为1
    如果第1位为1,则返回1,否则返回0
    所以这段实现代码简单地将fd映射到unsigned long的某一位上,然后检查那一位是否置1,以判断fd是否在指定的fdset中。

2.2 函数poll相关

2.2.1 poll函数

头文件

#include <poll.h>

功能
poll() 系统调用用于监视文件描述符的活动状况,它可以监视多个文件描述符,当某个文件描述符就绪时,它能够通知调用者哪个文件描述符就绪了。

poll() 的原型是:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds: 是一个结构体数组,每个元素包含一个要监视的文件描述符和监视事件。
  • nfds: 表示fds数组的大小。
  • timeout: 超时时间,以毫秒为单位。如果timeout为-1,则poll()会一直阻塞直到有描述符就绪;如果timeout为0,则poll()会立即返回,不管描述符是否就绪。

struct pollfd 结构如下:

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 监视事件 */
    short revents;    /* 返回事件 */ 
};

其中的, events 用于指定要监视的事件, - revents 返回时,设置发生的具体事件。详细见下:

详细
****在这里插入图片描述
返回值:

  • 该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;
  • 如果在超时前没有任何事件发生,poll()返回0;
  • 失败时,poll()返回-1,并设置errno为下列值之一:
    EBADF 一个或多个结构体中指定的文件描述符无效。
    EFAULTfds 指针指向的地址超出进程的地址空间。
    EINTR 请求的事件之前产生一个信号,调用可以重新发起。
    EINVALnfds 参数超出PLIMIT_NOFILE值。
    ENOMEM 可用内存不足,无法完成请求。

使用示例:

#include <poll.h>
int main() {
    struct pollfd fds[2];
    int nfds = 2;
    int timeout = 5000;  // 5s 超时

    fds[0].fd = STDIN_FILENO;    //标准输入
    fds[0].events = POLLIN;

    fds[1].fd = socket_fd;      //socket
    fds[1].events = POLLIN;

    int ret = poll(fds, nfds, timeout);
    if (ret == 0) {
        printf("poll timeout!\n");
    } else if (ret > 0) {
        if (fds[0].revents & POLLIN) {
            // 标准输入有数据可读
        } 
        if (fds[1].revents & POLLIN) {
            // socket 有数据可读
        }
    } else {
        perror("poll");
    }
}

2.3 驱动层 函数

对应于应用层的系统调用 select 、poll、epoll 这两个函数,对应驱动层的内核调用都是xxx_poll()函数,该函数的声明已在struct file_operations 结构体中定义了。

因此,驱动层的xxx_poll协助这些多路监控函数判断本设备是否有数据可读写,需要由开发者完成。这些操作实质是一些程式化的内容。具体如下:
.poll原函数原形模板:

unsigned int (*poll) (struct file *, struct poll_table_struct *);

开发者需要在这个函数内完成三件事:

static unsigned int char_poll(struct file *file, poll_table *wait)
{
   	1. 调用poll_wait() 注册一个waitqueue,以便内核可以唤醒该文件在poll()调用中睡眠的进程。
	2. 检查设备状态,设置mask来表示该文件可进行的IO事件。如可读、可写等。
	 并设置mask,判断是否可读,如可读则mask |= POLLIN | POLLRDNORM;
     判断是否可写,如可写则mask |= POLLOUT | POLLWRNORM;
	3、 返回mask值,poll()调用会根据这个mask值来判断哪些文件描述符就绪。    return mask;
}

其典型模板如下:
在这里插入图片描述

当内核需要唤醒在该设备文件上睡眠的应用进程时,会调用wake_up_poll() 来唤醒进程,这时应用进程从poll()系统调用返回,并可以进行相应的IO操作。
所以XX_poll方法为基于设备文件的poll实现提供了一机制,让设备与内核的poll机制结合起来,实现设备状态的监听与唤醒。

poll方法实现:
例如:实现一个环形缓冲区,当用户空间进程调用poll时,内核需要根据环形缓冲区的状态来唤醒进程。

首先定义环形缓冲区结构:

struct circ_buf {
    char *buf;
    int head;
    int tail;
    int size;
};

字符设备结构体:

struct char_dev {
    struct circ_buf circ_buf;
    wait_queue_head_t waitq;   // 等待队列
    bool written;
};

在open方法中申请 buffer:

static int char_open(struct inode *inode, struct file *file) 
{
    struct char_dev *dev;
    dev = kmalloc(sizeof(*dev), GFP_KERNEL);
    dev->circ_buf.buf = kmalloc(SIZE, GFP_KERNEL);
    dev->circ_buf.head = 0;
    dev->circ_buf.tail = 0;
    dev->circ_buf.size = SIZE;
    dev->written = false;
    init_waitqueue_head(&dev->waitq);
    file->private_data = dev;
    return 0;
}

写入方法中向环形缓冲区写入数据,并唤醒等待的进程:

static ssize_t char_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    struct char_dev *dev = file->private_data;
    int len = circ_buf_write(&dev->circ_buf, buf, count);
    
    dev->written = true;
    wake_up_interruptible(&dev->waitq);
    return len;  
} 

最后是关键的poll

static unsigned int char_poll(struct file *file, poll_table *wait)
{
    struct char_dev *dev = file->private_data;
    unsigned int mask = 0;

    poll_wait(file, &dev->waitq, wait);    /

    if (dev->written)  // 如果有数据写入
        mask |= POLLIN | POLLRDNORM;  // 表示可读
    
    if (!circ_buf_full(&dev->circ_buf)) // 如果接收缓冲区没满
        mask |= POLLOUT | POLLWRNORM;  // 表示可写

    return mask;
}

当用户调用poll系统调用时,内核会调用char_poll方法。char_poll函数这里调用了poll_wait(),将当前进程加入dev->waitq等待队列,并标记为可以被IO唤醒。

然后如果没有就绪事件,进程将在char_poll中睡眠。这时,如果有写入操作(这个写入操作就是上面的write函数内的wake_up_interreupible())在dev->waitq上调用了wake_up操作,内核会唤醒在该等待队列上睡眠的进程, char_poll也会返回,poll()调用也会返回。

被唤醒后,char_poll继续运行,接下来根据环形缓冲区的状态来设置mask, 如果此时 written 为true(在write函数中设置),表示有数据可读,mask被设置为POLLIN,这时应用层poll系统调用会退出阻塞直接返回。

关联函数poll_wait

poll_wait() 函数是设备驱动中实现poll方法时常用的函数。它的作用是:

  1. 将当前进程加入一个等待队列中。
  2. 标记当前进程正在睡眠,并可以被IO唤醒。
  3. 向内核注册一个唤醒源,一旦该唤醒源(等待队列)被唤醒,内核会唤醒等待队列上睡眠的进程。
    它的原型是:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) 
  • filp: 文件结构,表示当前正在调用的文件。
  • wait_address: 等待队列头,当前进程将加入这个等待队列中。
  • p: poll_table结构,该结构用于标记进程可以被IO唤醒。

当一个进程调用poll_wait()时,即表示它正在该(设备?)文件上睡眠,并可以被IO唤醒。这时内核会将其加入等待队列wait_address中,并标记为可被IO唤醒状态。

一旦有其他操作在该等待队列上调用wake_up()等唤醒函数(如上个示例中的write函数中的wake_up_interruptible(&dev->waitq);),内核会唤醒waiting队列上的睡眠进程,被唤醒的进程将从原来的睡眠态(如调用poll()进入的睡眠)中返回,这时(对应的应用层)poll()调用也会返回。

所以,通过调用poll_wait(),我们可以很方便的把进程睡眠状态与设备等待队列关联起来,这是实现设备的poll方法的基础。

源码追踪:

/include/linux/poll.h
typedef struct poll_table_struct {
	poll_queue_proc _qproc;
	unsigned long _key;
} poll_table;

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
	if (p && p->_qproc && wait_address)
		p->_qproc(filp, wait_address, p);
}
poll_wait参数poll_table是来自于系统调用->poll时传递进来的。这样,就要去找select函数如何把这个poll_table构造的。见下面这段代码

在这里插入图片描述

void poll_initwait(struct poll_wqueues *pwq)
{
	init_poll_funcptr(&pwq->pt, __pollwait);
	pwq->polling_task = current;
	pwq->triggered = 0;
	pwq->error = 0;
	pwq->table = NULL;
	pwq->inline_index = 0;
}
EXPORT_SYMBOL(poll_initwait);

这个函数把__pollwait赋给了上面提到wait这个poll_table变量,所以实际就是__pollwait才是poll_wait()函数的本体。

fs/select.c
/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
				poll_table *p)
{
	struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
	struct poll_table_entry *entry = poll_get_entry(pwq);
	if (!entry)
		return;
	entry->filp = get_file(filp);
	entry->wait_address = wait_address;
	entry->key = p->_key;
	init_waitqueue_func_entry(&entry->wait, pollwake);
	entry->wait.private = pwq;
	add_wait_queue(wait_address, &entry->wait);
}

这段代码做了以下几件事:

  1. 通过container_of()得到poll_wqueues结构体pwq,该结构体包含了poll_table以及相关等待队列信息。
  2. 调用poll_get_entry()获取一个poll_table_entry结构体entry。如果获取失败,直接返回。
  3. 设置entry->filp为当前文件filp,entry->wait_address为等待队列头wait_address,entry->key为poll_table的_key。
  4. 调用init_waitqueue_func_entry()初始化entry->wait。并设置回调函数为pollwake(),private数据为pwq。
  5. 调用add_wait_queue()将entry->wait加入到等待队列wait_address中。
  6. 此时,当前进程会在poll()的睡眠中等待唤醒。如果在超时时间内没有唤醒,会进入真正的阻塞状态。
  7. 如果wait_address等待队列被唤醒,则会调用entry->wait.func,也就是pollwake()函数。
  8. pollwake()函数会获取私有数据pwq,并标记pwq->triggered值为true,表示有就绪事件。这会导致当前进程从睡眠中唤醒,并从poll()系统调用返回。

所以,这个__pollwait()实现主要做了两件事:

  1. 将当前进程加入等待队列wait_address,同时设置回调函数pollwake()。
  2. pollwake()函数用于在有就绪事件时唤醒等待队列上的进程,它会标记pwq->triggered来通知__pollwait()已经有就绪事件,从而避免进程不必要的等待。

所以总的来说是用于在就绪事件来临 唤醒进程,以实现poll()的监听功能。

2.4 实例

/*************************************************************************
	> 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>
#include <linux/poll.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;

/*所有驱动函数声明*/
loff_t llseek (struct file *, loff_t, int);
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
ssize_t aio_read (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t aio_write (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int iterate (struct file *, struct dir_context *);
unsigned int poll (struct file *, struct poll_table_struct *);
long unlocked_ioctl (struct file *, unsigned int, unsigned long);
long compat_ioctl (struct file *, unsigned int, unsigned long);
int mmap (struct file *, struct vm_area_struct *);
int open (struct inode *, struct file *);
int flush (struct file *, fl_owner_t id);
int release (struct inode *, struct file *);
int fsync (struct file *, loff_t, loff_t, int datasync);
int aio_fsync (struct kiocb *, int datasync);
int fasync (int, struct file *, int);
int lock (struct file *, int, struct file_lock *);
ssize_t sendpage (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long get_unmapped_area(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int check_flags(int);
int flock (struct file *, int, struct file_lock *);
ssize_t splice_write(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t splice_read(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int setlease(struct file *, long, struct file_lock **);
long fallocate(struct file *file, int mode, loff_t offset,  loff_t len);
int show_fdinfo(struct seq_file *m, struct file *f);

//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
    .open = open,
    .release = release,
    .read = read,
    .write = write,
    .poll = poll,
};

/*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 , "select_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 select_memory[%d]  initalization completed\n", i);
        } else
            printk("deiver : the driver select_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 : select_memory[%d , %d] is opened by nonblock mode\n",major , minor);
    }else{
        printk("driver : select_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("select_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;

}


unsigned int poll (struct file *pf, struct poll_table_struct *  pts){
    struct mem_dev_t *p = pf->private_data;
    unsigned int mark = 0;
    poll_wait(pf, &p->read_queue , pts);
    poll_wait(pf , &p->write_queue , pts);
    //测试这里是否是阻塞还是轮询
    //printk("poll in waiting......or .....poll.");
    //
    if (p->curpos > 0){
        mark |= POLLIN | POLLRDNORM;
    }
    
    if (p->curpos < MEM_SIZE-1){
        mark |= POLLOUT | POLLWRNORM;
    }
   
    return mark;
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");

测试程序

/*************************************************************************
	> File Name: op_mem.c
 ************************************************************************/

#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

int  main(int argc , char **argv){

    int fd = 0;
    int size = 0;
    char buf[20] = {0};
    char * mesg ="this is test\n";
    int pos = 0;
    fd_set rfd; 
    int ret=0;
    int j=0;
    if  (argc < 2){
        printf("argument is  less!\n");
        return 0;
    }

    fd = open(argv[1] , O_RDWR|O_APPEND  );
    if (fd < 0){
        perror("open ");
    }
    //从设备循环读出数据
    while (1){
        j++;
        printf("while times is %d:\n" , j);
        FD_ZERO(&rfd);
        FD_SET(fd,&rfd);
        ret = select(fd+1 , &rfd,NULL,NULL,NULL);
        if (ret<0){
            if(errno == EINTR){
                continue;
            }else{
                printf("select error \n");
                break;
            }

        }else{
            if (FD_ISSET(fd,&rfd)){
                read(fd, buf, 10);
                printf("buf = %s \n", buf);
            }
        }
        memcpy(buf,"\0\0\0\0\0\0\0\0\0\0",10);

    }

    close(fd);
    return 0;

}

测试方法:
1、在一个终端窗口将驱动加载 sudo insmod select-memory.ko
2、查看主设备号 cat /proc/devices ,找到select-memory设备可以看到主设备号
3、创建设备文件 sudo mknod /dev/select0 c 主设备号 0
4、修改设备文件权限 sudo chmod 777 /dev/select0
5、加载后运行测试程序: ./op_mem.elf ,这时可以看到设试程序会阻塞在当前。
6、打开另一个终端窗口,输出 : echo “hello” > /dev/select0
7、这时在原窗口会看到有 buf = hello 字样的输出,并继续阻塞。

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

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

相关文章

常见的接口优化技巧思路

一、背景 针对老项目&#xff0c;去年做了许多降本增效的事情&#xff0c;其中发现最多的就是接口耗时过长的问题&#xff0c;就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 二、接口优化方案总结 1.批处理 批量思想&#xff1a;批量操作数据…

windows如何确认服务器上程序端口是否正常

方式1&#xff1a;ping命令 ping命令说明 ping命令是个使用频率极高的网络诊断工具&#xff0c;在Windows、Unix和Linux系统下均适用。它是TCP/IP协议的一部分&#xff0c;用于确定本地主机是否能与另一台主机交换数据报。根据返回的信息&#xff0c;我们可以推断TCP/IP参数设…

类和对象 -上(C++)

目录 认识面向过程和面向对象 类的引入 类的定义 语法&#xff1a; 类的两种定义方式&#xff1a; 成员变量命名规则建议 类的访问限定符及封装 访问限定符 C 中 class 和 struct 的区别&#xff1f; 封装 类的作用域 类的实例化 类对象模型 如何计算类对象的大小 结构体的内存…

想保护你的网站?用Python来生成验证码图片

前言 随着互联网的发展&#xff0c;我们越来越多地依赖于网站和应用程序&#xff0c;而这些网站和应用程序也面临着各种各样的安全威胁&#xff0c;其中之一就是用户可能会通过脚本攻击你的网站。为了缓解这些安全风险&#xff0c;一个常见的做法是在用户进行操作时&#xff0…

关于电信设备进网许可制度若干改革举措的通告

Q&#xff1a;3月1日后&#xff0c;不再实行进网许可管理的11种电信设备是否还需要继续申请和使用标志&#xff1f; A&#xff1a;3月1日起&#xff0c;对不再实行进网许可管理的11种电信设备停止核发进网许可标志&#xff0c;已申请的标志可在证书有效期内继续使用。 Q&#…

应用启动时aerospike客户端查询rt高原因

在应用刚起步时&#xff0c;发到预发测试或者生产小部分流量进来时&#xff0c;发现aerospike的rt特别高&#xff0c;在流量稍微大点时&#xff0c;rt恢复正常。基本可以断定客户端存在预热问题。 应用没有设置连接池配置&#xff0c;因此check下默认配置 可以看到&#xff0…

c++类 笔记(陆续更新该文档)

派生类 #include <iostream> using namespace std; class Box{private://类私有&#xff0c;只有成员可以调用 也就是说你不可以通过box1.a来调用 ,这些变量其实你默认不用写private 这个变量&#xff0c;只要放在最上面他默认就是 私有int a1;protected://protected&am…

AlgoC++:课程总结

目录 课程总结前言1. 未讲解内容2. 复习2.1 矩阵求导2.2 优化方法2.3 具体的算法 3. 未来怎么学C(必看&#xff01;&#xff01;&#xff01;) 课程总结 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课…

【嵌入式环境下linux内核及驱动学习笔记-(8-内核 I/O)-信号驱动】

目录 3 信号驱动的异步通知3.1 linux异步通知编程3.1.1 什么是信号3.1.2 信号的工作流程: 3.2. 应用层3.2.1 信号接收 signal函数3.2.2 应用层 fcntl 函数3.2.3 应用层信号驱动机制步骤 3.3 驱动层3.3.1 驱动层模板3.3.2 驱动层 实现fasync函数3.3.3 fasync_helper3.3.4 struct…

Golang-常见数据结构Slice

Slice slice 翻译成中文就是切片&#xff0c;它和数组&#xff08;array&#xff09;很类似&#xff0c;可以用下标的方式进行访问&#xff0c;如果越界&#xff0c;就会产生 panic。但是它比数组更灵活&#xff0c;可以自动地进行扩容。 了解 slice 的本质, 最简单的方法就是…

MySQL 一条SQL语句是如何执行的?

总览 ​ 所以今天我们把MySQL拆解一下&#xff0c;看看里边有哪些零件。下边是MySQL的基本架构示意图。 大体来说&#xff0c;MySQL分为Server层和存储引擎两部分。 Server 层包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&am…

小白也能懂的可转债配债价格计算

可转债配债如何计算 先给理论公式&#xff1a; 配债10张/1手所需的钱数 配债所需股数 * 当前的股价 这个公式应该很好理解&#xff0c;不需要做过多的解释。 那&#xff0c; 为什么如此简单的公式&#xff0c;还是很多人不会算&#xff0c;是因为&#xff1a; 配债所需的股数跟…

类与对象之构造函数

文章目录 导读类的6个默认构造函数构造函数概念特性 析构函数概念特性 拷贝构造函数概念特性 赋值运算符重载运算符重载赋值运算符重载前置和后置重载 导读 本文是md导入的可能格式有点乱&#xff0c;希望各位理解一下 类的6个默认构造函数 默认成员函数&#xff1a;用户没有…

五一堵车 | AI“高速”车辆检测轻而易举监测大家安全

点击蓝字关注我们 关注并星标 从此不迷路 计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 计算机视觉研究院专栏 作者&#xff1a;Edison_G 五一节不管是离开小城镇还是进入大城市&#xff0c;每个高速路口都是堵车&#xff0c;现在人工智能愈来愈发达&#xff0c…

git fetch时,FETCH_HEAD和.git\refs\remotes\origin会有哪些变化

目录 github远程仓库状态clone 到本地对新clone的仓库直接 fetchgit fetchgit fetch origingit fetch origin test1git fetch origin test2:test22 结论 github远程仓库状态 clone 到本地 git fetchgit fetch origingit fetch origin test3git fetch origin test2:test22 git f…

Photon AI Translator 和做产品的一些思考

近 4 个月内我一直在做 Apple 平台的产品&#xff0c;虽然从使用量来说「简体中文」用户是占多数&#xff0c;但我一直有做多语言的支持&#xff1a;英语、简体中文和繁体中文。习惯上 Google 翻译的我&#xff0c;基本上在使用 Xcode 过程中也会一直在浏览器开着 Google Trans…

复古决战快速施法穿墙秒怪分析流程及安全防护

《决战》是一款非常古老的RPG游戏&#xff0c;作为热血传奇同期的热门游戏也深受7080后的喜爱。 在十几年前玩这个游戏时&#xff0c;我也使用过瞬移穿墙&#xff0c;快速施法秒怪等功能的辅助。 下面我们就用一个自己架设的单机版来回顾一下当年辅助开发的流程&#xff0c;并…

【三十天精通Vue 3】 第二十二天 Vue 3的UI框架详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、常用的Vue 3 UI框架概览1. 常用的Vue 3 UI框架有哪些&…

不同主题增删改查系统【控制台+MySQL】(Java课设)

有很多顾客都是只要实现各种各样的增删改查系统即可&#xff0c;只是主题和数据库表不一样&#xff0c;功能都是增删改查这四个功能&#xff0c;做出来的效果和下面的截图是一样的&#xff0c;后续这样的增删改查系统的运行效果请参考下面的截图&#xff0c;我就不一一演示了&a…

OSPF-MGRE综合实验

拓扑结构&#xff1a; 要求&#xff1a; 1、R6为ISP只能配置IP地址&#xff0c;R1~R5的环回为私有网段 2、R1/4/5为全连的MGRE结构&#xff0c;R1/2/3为星型的拓扑结构&#xff0c;R1为中心站点 3、所有私有网段可以互相通讯&#xff0c;私有网段使用OSPF协议完成 使用的设备…