• Linux设备驱动异步通知


    注:本文是《Linux设备驱动开发详解:基于最新的Linux 4.0内核 by 宋宝华 》一书学习的笔记,大部分内容为书籍中的内容。

    书籍可直接在微信读书中查看:Linux设备驱动开发详解:基于最新的Linux4.0内核-宋宝华-微信读书 (qq.com)

    Linux设备驱动中的异步通知和异步I/O

    在设备驱动中使用异步通知可以使得在对设备访问时,由驱动主动通知应用程序进行访问

    使用非阻塞I/O的应用程序不需要轮训设备是否可以访问,而阻塞访问可以被类似“中断”的异步通知所取代。

    除了异步通知以外,应用还可以在发起I/O请求后,立即返回。之后,再查询I/O完成情况,或者I/O完成后被调回。这个过程叫作异步I/O。

    1.1 异步通知简介

    异步通知:一旦设备就绪,则主动通知应用程序,应用程序不需要查询设备状态,较为准确的称呼为“信号驱动的异步I/O”。

    信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。

    阻塞I/O意味着一直等待设备可访问后再访问。非阻塞I/O使用poll()意味着查询设备是否可访问。

    下图呈现了阻塞I/O,结合轮询的非阻塞I/O,以及基于SIGIO的异步通知在时间先后顺序上的不同。

    image-20220130202942548

    1.2 Linux异步通知编程

    1.2.1 Linux信号

    Linux中异步通知使用信号来实现,Linux中可用的信号及其定义如下:

    image-20220130210010063

    image-20220130210133027

    除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程所捕获,内核将采用默认行为处理。

    1.2.2 信号的接收

    在用户程序中,捕获信号可以使用signal()函数来设置对应信号的处理函数,函数原型为:

    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    

    参数signum:指定信号的值

    参数handler:指定针对信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号捕获后,该函数被执行。

    返回值:如果调用成功,返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。

    在进程执行时,按下Ctrl+C将向其发出SIGINT信号,正在运行kill的进程将向其发出SIGTERM信号,捕获这两个信号并输出信号值的代码如下:

    void sigterm_handler(int signo)
    {
    	printf("Have caught sig N.0.%d\n", signo);
        exit(0);
    }
    
    int main(void)
    {
        signal(SIGINT, sigterm_handler);
        signal(SIGTERM, sigterm_handler);
        while(1);
        
        return 0;
    }
    

    编译、测试:

    $ ./a.out 
    ^C^CHave caught sig N.0.2
    

    sigaction()函数:可用于改变进程接收到特定信号后的行为,函数原型为:

    #include <signal.h>
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    

    参数signum:信号的值,可以为SIGKILL以及SIGSTOP外的任何一个特定有效的信号。

    参数act:指向结构体sigaction的一个实例的指针,在结构体sigaction的实例中指定了对特定信号的处理函数,若为空,则进程会以缺省方式对信号处理。

    参数oldact:指向的对象用来保存原来对相应信号的处理函数,可指定为NULL。

    如果把第二、第三个参数设置为NULL,该函数可用于检查信号的有效性。

    返回值:成功返回0,失败返回-1。

    1.2.3 异步通知的应用程序实例

    通过signal(SIGIO, input_handler)对标准输入文件描述符STDIN_FIFLNO启动信号机制。用户输入后,应用程序将接收到SIGIO信号,其处理函数input_handler()将被调用。

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <unistd.h>
    
    #define MAX_LEN  100
    
    void input_handler(int num)
    {
        char data[MAX_LEN];
        int len;
    
        /* 读取并输出STDIN_FIFLNO */
        len = read(STDIN_FILENO, &data, MAX_LEN);
        data[len] = 0;
        printf("input data:%s\n", data);
    }
    
    int main()
    {
        int oflags;
    
        /* 启动信号驱动机制 */
        signal(SIGIO, input_handler); //SIGIO信号安装input_handler()作为处理函数
        fcntl(STDIN_FILENO, F_SETOWN, getpid());  //设置本进程为STDIN_FIFENO文件的拥有者
        oflags = fcntl(STDIN_FILENO, F_GETFL);  
        fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);  //设置FASYNC
    
        /* 最后进入一个死循环,保持进程不终止 */
        while(1);
    }
    

    编译、测试:

    $ gcc sigio_handler.c 
    $ ./a.out 
    i am chinese.  
    input data:i am chinese.
    
    i love linux driver    
    input data:i love linux driver
    
    

    可以看出,用户输入一串字符之后,标准输入设备释放SIGIO信号,这个信号“中断”驱使应用程序中的input_handler()得以执行,并在用户输入显示出来。

    因此,在用户空间中处理一个设备释放的信号,必须完成以下三项内容:

    1)通过F_SETOWN I/O控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。

    2)通过F_SETFL I/O控制命令设备设备文件以支持FASYNC,即异步通知模式。

    3)通过signal()函数连接信号和信号处理函数。

    1.2.4 设备驱动中异步通知

    在设备驱动和应用程序的交互中,信号的源头在设备驱动端,捕获在应用程序端。因此,信号的释放应该在设备驱动中进行。

    设备支持异步通知机制,驱动程序需要支持以下三项工作:

    1)支持F_SETOWN命令,能在这个控制命令中处理file->f_owner为对应进程的ID,这个已经由内核完成,设备驱动无须处理。

    2)支持F_SETFL命令,每当FASYNC标志改变时,驱动程序中的fasync()函数得到执行,设备驱动中应该实现fasync()函数。

    3)在设备资源可获得时,调用kill_fasync()函数激发对应的信号。

    驱动中的三项工作和应用程序中的三项工作是一一对应的,设备驱动异步通知处理和用户空间交互过程如下:

    image-20220201111407146

    设备驱动中异步编程的函数涉及一个数据结构fasync_struct和两个函数fasync_helper()和kill_fasync():

    (1)处理FASYNC标志变更的函数

    int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);
    

    (2)释放信号函数

    void kill_fasync(struct fasync_struct **fp, int sig, int band);
    

    在设备资源可以获得时,应该调用kill_fasync()释放SIGIO信号。在可读时,第三个参数band设置为POLL_IN;在可写时,第三个参数band设置为POLL_OUT。

    fasync_struct数据结构体指针一般放在设备结构体中,支持异步通知的设备结构体模板如下:

    struct  xxx_dev {
        struct cdev cdev;
        ... ....;
        struct fasync_struct *async_queue;  /* 异步结构体指针 */
        ... ...;
    };
    

    设备驱动的faync()函数:

    int (*fasync) (int, struct file *, int);
    

    支持异步通知的设备驱动程序fasync()函数的模板:

    static int xxx_fasync(int fd, struct file *filp, int mode)
    {
        struct xxx_dev *dev = filp->private_data;
        return fasync_helper(fd, filp, mode, &dev->async_queue);
    }
    

    支持异步通知的设备驱动信号释放模板:

    在资源可获得时,调用kill_fasync()函数释放SIGIO信号。

    static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
    {
        struct xxx_dev *dev = filp->private_data;
        ... ...;
        
        /* 产生异步读信号 */
        if (dev->async_queue)
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
    }
    

    最后在文件关闭时,在设备的release()函数中,调用设备驱动的fasync()函数将文件从异步通知的列表中删除。支持异步通知的设备驱动release()函数模板如下:

    static int xxx_release(struct inode *inode, struct file *filp)
    {
    	/* 将文件从异步通知的列表中删除 */
        xxx_fasync(-1, filp, 0);
        ... ...;
        return 0;
    }
    

    1.3 支持异步通知的globalfifo驱动

    1.3.1 设备驱动globalfifo的改写

    首先,需要将异步数据结构体指针添加到globalfifo_dev设备结构体中:

    /* 设备结构体 */
    struct  globalfifo_dev {
        struct cdev cdev;
        unsigned int current_len;  /* 当前FIFO中有效数据的长度 */
        unsigned char mem[GLOBALFIFO_SIZE];
        struct mutex mutex;
        wait_queue_head_t r_wait;
        wait_queue_head_t w_wait;
        struct fasync_struct *async_queue;  /* 异步结构体指针 */
    };
    

    然后,编写异步通知的globalfifo设备驱动的fasync()函数:

    static int globalfifo_fasync(int fd, struct file *filp, int mode)
    {
        struct globalfifo_dev *dev = filp->private_data;
        return fasync_helper(fd, filp, mode, &dev->async_queue);
    }
    

    然后,改写globalfifo设备驱动的写函数,在globalfifo设备被正确写入之后,可以进行读取,需要支持释放SIGIO信号,通过给应用程序捕获。

    /**
     * 写设备
     * @param[in] filp:文件结构体指针
     * @param[in] buf: 用户空间内存地址,不能在内核中直接读写
     * @param[in] size: 写入的字节数
     * @param[in/out] ppos: 写的位置相当于文件头的偏移
     * @return  若成功返回实际写的字节数,若出错返回错误码
     */
    static ssize_t globalfifo_write(struct file *filp,
        const char __user *buf, size_t count, loff_t *ppos)
    {
        int ret = 0;
        struct globalfifo_dev *dev = filp->private_data;
        DECLARE_WAITQUEUE(wait, current);
    
        mutex_lock(&dev->mutex);
        add_wait_queue(&dev->w_wait, &wait);
    
        while (dev->current_len == GLOBALFIFO_SIZE) {
            if (filp->f_flags & O_NONBLOCK) {
                ret = -EAGAIN;
                goto out;
            }
            __set_current_state(TASK_INTERRUPTIBLE);
    
            mutex_unlock(&dev->mutex);
    
            schedule();
            if (signal_pending(current)) {
                ret = -ERESTARTSYS;
                goto out2;
            }
            mutex_lock(&dev->mutex);
        }
    
        if (count > GLOBALFIFO_SIZE - dev->current_len)
            count = GLOBALFIFO_SIZE - dev->current_len;
    
        /* 用户空间缓存区到内核空间缓存区的复制 */
        if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
            ret = -EFAULT;
            goto out;
        } else {
            dev->current_len += count;
            printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
            wake_up_interruptible(&dev->r_wait);
            if (dev->async_queue) {
                kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
                printk(KERN_INFO "%s kill SIGIO\n", __func__);
            }
            ret = count;
        }
    
    out:
        mutex_unlock(&dev->mutex);
    
    out2:
        remove_wait_queue(&dev->w_wait, &wait);
        set_current_state(TASK_RUNNING);
        return ret;
    }
    

    globalfifo设备驱动的release()函数需要调用globalfifo_fasync()函数将文件从异步通知列表中删除:

    static int globalfifo_release(struct inode *inode, struct file *filp)
    {
    	/* 将文件从异步通知的列表中删除 */
        globalfifo_fasync(-1, filp, 0);
        return 0;
    }
    

    完整的设备驱动代码:

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>
    #include <linux/uaccess.h>
    #include <linux/poll.h>
    
    /* 直接使用立即数当作命令不合理,暂定 */
    #define MEM_CLEAR           0x1
    #define GLOBALFIFO_MAJOR    230
    #define GLOBALFIFO_SIZE     0x1000
    
    static int globalfifo_major = GLOBALFIFO_MAJOR;
    module_param(globalfifo_major, int, S_IRUGO);
    
    /* 设备结构体 */
    struct  globalfifo_dev {
        struct cdev cdev;
        unsigned int current_len;  /* 当前FIFO中有效数据的长度 */
        unsigned char mem[GLOBALFIFO_SIZE];
        struct mutex mutex;
        wait_queue_head_t r_wait;
        wait_queue_head_t w_wait;
        struct fasync_struct *async_queue;  /* 异步结构体指针 */
    };
    
    struct globalfifo_dev *globalfifo_devp;
    
    static int globalfifo_fasync(int fd, struct file *filp, int mode)
    {
        struct globalfifo_dev *dev = filp->private_data;
        return fasync_helper(fd, filp, mode, &dev->async_queue);
    }
    
    static int globalfifo_open(struct inode *inode, struct file *filp)
    {
        /* 使用文件的私有数据作为获取globalfifo_dev的实例指针 */
        filp->private_data = globalfifo_devp;
        return 0;
    }
    
    static int globalfifo_release(struct inode *inode, struct file *filp)
    {
        /* 将文件从异步通知的列表中删除 */
        globalfifo_fasync(-1, filp, 0);
        return 0;
    }
    
    /**
     * 设备ioctl函数
     * @param[in] filp:文件结构体指针
     * @param[in] cmd: 命令,当前仅支持MEM_CLEAR
     * @param[in] arg: 命令参数
     * @return  若成功返回0,若出错返回错误码
     */
    static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
        unsigned long arg)
    {
        struct globalfifo_dev *dev = filp->private_data;
    
        switch (cmd) {
        case MEM_CLEAR:
            mutex_lock(&dev->mutex);
            dev->current_len = 0;
            memset(dev->mem, 0, GLOBALFIFO_SIZE);
            mutex_unlock(&dev->mutex);
            printk(KERN_INFO "globalfifo is set to zero\n");
            break;
        
        default:
            return -EINVAL;
        }
        return 0;
    }
    
    /**
     * 查询对一个或多个文件描述符的读或写是否会阻塞
     * @param[in] filp:文件结构体指针
     * @param[in] wait: 轮询表指针
     * @return  返回位掩码指示是否非阻塞的读或写是可能的
     */
    static unsigned int globalfifo_poll(struct file *filp,
        struct poll_table_struct *wait)
    {
        unsigned int mask = 0;
        struct globalfifo_dev *dev = filp->private_data;
    
        mutex_lock(&dev->mutex);
    
        /* 调用select而阻塞的进程可以被r_wait和w_wait唤醒 */
        poll_wait(filp, &dev->r_wait, wait);
        poll_wait(filp, &dev->w_wait, wait);
    
        if (dev->current_len != 0) {
            /* 设备可以无阻塞的读,正常数据可用来读 */
            mask |= POLLIN | POLLRDNORM;
        }
    
        if (dev->current_len != GLOBALFIFO_SIZE) {
            /* 设备可以无阻塞的写 */
            mask |= POLLOUT | POLLWRNORM;
        }
    
        mutex_unlock(&dev->mutex);
        return mask;
    }
    
    
    /**
     * 读设备
     * @param[in] filp:文件结构体指针
     * @param[out] buf: 用户空间内存地址,不能在内核中直接读写
     * @param[in] size: 读取的字节数
     * @param[in/out] ppos: 读的位置相当于文件头的偏移
     * @return  若成功返回实际读的字节数,若出错返回错误码
     */
    static ssize_t globalfifo_read(struct file *filp,
        char __user *buf, size_t size, loff_t *ppos)
    {
        int ret = 0;
        unsigned long count = size;
        struct globalfifo_dev *dev = filp->private_data;
    
        DECLARE_WAITQUEUE(wait, current);
    
        mutex_lock(&dev->mutex);
        add_wait_queue(&dev->r_wait, &wait);
    
        while (dev->current_len == 0) {
            if (filp->f_flags & O_NONBLOCK) {
                ret = -EAGAIN;
                goto out;
            }
    
            __set_current_state(TASK_INTERRUPTIBLE);
            mutex_unlock(&dev->mutex);
    
            schedule();
            if (signal_pending(current)) {
                ret = -ERESTARTSYS;
                goto out2;
            }
            
            mutex_lock(&dev->mutex);
        }
    
        if (count > dev->current_len)
            count = dev->current_len;
    
        /* 内核空间到用户空间缓存区的复制 */
        if (copy_to_user(buf, dev->mem, count)) {
            ret = -EFAULT;
            goto out;
        } else {
            memcpy(dev->mem, dev->mem + count, dev->current_len - count);
            dev->current_len -= count;
            printk(KERN_INFO "read %lu bytes(s) from %u\n", count, dev->current_len);
            wake_up_interruptible(&dev->w_wait);
            ret = count;
        }
    
    out:
        mutex_unlock(&dev->mutex);
    
    out2:
        remove_wait_queue(&dev->r_wait, &wait);
        set_current_state(TASK_RUNNING);
        return ret;
    }
    
    /**
     * 写设备
     * @param[in] filp:文件结构体指针
     * @param[in] buf: 用户空间内存地址,不能在内核中直接读写
     * @param[in] size: 写入的字节数
     * @param[in/out] ppos: 写的位置相当于文件头的偏移
     * @return  若成功返回实际写的字节数,若出错返回错误码
     */
    static ssize_t globalfifo_write(struct file *filp,
        const char __user *buf, size_t count, loff_t *ppos)
    {
        int ret = 0;
        struct globalfifo_dev *dev = filp->private_data;
        DECLARE_WAITQUEUE(wait, current);
    
        mutex_lock(&dev->mutex);
        add_wait_queue(&dev->w_wait, &wait);
    
        while (dev->current_len == GLOBALFIFO_SIZE) {
            if (filp->f_flags & O_NONBLOCK) {
                ret = -EAGAIN;
                goto out;
            }
            __set_current_state(TASK_INTERRUPTIBLE);
    
            mutex_unlock(&dev->mutex);
    
            schedule();
            if (signal_pending(current)) {
                ret = -ERESTARTSYS;
                goto out2;
            }
            mutex_lock(&dev->mutex);
        }
    
        if (count > GLOBALFIFO_SIZE - dev->current_len)
            count = GLOBALFIFO_SIZE - dev->current_len;
    
        /* 用户空间缓存区到内核空间缓存区的复制 */
        if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
            ret = -EFAULT;
            goto out;
        } else {
            dev->current_len += count;
            printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
            wake_up_interruptible(&dev->r_wait);
            if (dev->async_queue) {
                kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
                printk(KERN_INFO "%s kill SIGIO\n", __func__);
            }
            ret = count;
        }
    
    out:
        mutex_unlock(&dev->mutex);
    
    out2:
        remove_wait_queue(&dev->w_wait, &wait);
        set_current_state(TASK_RUNNING);
        return ret;
    }
    
    /**
     * 文件偏移设置
     * @param[in] filp:文件结构体指针
     * @param[in] offset: 偏移值大小
     * @param[in] orig: 起始偏移位置
     * @return  若成功返回文件当前位置,若出错返回错误码
     */
    static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
    {
        loff_t ret = 0;
        switch (orig) {
        case 0:  /* 从文件头位置设置偏移 */
            if (offset < 0) {
                ret = -EINVAL;
                break;
            }
            if ((unsigned int)offset > GLOBALFIFO_SIZE) {
                ret = -EINVAL;
                break;
            }
            filp->f_pos = (unsigned int)offset;
            ret = filp->f_pos;
            break;
        case 1:  /* 从当前位置设置偏移 */
            if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
                ret = -EINVAL;
                break;
            }
            if ((filp->f_pos + offset) < 0) {
                ret = -EINVAL;
                break;
            }
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;
        
        default:
            ret = -EINVAL;
            break;;
        }
        return ret;
    }
    
    static const struct file_operations globalfifo_fops = {
    	.owner = THIS_MODULE,
    	.llseek = globalfifo_llseek,
    	.read = globalfifo_read,
    	.write = globalfifo_write,
    	.unlocked_ioctl = globalfifo_ioctl,
    	.open = globalfifo_open,
    	.release = globalfifo_release,
        .poll = globalfifo_poll,
        .fasync = globalfifo_fasync,
    };
    
    static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
    {
        int err, devno = MKDEV(globalfifo_major, index);
    
        /* 初始化cdev */
        cdev_init(&dev->cdev, &globalfifo_fops);
        dev->cdev.owner = THIS_MODULE;
        /* 注册设备 */
        err = cdev_add(&dev->cdev, devno, 1);
        if (err)
            printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index);
    }
    
    /* 驱动模块加载函数 */
    static int __init globalfifo_init(void)
    {
        int ret;
        dev_t devno = MKDEV(globalfifo_major, 0);
    
        /* 获取设备号 */
        if (globalfifo_major)
            ret = register_chrdev_region(devno, 1, "globalfifo");
        else {
            ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
            globalfifo_major = MAJOR(devno);
        }
        
        if (ret < 0)
            return ret;
        
        /* 申请内存 */
        globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
        if (!globalfifo_devp) {
            ret = -ENOMEM;
            goto fail_malloc;
        }
        globalfifo_setup_cdev(globalfifo_devp, 0);
    
        mutex_init(&globalfifo_devp->mutex);
    
        init_waitqueue_head(&globalfifo_devp->r_wait);
        init_waitqueue_head(&globalfifo_devp->w_wait);
    
        return 0;
    
    fail_malloc:
        unregister_chrdev_region(devno, 1);
        return ret;
    }
    module_init(globalfifo_init);
    
    /* 驱动模块卸载函数 */
    static void __exit globalfifo_exit(void)
    {
        cdev_del(&globalfifo_devp->cdev);
        kfree(globalfifo_devp);
        /* 释放设备号 */
        unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
    }
    module_exit(globalfifo_exit);
    
    MODULE_AUTHOR("MrLayfolk");
    MODULE_LICENSE("GPL v2");
    

    1.3.2 用户空间globalfifo驱动验证

    用户空间应用程序实现在接收到globalfifo发出的信号后输出信号值。

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <unistd.h>
    
    #define MAX_LEN  100
    
    static void signalio_handler(int signum)
    {
        printf("receive a signal, signal number:%d\n", signum);
    }
    
    int main()
    {
        int fd, oflags;
    
        /* 以非阻塞方式打开设备文件 */
        fd = open("/dev/globalfifo", O_RDONLY | S_IRUSR | S_IWUSR);
        if (fd != -1) {
            /* 启动信号驱动机制 */
            signal(SIGIO, signalio_handler); //SIGIO信号安装input_handler()作为处理函数
            fcntl(fd, F_SETOWN, getpid());  //设置本进程为STDIN_FIFENO文件的拥有者
            oflags = fcntl(fd, F_GETFL);  
            fcntl(fd, F_SETFL, oflags | FASYNC);  //设置FASYNC    
            while (1) {
                sleep(100);
            }
        } else {
            printf("device open failure\n");
        }
        
        return 0;
    }
    

    1.3.3 编译测试

    (1)编译设备驱动ko,并插入ko

    $ make
    $ insmod globalfifo.ko 
    

    (2)创建设备节点

    $ mknod /dev/globalfifo c 230 0 
    

    (3)编译用户程序,并且运行

    $ gcc globalfifo_app.c 
    $ ./a.out
    
    

    (4)向设备驱动写入数据,signalio_handler()会被调用

    $ echo "hello" > /dev/globalfifo
    $ echo "hello" > /dev/globalfifo
    $ echo "hello" > /dev/globalfifo
    $ ./a.out              
    receive a signal, signal number:29
    receive a signal, signal number:29
    receive a signal, signal number:29
    
  • 相关阅读:
    Linux 下使用gettimeofday函数时已添加time.h后,还是报找不到gettimeofday函数问题
    记录linux编译opencv时报错: relocation R_X86_64_PC32 against symbol `XXXX' can not be used when making a shared object; recompile with fPIC
    记录Linux 没有声音/Gnome 没有声音输出设备问题
    c#抓屏(截屏)
    抽象类与接口区别 (收集)
    种方式遍历repeater中的CheckBox全选
    .net 发送电子邮件
    Repeater 双向排序
    通过SessionID和用户名来保证同一个用户不能同时登录
    ASP.NET页面刷新方法总结
  • 原文地址:https://www.cnblogs.com/mrlayfolk/p/15858989.html
Copyright © 2020-2023  润新知