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


接上篇,继续内核 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 字样的输出,并继续阻塞。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑牛唱剧本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值