Linux驱动程序——带缓冲区的简单虚拟设备

一个简单的带缓冲区虚拟设备

入下图所示,一个简单的虚拟设备只有一个缓冲区或者FIFO的部件,实现了一个先进先出的缓冲区。用户程序可以通过wirte()函数把用户数据写入这个虚拟设备的FIFO中,还可以通过read()函数把虚拟设备上的FIFO数据读出到用户空间的缓冲区里面。

在这里插入图片描述

代码文件(simple_virtual_dev/simple_virtualchardev.c)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>

#define DEMO_NAME "simple_virtualchardev"
static struct device *simple_virtualchardev;
#define MAX_DEVICE_BUFFER_SIZE 64
static char device_buffer[MAX_DEVICE_BUFFER_SIZE] = {0};

static int simpledev_open(struct inode *inode, struct file *file)
{
    int major = MAJOR(inode->i_rdev);
    int minor = MINOR(inode->i_rdev);

    printk("%s: major = %d, minor = %d\n", __func__, major, minor);

    return 0;
}

static int simpledev_release(struct inode *inode, struct file *file)
{
    return 0;
}

/**
 * @brief 设备读
 * 
 * @param file 表示打开的设备文件
 * @param buf 用户空间的内存起始地址,使用__user来提醒
 * @param count 表示用户想读取多少字节的数据
 * @param ppos 表示文件的位置指针
 * @return ssize_t 表示最终读到的字节数
 */
static ssize_t simpledev_read(struct file *file, char __user* buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    int actual_readed;
    int max_free;
    int need_read;
    int ret;

    max_free = MAX_DEVICE_BUFFER_SIZE - *ppos;  // 表示当前设备的FIFO还剩下多少空间
    need_read = max_free > count ? count : max_free;
    if(need_read == 0) {
        dev_warn(simple_virtualchardev, "no space for read");
    }

    ret = copy_to_user(buf, device_buffer + *ppos, need_read);
    // 复制到用户进程空间,返回0表示成功,need_read表示复制失败
    if (ret == need_read) {
        return -EFAULT;
    }

    actual_readed = need_read-ret;
    *ppos += actual_readed;

    printk("%s, actual_readed = %d, pos = %lld\n", __func__, actual_readed, *ppos);
    return actual_readed;
}

static ssize_t simpledev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    int actual_writed;
    int free;
    int need_write;
    int ret;

    free = MAX_DEVICE_BUFFER_SIZE - *ppos;
    need_write = free > count ? count : free;
    if(need_write == 0) {
        dev_warn(simple_virtualchardev, "no space for write");
    }

    ret = copy_from_user(device_buffer + *ppos, buf, need_write);
    if (ret == need_write) {
        return -EFAULT;
    }

    actual_writed = need_write - ret;
    *ppos += actual_writed;
    printk("%s: actual_wited = %d, ppos = %lld\n", __func__, actual_writed, *ppos);
    return actual_writed;
}

static const struct file_operations simpledev_fops = {
    .owner = THIS_MODULE,
    .open = simpledev_open,
    .release = simpledev_release,
    .read = simpledev_read,
    .write = simpledev_write
};

static struct miscdevice simple_misc_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEMO_NAME,
    .fops = &simpledev_fops,
};

static int __init simple_char_init(void)
{
    int ret;

    ret = misc_register(&simple_misc_device);
    if (ret) {
        printk("failed to allocate misc char device region");
        return ret;
    }

    simple_virtualchardev = simple_misc_device.this_device;
    printk("Major number = %d, minor number = %d\n", MAJOR(simple_virtualchardev->devt), MINOR(simple_virtualchardev->devt));
    return 0;
}

static void __exit simple_char_exit(void)
{
    printk("removing device\n");

    misc_deregister(&simple_misc_device);
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_AUTHOR("marvin");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple vitual character device");

makefile文件:

ifneq ($(KERNELRELEASE),)
MODULE_NAME = simplevirtualchardev
$(MODULE_NAME)-objs := simple_virtualchardev.o
obj-m := $(MODULE_NAME).o
else
KERNEL_DIR = /lib/modules/`uname -r`/build
MODULEDIR := $(shell pwd)
 
.PHONY: modules
default: modules
 
modules:
	make -C $(KERNEL_DIR) M=$(MODULEDIR) modules
 
clean distclean:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
	rm -f *.o *.mod.c .*.*.cmd *.ko
	rm -rf .tmp_versions
endif

测试代码文件(simple_virtual_dev/test.c)

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEMO_DEV_NAME "/dev/simple_virtualchardev"

int main()
{
    char buffer[64];
    int fd;
    int ret;
    size_t len;
    char message[] = "test the virtual char device";
    char *read_buffer;

    len = sizeof(message);

    fd = open(DEMO_DEV_NAME, O_RDWR);
    if (fd < 0) {
        printf("open device %s failed\n", DEMO_DEV_NAME);
        return -1;
    }

    // write message to device
    ret = write(fd, message, len);
    if(ret != len) {
        printf("cannot write on device %d, ret = %d", fd, ret);
        return -1;
    }

    read_buffer = (char *)malloc(2 * len);
    memset(read_buffer, 0, 2 * len);

    // close fd, and reopen;
    close(fd);

    fd = open(DEMO_DEV_NAME, O_RDWR);
    if (fd < 0) {
        printf("open device %s failed\n", DEMO_DEV_NAME);
        return -1;
    }

    ret = read(fd, read_buffer, 2 * len);
    printf("read %d bytes\n", ret);
    printf("read buffer = %s\n", read_buffer);

    close(fd);

    return 0;
}

测试程序主要流程就是打开设备,向设备里面写入字符串,之后重新打开这个设备,读取字符串。
测试结果:

$ sudo ./test 
read 58 bytes
read buffer = test the virtual char device

内核日志输出:

[ 5410.390950] Major number = 10, minor number = 122
[ 5412.946673] simpledev_open: major = 10, minor = 122
[ 5412.946680] simpledev_write enter
[ 5412.946681] simpledev_write: actual_wited = 29, ppos = 29
[ 5412.946694] simpledev_open: major = 10, minor = 122
[ 5412.946696] simpledev_read enter
[ 5412.946697] simpledev_read, actual_readed = 58, pos = 58

使用KFIFO

上面的设备驱动没有考虑到读和写的并行管理问题,所以需要重新打开设备才能将数据读出来。
Linux内核实现了一个称为KFIFO的环形缓冲区的机制,它可以在一个读者线程和一个写者线程并发执行的场景下,无需使用额外的锁来保证环形缓冲区的数据安全。

/**
 * DEFINE_KFIFO - macro to define and initialize a fifo
 * @fifo: name of the declared fifo datatype
 * @type: type of the fifo elements
 * @size: the number of elements in the fifo, this must be a power of 2
 *
 * Note: the macro can be used for global and local fifo data type variables.
 */
#define DEFINE_KFIFO(fifo, type, size)

/**
 * kfifo_from_user - puts some data from user space into the fifo
 * @fifo: address of the fifo to be used
 * @from: pointer to the data to be added
 * @len: the length of the data to be added
 * @copied: pointer to output variable to store the number of copied bytes
 *
 * This macro copies at most @len bytes from the @from into the
 * fifo, depending of the available space and returns -EFAULT/0.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these macro.
 */
#define	kfifo_from_user(fifo, from, len, copied)

/**
 * kfifo_to_user - copies data from the fifo into user space
 * @fifo: address of the fifo to be used
 * @to: where the data must be copied
 * @len: the size of the destination buffer
 * @copied: pointer to output variable to store the number of copied bytes
 *
 * This macro copies at most @len bytes from the fifo into the
 * @to buffer and returns -EFAULT/0.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these macro.
 */
#define	kfifo_to_user(fifo, to, len, copied)

DEFINE_KFIFO用来初始化一个环形缓冲区,其中参数fifo表示环形缓冲区的名字;type表示缓冲区中数据的类型;size表示缓冲区有多少个元素,元素的个数必须是2的整数次幂。
kfifo_from_user用来将用户空间的数据写入环形缓冲区中,其中参数fifo表示使用哪个环形缓冲区;from表示用户空间缓冲区的起始地址;len表示要复制多少个元素;copied表示成功复制的元素的数量。
kfifo_to_user用来读出环形缓冲区的数据并复制到用户空间中,参数作用和kfifo_from_user类似

使用kfifo机制重写上面的驱动:
代码文件:simple_virtual_dev_kfifo/simple_virtualchardev.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>

#define DEMO_NAME "simple_virtualchardev"
static struct device *simple_virtualchardev;
#define MAX_DEVICE_BUFFER_SIZE 64
DEFINE_KFIFO(demo_fifo, char, 64);

static int simpledev_open(struct inode *inode, struct file *file)
{
    int major = MAJOR(inode->i_rdev);
    int minor = MINOR(inode->i_rdev);

    printk("%s: major = %d, minor = %d\n", __func__, major, minor);

    return 0;
}

static int simpledev_release(struct inode *inode, struct file *file)
{
    return 0;
}

/**
 * @brief 设备读
 * 
 * @param file 表示打开的设备文件
 * @param buf 用户空间的内存起始地址,使用__user来提醒
 * @param count 表示用户想读取多少字节的数据
 * @param ppos 表示文件的位置指针
 * @return ssize_t 表示最终读到的字节数
 */
static ssize_t simpledev_read(struct file *file, char __user* buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    int actual_readed;
    int ret;

    ret = kfifo_to_user(&demo_fifo, buf, count, &actual_readed);
    if (ret) {
        return -EIO;
    }

    printk("%s, actual_readed = %d, pos = %lld\n", __func__, actual_readed, *ppos);
    return actual_readed;
}

static ssize_t simpledev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    unsigned int actual_writed;
    int ret;

    ret = kfifo_from_user(&demo_fifo, buf, count, &actual_writed);
    if (ret) {
        return -EIO;
    }

    printk("%s: actual_wited = %d, ppos = %lld\n", __func__, actual_writed, *ppos);
    return actual_writed;
}

static const struct file_operations simpledev_fops = {
    .owner = THIS_MODULE,
    .open = simpledev_open,
    .release = simpledev_release,
    .read = simpledev_read,
    .write = simpledev_write
};

static struct miscdevice simple_misc_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEMO_NAME,
    .fops = &simpledev_fops,
};

static int __init simple_char_init(void)
{
    int ret;

    ret = misc_register(&simple_misc_device);
    if (ret) {
        printk("failed to allocate misc char device region");
        return ret;
    }

    simple_virtualchardev = simple_misc_device.this_device;
    printk("Major number = %d, minor number = %d\n", MAJOR(simple_virtualchardev->devt), MINOR(simple_virtualchardev->devt));
    return 0;
}

static void __exit simple_char_exit(void)
{
    printk("removing device\n");

    misc_deregister(&simple_misc_device);
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_AUTHOR("marvin");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple vitual character device");

同时将测试程序中重新打开设备的逻辑去掉

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEMO_DEV_NAME "/dev/simple_virtualchardev"

int main()
{
    char buffer[64];
    int fd;
    int ret;
    size_t len;
    char message[] = "test the virtual char device";
    char *read_buffer;

    len = sizeof(message);

    fd = open(DEMO_DEV_NAME, O_RDWR);
    if (fd < 0) {
        printf("open device %s failed\n", DEMO_DEV_NAME);
        return -1;
    }

    // write message to device
    ret = write(fd, message, len);
    if(ret != len) {
        printf("cannot write on device %d, ret = %d", fd, ret);
        return -1;
    }

    read_buffer = (char *)malloc(2 * len);
    memset(read_buffer, 0, 2 * len);

    // close fd, and reopen;
    // close(fd);

    // fd = open(DEMO_DEV_NAME, O_RDWR);
    // if (fd < 0) {
    //     printf("open device %s failed\n", DEMO_DEV_NAME);
    //     return -1;
    // }

    ret = read(fd, read_buffer, 2 * len);
    printf("read %d bytes\n", ret);
    printf("read buffer = %s\n", read_buffer);

    close(fd);

    return 0;
}

运行结果:

$ sudo ./test 
read 29 bytes
read buffer = test the virtual char device

内核输出日志:

[ 7664.852273] Major number = 10, minor number = 122
[ 7671.642176] simpledev_open: major = 10, minor = 122
[ 7671.642180] simpledev_write enter
[ 7671.642181] simpledev_write: actual_wited = 29, ppos = 0
[ 7671.642189] simpledev_read enter
[ 7671.642190] simpledev_read, actual_readed = 29, pos = 0

非阻塞模式

open函数有一个flag参数,通过其可以指定文件打开的属性,其中O_NONBLOCK标志位设置访问文件的方式为非阻塞模式。
下面将上面的驱动增加支持非阻塞模式,
代码:simple_virtual_dev_kfifo_nonblock/simple_virtualchardev.c
主要修订:

static ssize_t simpledev_read(struct file *file, char __user* buf, size_t count, loff_t *ppos)
{
    ...
    if (kfifo_is_empty(&demo_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }
    }
    ...
}

static ssize_t simpledev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    ...
    if (kfifo_is_full(&demo_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }
    }
    ...
}

测试程序如下,为了获得缓冲区满的效果,测试程序里面的消息大小设置和驱动缓冲区大小一致。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define DEMO_DEV_NAME "/dev/simple_virtualchardev"

int main()
{
    char buffer[64];
    int fd;
    int ret;
    size_t len;
    char message[] = "test the virtual char fifo device, test the virtual char fifo .";
    char *read_buffer;

    len = sizeof(message);

    fd = open(DEMO_DEV_NAME, O_RDWR | O_NONBLOCK);
    if (fd < 0) {
        printf("open device %s failed\n", DEMO_DEV_NAME);
        return -1;
    }

    read_buffer = (char *)malloc(2 * len);
    memset(read_buffer, 0, 2 * len);

    // first read
    ret = read(fd, read_buffer, 2*len);
    printf("read %d bytes\n", ret);
    printf("read buffer = %s\n", read_buffer);

    // first write
    ret = write(fd, message, len);
    if(ret != len) {
        printf("cannot write on device %d, ret = %d\n", fd, ret);
    }

    // write again
    ret = write(fd, message, len);
    if(ret != len) {
        printf("cannot write on device %d, ret = %d\n", fd, ret);
    }

    // read again
    ret = read(fd, read_buffer, 2 * len);
    printf("read %d bytes\n", ret);
    printf("read buffer = %s\n", read_buffer);

    close(fd);

    return 0;
}

测试结果

$ sudo ./test                        
read -1 bytes
read buffer = 
cannot write on device 3, ret = -1
read 64 bytes
read buffer = test the virtual char fifo device, test the virtual char fifo .

驱动输出:

[114065.704017] Major number = 10, minor number = 119
[114071.041270] simpledev_open: major = 10, minor = 119
[114071.041302] simpledev_read enter
[114071.041318] simpledev_write enter
[114071.041319] simpledev_write: actual_wited = 64, ppos = 0
[114071.041320] simpledev_write enter
[114071.041321] simpledev_read enter
[114071.041322] simpledev_read, actual_readed = 64, pos = 0

从结果可以看出,第一次读取因为缓冲区为空,返回-1;第二次写入因为缓冲区满,返回-1。

阻塞模式

阻塞模式下,当请求数据无法立刻满足时,让该进程睡眠直到数据准备好为止。
把一个进程设置成睡眠状态,就是把这个进程从TASK_RUNNING状态设置为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE状态,并且从进程调度器的运行队列中移走,我们称这个点为“睡眠点”。当请求的资源或者数据到达时,进程会被唤醒,然后从睡眠点重新执行。
在Linux内核中,采用等待队列(wait queue)的机制来实现进程阻塞操作。

  • 等待队列数据结构include/linux/wait.h
    • wait_queue_head_t
struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
};
typedef struct wait_queue_head wait_queue_head_t;
  • wait_queue_entry_t
typedef struct wait_queue_entry wait_queue_entry_t;

struct wait_queue_entry {
	unsigned int		flags;
	void			*private;
	wait_queue_func_t	func;
	struct list_head	entry;
};
  • 睡眠等待
    Linux 内核提供了简单的睡眠方式,并封装成wait_event的宏和其他几个扩展宏,主要功能是在让进程睡眠时也检查进程的唤醒条件。
wait_event(wq_head, condition)
wait_event_interruptible(wq_head, condition)
wait_event_timeout(wq_head, condition, timeout)
wait_event_interruptible_timeout(wq_head, condition, timeout)

wq_head表示等待队头。condition是一个布尔表达式,在condition为真之前,进程会保持睡眠状态。timeout表示当timeout时间到达之后,进程会被唤醒,因此它只会等待限定的时间。当给定的时间到了之后,wait_event_timeout和wait_event_interruptible_timeout这两个宏无论condition是否为真,都会返回0。
wait_event_interruptible会让进程进入可中断睡眠状态,而wait_event会让进程进入不可中断睡眠状态,也就是说不受干扰,对信号不做任何反应,不可能发送SIGKILL信号使它停止,因为它不响应信号。因此,一般驱动程序不会采用这种睡眠模式。

  • 唤醒
wake_up(x)
wake_up_interruptible(x)

wake_up会唤醒等待队列中所有的进程。wake_up应该和wait_event或者wait_event_timeout配对使用,而wake_up_interruptible应该和wait_event_interruptible和wait_event_interruptible_timeout配对使用。

修订后的驱动代码:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/wait.h>

#define DEMO_NAME "simple_virtualchardev"
static struct device *simple_virtualchardev;
#define MAX_DEVICE_BUFFER_SIZE 64
DEFINE_KFIFO(demo_fifo, char, 64);

struct simple_char_device {
    const char *name;
    struct miscdevice *mscdev;
    wait_queue_head_t read_queue;
    wait_queue_head_t write_queue;
};
static struct simple_char_device *device;

static int simpledev_open(struct inode *inode, struct file *file)
{
    int major = MAJOR(inode->i_rdev);
    int minor = MINOR(inode->i_rdev);

    printk("%s: major = %d, minor = %d\n", __func__, major, minor);

    return 0;
}

static int simpledev_release(struct inode *inode, struct file *file)
{
    return 0;
}

/**
 * @brief 设备读
 * 
 * @param file 表示打开的设备文件
 * @param buf 用户空间的内存起始地址,使用__user来提醒
 * @param count 表示用户想读取多少字节的数据
 * @param ppos 表示文件的位置指针
 * @return ssize_t 表示最终读到的字节数
 */
static ssize_t simpledev_read(struct file *file, char __user* buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    int actual_readed;
    int ret;

    if (kfifo_is_empty(&demo_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }
        printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
        ret = wait_event_interruptible(device->read_queue, !kfifo_is_empty(&demo_fifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_to_user(&demo_fifo, buf, count, &actual_readed);
    if (ret) {
        return -EIO;
    }

    if(!kfifo_is_full(&demo_fifo)) {
        wake_up_interruptible(&device->write_queue);
    }

    printk("%s, pid=%d, actual_readed = %d, pos = %lld\n", __func__, current->pid, actual_readed, *ppos);
    return actual_readed;
}

static ssize_t simpledev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    unsigned int actual_writed;
    int ret;

    if (kfifo_is_full(&demo_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }

        printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
        ret = wait_event_interruptible(device->write_queue, !kfifo_is_full(&demo_fifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_from_user(&demo_fifo, buf, count, &actual_writed);
    if (ret) {
        return -EIO;
    }

    if (!kfifo_is_full(&demo_fifo)) {
        wake_up_interruptible(&device->read_queue);
    }

    printk("%s: pid=%d, actual_wited = %d, ppos = %lld\n", __func__, current->pid, actual_writed, *ppos);
    return actual_writed;
}

static const struct file_operations simpledev_fops = {
    .owner = THIS_MODULE,
    .open = simpledev_open,
    .release = simpledev_release,
    .read = simpledev_read,
    .write = simpledev_write
};

static int __init simple_char_init(void)
{
    int ret;

    device = kmalloc(sizeof(struct simple_char_device), GFP_KERNEL);
    if (!device) {
        return -ENOMEM;
    }
    device->name = DEMO_NAME;
    device->mscdev = kmalloc(sizeof(struct miscdevice), GFP_KERNEL);
    if (!device->mscdev) {
        return -ENOMEM;
    }
    device->mscdev->minor = MISC_DYNAMIC_MINOR;
    device->mscdev->name = DEMO_NAME;
    device->mscdev->fops = &simpledev_fops;
    ret = misc_register(device->mscdev);
    if (ret) {
        printk("failed to allocate misc char device region");
        return ret;
    }

    simple_virtualchardev = device->mscdev->this_device;
    printk("Major number = %d, minor number = %d\n", MAJOR(simple_virtualchardev->devt), MINOR(simple_virtualchardev->devt));

    init_waitqueue_head(&device->read_queue);
    init_waitqueue_head(&device->write_queue);

    return 0;
}

static void __exit simple_char_exit(void)
{
    printk("removing device\n");

    misc_deregister(device->mscdev);
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_AUTHOR("marvin");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple vitual character device");

主要改动:

  • 定义两个等待队列,分别表示读操作和写操作的等待队列
  • 在读函数中,当缓冲区为空时,调用wait_event_interruptible让用户进程进入睡眠状态,唤醒的条件为缓冲区不为空
  • 当数据从缓冲区读到用户空间后,调用wait_event_interruptible唤醒写等待队列中睡眠等待的进程
  • 在写函数中,当缓冲区满时,调用wait_event_interruptible让用户进程进入睡眠状态,唤醒条件为缓冲区不满
  • 当数据从用户空间写入缓冲区后,调用wait_event_interruptible唤醒读等待队列中睡眠等待的进程

实验验证:

  1. cat打开设备
# cat /dev/simple_virtualchardev

驱动日志:

[199617.461666] simpledev_open: major = 10, minor = 119
[199617.461687] simpledev_read enter
[199617.461688] simpledev_read: pid=96736, going to sleep

从日志可以看出,因为缓冲区没有数据,读进程(96736)会进入睡眠

  1. 打开其他终端,echo写入数据
# echo "simple test">/dev/simple_virtualchardev 

此时会正常写入并唤醒读进程

[199649.198814] simpledev_open: major = 10, minor = 119
[199649.198856] simpledev_write enter
[199649.198876] simpledev_write: pid=96750, actual_wited = 12, ppos = 0
[199649.199031] simpledev_read, pid=96736, actual_readed = 12, pos = 0
[199649.199078] simpledev_read enter
[199649.199082] simpledev_read: pid=96736, going to sleep

之前的cat进程随即打印出内容:simple test

从日志可以看出,由于缓冲区非满,写进程(96750)正常写入。然后,写进程立马唤醒了读进程,读进程把刚刚写入的数据读取出来,此时缓冲区被清空,读进程有进入了睡眠状态。

I/O多路复用

Linux 内核提供poll、select和epoll这3种I/O多路复用机制。I/O多路复用可以理解为一个进程可以同时监控多个打开的文件描述符,一旦某个文件描述符就绪,就立即通知程序进行相应的读写操作。
因此,多路复用机制经常应用在那些需要使用多个输入或输出数据流而不会阻塞在其中一个数据流的应用中。

poll系统调用API如下:

struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll函数的第一个参数fds是要监听的文件描述符集合,第二个参数nfds是要监听的文件描述符的个数,第三个参数timeout是超时ms,负数表示一直监听,直到被监听的文件描述符集合中有设备发生了事件。

linux内核中,在file_operations中提供了poll方法的实现:

struct file_operations {
	...
	__poll_t (*poll) (struct file *, struct poll_table_struct *);
    ...
}

当用户程序打开设备文件后执行poll或者select系统调用时,驱动程序的poll方法就会被调用。设备驱动程序的poll方法执行入校步骤:

  • 在一个或者多个等待队列中调用poll_wait函数。poll_wait会把当前进程添加到指定的等待列表(poll_table)中,当请求数据准备好之后,会唤醒这些睡眠的进程。
  • 返回监听事件,也就是POLLIN或者POLLOUT等掩码
    因此,poll方法的作用就是让应用程序同时等待多个数据流。

接着,为上面的驱动程序添加I/O多路复用支持:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/poll.h>

#define DEMO_NAME "simple_virtualchardev"
static struct device *simple_virtualchardev;
#define MAX_DEVICE_BUFFER_SIZE 64
/* 最多支持8个设备 */
#define MAX_SIMPLE_DEVICES 8

/**
 * @brief 虚拟设备抽象
 * 
 */
struct simple_char_device {
    const char *name;
    struct miscdevice *mscdev;
    wait_queue_head_t read_queue;
    wait_queue_head_t write_queue;
    struct kfifo simple_fifo;
};

/**
 * @brief 驱动私用数据
 * 
 */
struct simple_device_private_data {
    struct simple_char_device *device;
    char name[64];
};

static struct simple_char_device *simpe_dev[MAX_SIMPLE_DEVICES];

static int simpledev_open(struct inode *inode, struct file *file)
{
    /* 通过次设备号找到对应的device数据结构 */
    unsigned int minor = iminor(inode);
    printk("device index = %d\n", minor);
    int i;
    for (i = 0; i < MAX_SIMPLE_DEVICES; i++) {
        if (MINOR(simpe_dev[i]->mscdev->this_device->devt) == minor) {
            break;
        }
    }
    if (i == MAX_SIMPLE_DEVICES) {
        printk("unknown device\n");
        return -EINVAL;
    }
	struct simple_device_private_data *data;
	struct simple_char_device *device = simpe_dev[i];

	printk("%s: major=%d, minor=%d, device=%s\n", __func__, 
			MAJOR(inode->i_rdev), MINOR(inode->i_rdev), device->name);

	data = kmalloc(sizeof(struct simple_device_private_data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	sprintf(data->name, "private_data_%d", minor);

	data->device = device;
    /* 添加私有数据到file */
	file->private_data = data;

    return 0;
}

static int simpledev_release(struct inode *inode, struct file *file)
{
    struct simple_device_private_data *data = file->private_data;
	
	kfree(data);
    return 0;
}

/**
 * @brief 设备读
 * 
 * @param file 表示打开的设备文件
 * @param buf 用户空间的内存起始地址,使用__user来提醒
 * @param count 表示用户想读取多少字节的数据
 * @param ppos 表示文件的位置指针
 * @return ssize_t 表示最终读到的字节数
 */
static ssize_t simpledev_read(struct file *file, char __user* buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
    int actual_readed;
    int ret;

    if (kfifo_is_empty(&device->simple_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }
        printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
        ret = wait_event_interruptible(device->read_queue, !kfifo_is_empty(&device->simple_fifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_to_user(&device->simple_fifo, buf, count, &actual_readed);
    if (ret) {
        return -EIO;
    }

    if(!kfifo_is_full(&device->simple_fifo)) {
        wake_up_interruptible(&device->write_queue);
    }

    printk("%s, pid=%d, actual_readed = %d, pos = %lld\n", __func__, current->pid, actual_readed, *ppos);
    return actual_readed;
}

static ssize_t simpledev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
    unsigned int actual_writed;
    int ret;

    if (kfifo_is_full(&device->simple_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }

        printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
        ret = wait_event_interruptible(device->write_queue, !kfifo_is_full(&device->simple_fifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_from_user(&device->simple_fifo, buf, count, &actual_writed);
    if (ret) {
        return -EIO;
    }

    if (!kfifo_is_full(&device->simple_fifo)) {
        wake_up_interruptible(&device->read_queue);
    }

    printk("%s: pid=%d, actual_wited = %d, ppos = %lld\n", __func__, current->pid, actual_writed, *ppos);
    return actual_writed;
}

static unsigned int simpledev_poll(struct file *file, poll_table *wait)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
	int mask = 0;

	poll_wait(file, &device->read_queue, wait);
    poll_wait(file, &device->write_queue, wait);

	if (!kfifo_is_empty(&device->simple_fifo))
		mask |= POLLIN | POLLRDNORM;
	if (!kfifo_is_full(&device->simple_fifo))
		mask |= POLLOUT | POLLWRNORM;
	
	return mask;
}

static const struct file_operations simpledev_fops = {
    .owner = THIS_MODULE,
    .open = simpledev_open,
    .release = simpledev_release,
    .read = simpledev_read,
    .write = simpledev_write,
    .poll = simpledev_poll
};

static int __init simple_char_init(void)
{
    int ret;
    int i;
    struct simple_char_device *dev;
    for (i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        dev = kmalloc(sizeof(struct simple_char_device), GFP_KERNEL);
        if (!dev) {
            ret = -ENOMEM;
            goto free_device;
        }
        dev->name = kmalloc(64, GFP_KERNEL);
        if (!dev->name) {
            ret = -ENOMEM;
            goto free_device;
        }
        sprintf(dev->name, "%s%d", DEMO_NAME, i);
        dev->mscdev = kmalloc(sizeof(struct miscdevice), GFP_KERNEL);
        if (!dev->mscdev) {
            ret = -ENOMEM;
            goto free_device;
        }
        dev->mscdev->minor = MISC_DYNAMIC_MINOR;
        dev->mscdev->name = dev->name;
        dev->mscdev->fops = &simpledev_fops;
        ret = misc_register(dev->mscdev);
        if (ret) {
            printk("failed to allocate misc char device region");
            goto free_device;
        }
        simple_virtualchardev = dev->mscdev->this_device;
        printk("Major number = %d, minor number = %d\n", MAJOR(simple_virtualchardev->devt), MINOR(simple_virtualchardev->devt));
        simpe_dev[i] = dev;

        init_waitqueue_head(&dev->read_queue);
        init_waitqueue_head(&dev->write_queue);
        ret = kfifo_alloc(&dev->simple_fifo, MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
        if (ret) {
            ret = -ENOMEM;
            goto free_kfifo;
        }
        printk("simple_fifo=%p\n", &dev->simple_fifo);
    }

    return 0;

free_kfifo:
    for (i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        dev = simpe_dev[i];
        if (&dev->simple_fifo) {
            kfifo_free(&dev->simple_fifo);
        }
    }
free_device:
    for (i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        if (simpe_dev[i]) {
            kfifo_free(&simpe_dev[i]->simple_fifo);
        }
    }

    return ret;
}

static void __exit simple_char_exit(void)
{
    printk("removing device\n");
    for (int i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        if (simpe_dev[i]) {
            misc_deregister(simpe_dev[i]->mscdev);
        }
    }
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_AUTHOR("marvin");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple vitual character device");

测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
    int ret;
    struct pollfd fds[2];
    char buffer0[64];
    char buffer1[64];

    fds[0].fd = open("/dev/simple_virtualchardev0", O_RDWR);
    if (fds[0].fd == -1) {
        goto fail;
    }
    fds[0].events = POLLIN;
    fds[0].revents = 0;

    fds[1].fd = open("/dev/simple_virtualchardev1", O_RDWR);
    if (fds[0].fd == -1) {
        goto fail;
    }
    fds[1].events = POLLIN;
    fds[1].revents = 0;

    while(1) {
        ret = poll(fds, 2, -1);
        if (ret == -1) {
            goto fail;
        }

        if (fds[0].revents & POLLIN) {
            ret = read(fds[0].fd, buffer0, 64);
            if (ret < 0) {
                goto fail;
            }
            printf("%s\n", buffer0);
        }

        if (fds[1].revents & POLLIN) {
            ret = read(fds[1].fd, buffer1, 64);
            if (ret < 0) {
                goto fail;
            }

            printf("%s\n", buffer1);
        }
    }

fail:
    perror("poll test\n");
    exit(EXIT_FAILURE);
}

测试结果:
运行测试程序,此时驱动缓冲区为空,程序poll循环等待

$ sudo ./test                        

此时内核输出

[  276.090979] device index = 119
[  276.090982] simpledev_open: major=10, minor=119, device=simple_virtualchardev0
[  276.090986] device index = 118
[  276.090987] simpledev_open: major=10, minor=118, device=simple_virtualchardev1
[  276.090989] simpledev_poll enter
[  276.090989] simpledev_poll enter

此时往设备写入数据:

# echo "demo0" > /dev/simple_virtualchardev0
# echo "demo2" > /dev/simple_virtualchardev1

此时测试程序输出写入字符串内容。
此时内核输出:

[  315.745110] device index = 119
[  315.745121] simpledev_open: major=10, minor=119, device=simple_virtualchardev0
[  315.745158] simpledev_write enter
[  315.745177] simpledev_write: pid=4239, actual_wited = 6, ppos = 0
[  315.745300] simpledev_poll enter
[  315.745309] simpledev_poll enter
[  315.745330] simpledev_read enter
[  315.745335] simpledev_read, pid=4233, actual_readed = 6, pos = 0
[  315.745607] simpledev_poll enter
[  315.745613] simpledev_poll enter
[  325.380068] device index = 118
[  325.380079] simpledev_open: major=10, minor=118, device=simple_virtualchardev1
[  325.380108] simpledev_write enter
[  325.380133] simpledev_write: pid=4239, actual_wited = 6, ppos = 0
[  325.380226] simpledev_poll enter
[  325.380234] simpledev_poll enter
[  325.380250] simpledev_read enter
[  325.380255] simpledev_read, pid=4233, actual_readed = 6, pos = 0
[  325.380290] simpledev_poll enter
[  325.380313] simpledev_poll enter

异步通知

异步通知类似中断,当请求的设备资源可以获取时,由驱动程序主动通知应用程序,应用程序调用read或者write函数来发起I/O操作。异步通知不会造成阻塞,只有设备驱动满足条件之后才通过信号机制通知应用程序去发起I/O操作。
异步通知使用了系统调用的signal函数和sigaction函数,signal函数让一个信号和一个函数对应,每当接收到这个信号时会调用相应的函数来处理。

异步通知在内核中使用的fasync_struct数据结构(include/linux/fs.h):

struct fasync_struct {
	rwlock_t		fa_lock;
	int			magic;
	int			fa_fd;
	struct fasync_struct	*fa_next; /* singly linked list */
	struct file		*fa_file;
	struct rcu_head		fa_rcu;
};

通过fasync_helper函数,构造fasync_struct类型的节点并添加到系统链表中:

/*
 * fasync_helper() is used by almost all character device drivers
 * to set up the fasync queue, and for regular files by the file
 * lease code. It returns negative on error, 0 if it did no changes
 * and positive if it added/deleted the entry.
 */
extern int fasync_helper(int, struct file *, int, struct fasync_struct **);

通过kill_fasync接口发送信号给用户程序:

extern void kill_fasync(struct fasync_struct **, int, int);

接着,修订驱动程序使支持fasync:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/poll.h>

#define DEMO_NAME "simple_virtualchardev"
static struct device *simple_virtualchardev;
#define MAX_DEVICE_BUFFER_SIZE 64
/* 最多支持8个设备 */
#define MAX_SIMPLE_DEVICES 8

/**
 * @brief 虚拟设备抽象
 * 
 */
struct simple_char_device {
    const char *name;
    struct miscdevice *mscdev;
    wait_queue_head_t read_queue;
    wait_queue_head_t write_queue;
    struct kfifo simple_fifo;
    struct fasync_struct *fasync;
};

/**
 * @brief 驱动私用数据
 * 
 */
struct simple_device_private_data {
    struct simple_char_device *device;
    char name[64];
};

static struct simple_char_device *simpe_dev[MAX_SIMPLE_DEVICES];

static int simpledev_open(struct inode *inode, struct file *file)
{
    /* 通过次设备号找到对应的device数据结构 */
    unsigned int minor = iminor(inode);
    printk("device index = %d\n", minor);
    int i;
    for (i = 0; i < MAX_SIMPLE_DEVICES; i++) {
        if (MINOR(simpe_dev[i]->mscdev->this_device->devt) == minor) {
            break;
        }
    }
    if (i == MAX_SIMPLE_DEVICES) {
        printk("unknown device\n");
        return -EINVAL;
    }
	struct simple_device_private_data *data;
	struct simple_char_device *device = simpe_dev[i];

	printk("%s: major=%d, minor=%d, device=%s\n", __func__, 
			MAJOR(inode->i_rdev), MINOR(inode->i_rdev), device->name);

	data = kmalloc(sizeof(struct simple_device_private_data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	sprintf(data->name, "private_data_%d", minor);

	data->device = device;
    /* 添加私有数据到file */
	file->private_data = data;

    return 0;
}

static int simpledev_release(struct inode *inode, struct file *file)
{
    struct simple_device_private_data *data = file->private_data;
	
	kfree(data);
    return 0;
}

/**
 * @brief 设备读
 * 
 * @param file 表示打开的设备文件
 * @param buf 用户空间的内存起始地址,使用__user来提醒
 * @param count 表示用户想读取多少字节的数据
 * @param ppos 表示文件的位置指针
 * @return ssize_t 表示最终读到的字节数
 */
static ssize_t simpledev_read(struct file *file, char __user* buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
    int actual_readed;
    int ret;

    if (kfifo_is_empty(&device->simple_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }
        printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
        ret = wait_event_interruptible(device->read_queue, !kfifo_is_empty(&device->simple_fifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_to_user(&device->simple_fifo, buf, count, &actual_readed);
    if (ret) {
        return -EIO;
    }

    if(!kfifo_is_full(&device->simple_fifo)) {
        wake_up_interruptible(&device->write_queue);
        kill_fasync(&device->fasync, SIGIO, POLL_OUT);
        printk("%s kill fasync\n", __func__);
    }

    printk("%s, pid=%d, actual_readed = %d, pos = %lld\n", __func__, current->pid, actual_readed, *ppos);
    return actual_readed;
}

static ssize_t simpledev_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
    unsigned int actual_writed;
    int ret;

    if (kfifo_is_full(&device->simple_fifo)) {
        if (file->f_flags & O_NONBLOCK) {
            return -EAGAIN;
        }

        printk("%s: pid=%d, going to sleep\n", __func__, current->pid);
        ret = wait_event_interruptible(device->write_queue, !kfifo_is_full(&device->simple_fifo));
        if (ret) {
            return ret;
        }
    }

    ret = kfifo_from_user(&device->simple_fifo, buf, count, &actual_writed);
    if (ret) {
        return -EIO;
    }

    if (!kfifo_is_full(&device->simple_fifo)) {
        wake_up_interruptible(&device->read_queue);
        kill_fasync(&device->fasync, SIGIO, POLL_IN);
        printk("%s kill fasync\n", __func__);
    }

    printk("%s: pid=%d, actual_wited = %d, ppos = %lld, ret=%d\n", __func__, current->pid, actual_writed, *ppos, ret);
    return actual_writed;
}

static unsigned int simpledev_poll(struct file *file, poll_table *wait)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
	int mask = 0;

	poll_wait(file, &device->read_queue, wait);
    poll_wait(file, &device->write_queue, wait);

	if (!kfifo_is_empty(&device->simple_fifo))
		mask |= POLLIN | POLLRDNORM;
	if (!kfifo_is_full(&device->simple_fifo))
		mask |= POLLOUT | POLLWRNORM;
	
	return mask;
}

static int simpledev_fasync(int fd, struct file *file, int on)
{
    printk("%s enter\n", __func__);
    struct simple_device_private_data *data = file->private_data;
    struct simple_char_device *device = data->device;
    return fasync_helper(fd, file, on, &device->fasync);
}

static const struct file_operations simpledev_fops = {
    .owner = THIS_MODULE,
    .open = simpledev_open,
    .release = simpledev_release,
    .read = simpledev_read,
    .write = simpledev_write,
    .poll = simpledev_poll,
    .fasync = simpledev_fasync,
};

static int __init simple_char_init(void)
{
    int ret;
    int i;
    struct simple_char_device *dev;
    for (i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        dev = kmalloc(sizeof(struct simple_char_device), GFP_KERNEL);
        if (!dev) {
            ret = -ENOMEM;
            goto free_device;
        }
        dev->name = kmalloc(64, GFP_KERNEL);
        if (!dev->name) {
            ret = -ENOMEM;
            goto free_device;
        }
        sprintf(dev->name, "%s%d", DEMO_NAME, i);
        dev->mscdev = kmalloc(sizeof(struct miscdevice), GFP_KERNEL);
        if (!dev->mscdev) {
            ret = -ENOMEM;
            goto free_device;
        }
        dev->mscdev->minor = MISC_DYNAMIC_MINOR;
        dev->mscdev->name = dev->name;
        dev->mscdev->fops = &simpledev_fops;
        ret = misc_register(dev->mscdev);
        if (ret) {
            printk("failed to allocate misc char device region");
            goto free_device;
        }
        simple_virtualchardev = dev->mscdev->this_device;
        printk("Major number = %d, minor number = %d\n", MAJOR(simple_virtualchardev->devt), MINOR(simple_virtualchardev->devt));
        simpe_dev[i] = dev;

        init_waitqueue_head(&dev->read_queue);
        init_waitqueue_head(&dev->write_queue);
        ret = kfifo_alloc(&dev->simple_fifo, MAX_DEVICE_BUFFER_SIZE, GFP_KERNEL);
        if (ret) {
            ret = -ENOMEM;
            goto free_kfifo;
        }
        printk("simple_fifo=%p\n", &dev->simple_fifo);
    }

    return 0;

free_kfifo:
    for (i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        dev = simpe_dev[i];
        if (&dev->simple_fifo) {
            kfifo_free(&dev->simple_fifo);
        }
    }
free_device:
    for (i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        if (simpe_dev[i]) {
            kfifo_free(&simpe_dev[i]->simple_fifo);
        }
    }

    return ret;
}

static void __exit simple_char_exit(void)
{
    printk("removing device\n");
    for (int i = 0; i< MAX_SIMPLE_DEVICES; i++) {
        if (simpe_dev[i]) {
            misc_deregister(simpe_dev[i]->mscdev);
        }
    }
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_AUTHOR("marvin");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple vitual character device");

对应的测试程序如下:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <linux/input.h>
#include <signal.h>
#include <wait.h>

static int fd;
static FILE *log_file;

void test_signal_func(int signum, siginfo_t *siginfo, void *act)
{
    fprintf(log_file, "%s enter\n", __func__);
    int ret;
    char buf[64];

    if (signum == SIGIO) {
        if (siginfo->si_band & POLLIN) {
            fprintf(log_file, "FIFO not empty\n");
            ret = read(fd, buf, sizeof(buf));
            fprintf(log_file, "read file: %d\n", ret);
            if (ret != -1) {
                buf[ret] = '\0';
                fputs(buf, log_file);
            }
        }
        if (siginfo->si_band & POLLOUT) {
            fprintf(log_file, "FIFO not full\n");
        }
    }
}

int main(int argc, char *argv[])
{
    int ret;
    int flag;
    char buf[64];
    struct sigaction act, oldact;

    log_file = fopen("/tmp/test.log", "a+");
    if (!log_file) {
        printf("open failed\n");
        return -1;
    }
    setbuf(log_file,NULL);
    fprintf(log_file, "start\n");

    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGIO);
    act.sa_flags = SA_SIGINFO;
    /* 设置处理函数为test_signal_func */
    act.sa_sigaction = test_signal_func;
    /* 通过sigaction设置进程接受指定信号 */
    if (sigaction(SIGIO, &act, &oldact) == -1) {
        goto fail;
    }

    fd = open("/dev/simple_virtualchardev0", O_RDWR);
    if (fd < 0) {
        goto fail;
    }
    fprintf(log_file, "open device\n");

    /* set io own*/
    pid_t current_pid = getpid();
    if (fcntl(fd, F_SETOWN, current_pid) == -1) {
        goto fail;
    }
    fprintf(log_file, "set io own: %d\n", current_pid);
    /* set SIGIO*/
    if (fcntl(fd, F_SETSIG, SIGIO) == -1) {
        goto fail;
    }
    fprintf(log_file, "set sigio\n");
    /* get file flags*/
    if ((flag = fcntl(fd, F_GETFL)) == -1) {
        goto fail;
    }
    fprintf(log_file, "get fla\n");
    /* set falgs, set FASYNC, to support async*/
    if (fcntl(fd, F_SETFL, flag | FASYNC) == -1) {
        goto fail;
    }
    fprintf(log_file, "set flag\n");
    while (1) {
        sleep(1);
    }

fail:
    perror("fasync test");
    exit(EXIT_FAILURE);
}

测试主要内容:

  1. 设置进程接收并处理指定信号——SIGIO
  2. 使用fcntl设置打开设备文件支持FASYNC
  3. 测试进程收到SIGIO后处理

运行结果:

$ sudo ./test
start
open device
set io own: 51317
set sigio
get fla
set flag

驱动输出:

[64977.502436] device index = 119
[64977.502442] simpledev_open: major=10, minor=119, device=simple_virtualchardev0
[64977.502491] simpledev_fasync enter

打开另一终端,往设备中写入数据:

# echo "test" > /dev/simple_virtualchardev0

驱动输出:

[65004.525538] device index = 119
[65004.525549] simpledev_open: major=10, minor=119, device=simple_virtualchardev0
[65004.525600] simpledev_write enter
[65004.525625] simpledev_write kill fasync
[65004.525628] simpledev_write: pid=38781, actual_wited = 5, ppos = 0, ret=0
[65004.526045] simpledev_read enter
[65004.526065] simpledev_read kill fasync
[65004.526068] simpledev_read, pid=51317, actual_readed = 5, pos = 0

测试进程输出:

test_signal_func enter
FIFO not empty
read file: 5
test
test_signal_func enter
FIFO not full
posted @ 2025-02-10 12:12  main_c  阅读(1)  评论(0)    收藏  举报  来源