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 异步通知简介

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

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

阻塞I/O意味着一直等待设备可访问后再访问。

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

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

image-20220130202942548

2 Linux异步通知编程

异步通知的核心就是信号,在arch\arm\include\uapi\asm\signal.h中定义了Linux使用的所有信号。

2.1 Linux信号

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

信号 含义
SIGHUP 1 终端挂起或控制进程终止
SIGINT 2 终端中断(Ctrl+C组合键)
SIGQUIT 3 终端退出(Ctrl+\组合键)
SIGILL 4 非法指令
SIGTRAP 5 debug使用,有断点指令产生
SIGABRT 6 由abort(3)发出的退出指令
SIGIOT 6 IOT指令
SIGBUS 7 总线错误
SIGFPE 8 浮点运算错误
SIGKILL 9 杀死、终止进程
SIGUSR1 10 用户自定义信号1
SIGSEGV 11 段违例(无效的内存段)
SIGUSR2 12 用户自定义信号2
SIGPIPE 13 向非读管道写入数据
SIGALRM 14 闹钟
SIGTERM 15 软件终止
SIGSTKFLT 16 栈异常
SIGCHLD 17 子进程结束
SIGCONT 18 进程继续
SIGSTOP 19 停止进程的执行,只是暂停
SIGTSTP 20 停止进程的运行(Ctrl+Z组合键)
SIGTTIN 21 后台进程需要从终端读取数据
SIGTTOU 22 后台进程需要向终端写数据
SIGURG 23 有“紧急”数据
SIGXCPU 24 超过CPU资源限制,超额使用CPU时间
SIGXFSZ 25 文件大小超额
SIGVTALRM 26 虚拟时钟信号
SIGPROF 27 时钟信号描述
SIGWINCH 28 窗口大小改变
SIGIO 29 可以进行输入/输出操作
SIGPOLL SIGIO
SIGPWR 30 断电重启
SIGSYS 31 非法的系统调用
SIGUNUSED 31 未使用信号

image-20220130210010063

image-20220130210133027

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

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(15)信号,捕获这两个信号并输出信号值的代码如下:

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。

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控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。

fcntl(fd, F_SETOWN, getpid());

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

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

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

void xxx_signal_handler(int num)
{
    ... ...
}

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标志变更的函数

struct fasync_struct {
	spinlock_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;
};

/* 参数fd、filp、on通过fasync函数的三个参数传入;
 * 参数fapp:要初始化的fasync_struct结构体指针变量。
*/
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;  /* 异步结构体指针 */
    ... ...;
};

(3)设备驱动file_operations操作集中的fasync()函数

int (*fasync) (int fd, struct file *filp, int mode);

支持异步通知的设备驱动程序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);
}

在release函数中释放fasync_struct,同样调用fasync_helper实现:

static int xxx_release(struct inode *inode, struct file *filp)
{
    return xxx_fasync(-1, filp, 0);
}

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

在资源可获得时,调用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;
}

3 支持异步通知的globalfifo驱动

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");

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;
}

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

4 按键中断使用信号

4.1 驱动程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/fcntl.h>

#define KEY_DEVICE_CNT     1        /* 设备个数 */
#define KEY_NAME    "key_noblock"   /*设备名字*/

/* 定义键值 */
#define KEY0_VALUE      0x01    /* KEY0按键值 */
#define INVALID_KEY     0xFF    /* 无效键值 */

#define KEY_NUM         1       /* 按键数量 */

/* 中断IO描述结构体 */
struct irq_key_desc {
    int gpio;               /* GPIO */
    int irq_num;            /* 中断号 */
    unsigned char value;    /* 按键对应的键值 */
    char name[10];          /* 名字 */
    irqreturn_t(*handler)(int, void *); /* 中断处理函数 */
};

/* KEY设备结构体 */
struct chrdev_key {
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    atomic_t key_value;     /* 有效的按键值 */
    atomic_t release_key;   /* 标记一次完成的按键是否完成 */
    struct timer_list timer;    /* 定时器 */
    struct irq_key_desc irq_key[KEY_NUM]; /* 按键描述 */
    unsigned char cur_key_num;  /* 当前的按键号 */
    wait_queue_head_t r_wait;   /* 读等待队列头 */
    struct fasync_struct *async_queue; /* 异步相关结构体 */
};

struct chrdev_key chrdev_key;

/* 中断服务函数:开启定时器,延时10ms,定时器用于消抖
 * 参数irq:中断号
 * 参数dev_id:设备结构
 * 返回值:中断处理结果
 */
irqreturn_t key0_irq_handler(int irq, void *dev_id)
{
    struct chrdev_key *dev = (struct chrdev_key *)dev_id;

    dev->cur_key_num = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器服务函数:消除按键抖动,定时器到了后再次读取按键值,如果按键处于按下则表示按键有效
 * arg:设备结构体变量
*/
void timer_func(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_key_desc *key_desc = NULL;
    struct chrdev_key *dev = (struct chrdev_key *)arg;

    num = dev->cur_key_num;
    key_desc = &dev->irq_key[num];
    value = gpio_get_value(key_desc->gpio);
    if (value == 0) { /* 按下按键 */
        atomic_set(&dev->key_value, key_desc->value);
    } else {    /* 按键松开 */
        atomic_set(&dev->key_value, 0x80 | key_desc->value);
        atomic_set(&dev->release_key, 1);   /* 标记松开按键 */
    }

    /* 唤醒进程 */
    if (atomic_read(&dev->release_key)) {  /* 完成一次按键过程 */
        if (dev->async_queue) {
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
        }
    }
}

/* 按键IO初始化 */
static int key_gpio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    chrdev_key.nd = of_find_node_by_path("/key");
    if (chrdev_key.nd == NULL) {
        printk("Cannot find device node!\r\n");
        return -EINVAL;
    }

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        chrdev_key.irq_key[i].gpio = of_get_named_gpio(chrdev_key.nd, "key-gpio", i);
        if (chrdev_key.irq_key[i].gpio < 0) {
            printk("Cannot get key%d!\r\n", i);
        }
    }

    /* 初始化key使用的IO,并且设置为中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(chrdev_key.irq_key[i].name, 0x0, sizeof(chrdev_key.irq_key[i].name));
        sprintf(chrdev_key.irq_key[i].name, "KEY%d", i);
        gpio_request(chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].name);
        gpio_direction_input(chrdev_key.irq_key[i].gpio); /* 设置为输入 */
        chrdev_key.irq_key[i].irq_num = irq_of_parse_and_map(chrdev_key.nd, i);
        printk("key%d: gpio:%d, irq_num:%d\r\n", i, chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].irq_num);
    }

    /* 申请中断 */
    chrdev_key.irq_key[0].handler = key0_irq_handler;
    chrdev_key.irq_key[0].value = KEY0_VALUE;
    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(chrdev_key.irq_key[i].irq_num, chrdev_key.irq_key[i].handler,
            IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
            chrdev_key.irq_key[i].name, &chrdev_key);
        if (ret < 0) {
            printk("key%d irq %d request failed!\r\n", i, chrdev_key.irq_key[i].irq_num);
            return -EFAULT;
        }
        printk("key%d irq %d request successfully!\r\n", i, chrdev_key.irq_key[i].irq_num);
    }

    /* 创建定时器 */
    init_timer(&chrdev_key.timer);
    chrdev_key.timer.function = timer_func;

    /* 初始化等待队列头 */
    init_waitqueue_head(&chrdev_key.r_wait);

    return 0;
}

static int key_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &chrdev_key;

    printk("Key open successful!\r\n");

    return 0;
}

/* 从设备读取数据
 * 参数filp:要打开的设备文件(文件描述符)
 * 参数buf:要返回给用户空间的数据缓冲区
 * 参数cnt:要读取的数据长度
 * 参数offt:相对于文件首地址的偏移
 * 返回值:读取的字节数,如果为负值,表示读取失败。
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char key_value = 0;
    unsigned char release_key = 0;
    struct chrdev_key *dev = filp->private_data;

    if (filp->f_flags & O_NONBLOCK) {   /* 非阻塞访问 */
        if (atomic_read(&dev->release_key) == 0) { /* 没有按键按下 */
            return -EAGAIN;
        }
    }
    /* 加入等待队列,等待被唤醒,也就是有按键按下 */
    ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->release_key));
    if (ret) {
        goto wait_error;
    }

    key_value = atomic_read(&dev->key_value);
    release_key = atomic_read(&dev->release_key);

    if (release_key) { /* key按下 */
        if (key_value & 0x80) {
            key_value &= ~0x80;
            ret = copy_to_user(buf, &key_value, sizeof(key_value));
        } else {
            key_value = INVALID_KEY;
            ret = copy_to_user(buf, &key_value, sizeof(key_value));
            goto data_error;
        }
        /* 按下标记清0 */
        atomic_set(&dev->release_key, 0x0);
    } else {
        goto data_error;
    }
    return 0;

wait_error:
    return ret;

data_error:
    return -EINVAL;
}

static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct chrdev_key *dev = filp->private_data;

    poll_wait(filp, &dev->r_wait, wait);

    if (atomic_read(&dev->release_key)) { /* 按键按下 */
        mask = POLLIN | POLLRDNORM;  /* 返回PLLIN */
    }

    return mask;
}

static int key_fasync(int fd, struct file *filp, int on)
{
    struct chrdev_key *dev = filp->private_data;

    return fasync_helper(fd, filp, on, &dev->async_queue);
}

static int key_release(struct inode *inode, struct file *filp)
{
    return key_fasync(-1, filp, 0);
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner   = THIS_MODULE,
    .open    = key_open,
    .read    = key_read,
    .poll    = key_poll,
    .fasync  = key_fasync,
    .release = key_release,
};

static int __init chrdev_key_init(void)
{
    /* 注册字符设备驱动 */
    /* 创建设备号 */
    if (chrdev_key.major) {  /* 定义了设备号 */
        chrdev_key.devid = MKDEV(chrdev_key.major, 0);
        register_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT, KEY_NAME);
    } else {  /* 没有定义设备号 */
        alloc_chrdev_region(&chrdev_key.devid, 0, KEY_DEVICE_CNT, KEY_NAME);
        chrdev_key.major = MAJOR(chrdev_key.devid);
        chrdev_key.minor = MINOR(chrdev_key.devid);
    }

    /* 初始化cdev */
    chrdev_key.cdev.owner = THIS_MODULE;
    cdev_init(&chrdev_key.cdev, &key_fops);

    /* 添加一个cdev */
    cdev_add(&chrdev_key.cdev, chrdev_key.devid, KEY_DEVICE_CNT);

    /* 创建类 */
    chrdev_key.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(chrdev_key.class)) {
        return PTR_ERR(chrdev_key.class);
    }

    /* 创建设备 */
    chrdev_key.device = device_create(chrdev_key.class, NULL, chrdev_key.devid, NULL, KEY_NAME);
    if (IS_ERR(chrdev_key.device)) {
        return  PTR_ERR(chrdev_key.device);
    }

    /* 初始化按键 */
    atomic_set(&chrdev_key.key_value, INVALID_KEY);
    atomic_set(&chrdev_key.release_key, 0x0);
    if (key_gpio_init() != 0) {
        goto ERROR;
    }

    printk("Key device driver register successful!\r\n");
    return 0;

ERROR:
    /* 注销字符设备驱动 */
    cdev_del(&chrdev_key.cdev);
    unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);

    device_destroy(chrdev_key.class, chrdev_key.devid);
    class_destroy(chrdev_key.class);

    return -EINVAL;
}

static void __exit chrdev_key_exit(void)
{
    unsigned char i = 0;

    /* 删除定时器 */
    del_timer_sync(&chrdev_key.timer);

    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(chrdev_key.irq_key[i].irq_num, &chrdev_key);
    }

    /* 注销字符设备驱动 */
    cdev_del(&chrdev_key.cdev);
    unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);

    device_destroy(chrdev_key.class, chrdev_key.devid);
    class_destroy(chrdev_key.class);

    printk("Key device driver unregister successful!\r\n");
}

module_init(chrdev_key_init);
module_exit(chrdev_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MrLayfolk");

4.2 应用程序

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

static int fd = 0;

static void sigio_signal_handler(int signum)
{
    int err = 0;
    unsigned int key_value = 0;

    err = read(fd, &key_value, sizeof(key_value));
    if (err < 0) {
        printf("read file error! error code = %d!\r\n", err);
    } else {
        printf("sigio signal caught, key value = %d.\r\n", key_value);
    }
}

int main(int argc, char *argv[])
{
    int flags = 0;
    char *file_name= NULL;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    file_name = argv[1];

    /* open file */
    fd = open(file_name, O_RDWR);
    if (fd < 0) {
        printf("file %s open failded!\r\n", file_name);
        return -1;
    }

    /* 设置SIGIO处理函数 */
    signal(SIGIO, sigio_signal_handler);

    fcntl(fd, F_SETOWN, getpid());      /* 将当前进程的进程号告诉内核 */
    flags = fcntl(fd, F_GETFD);         /* 获取当前进程状态 */
    fcntl(fd, F_SETFL, flags | FASYNC); /* 设置进程启动异步通知功能 */

    while (1) {
        sleep(2);
    }

    close(fd);

    return 0;
}
posted @ 2022-02-01 12:27  zhengcixi  阅读(1015)  评论(1编辑  收藏  举报
回到顶部