字符设备驱动(8)-信号驱动,又名异步通知

        在前面的I/O模型中,用户态程序都是主动获取设备的资源信息,而异步通知是当设备资源可用时,有驱动主动通知用户态程序,在由用户态程序发起读写操作。这种机制和中断非常相似,可以借用中断的思路来理解这一过程。

用户态程序

         如上图,用户态程序的一般步骤是:

        (1)注册信号处理函数

        (2)设置设备文件的属主。为啥要设置属主呢?因为我们知道linux系统中,进程有单独的用户态空间,但所有的进程共享内核态空间,若不设置属主,驱动就不知道向哪个进程发送信号,所以这一步的目的是是使驱动根据文件的file结构,找到对应的进程,从而向该进程发送信号。

        (3)设置当设备资源可用时设备驱动向进程发出的信号,这一步不是必须的,但如果要使用sigaction的高级特性,这步就需要。

        (4)设置信号驱动标记,即一步通过fcntl的F_SETFL完成,设置FASYNC标记,这才真正启用异步通知机制,相当于中断使能。

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
 
#define FILE_NAME "/dev/mydev"
#define MAX_LEN   64
 
int fd;

void myhandler(int arg)
{
    int rlen;		
    char rbuf[MAX_LEN] = {0};

    rlen = read(fd, rbuf, 10);
    if (rlen < 0)
    {
    	perror("Read error");
    	return;
    }
    printf("Read buf is %s\n", rbuf);
}

int main(void)
{
    int ret,wlen,rlen,flag; 
    char wbuf[MAX_LEN] = "abcdefg";
    char rbuf[MAX_LEN] = {0};
    int i = 0;

    //注意以非阻塞方式打开文件
    fd = open(FILE_NAME, O_RDWR);
    if (0 > fd) 
    {   
        printf("Open failed.\n");
        return -1; 
    }   
     
    signal(SIGIO, myhandler);

    ret = fcntl(fd, F_SETOWN, getpid());
    if (0 > ret)
    {
        perror("Set owner");
		return ret;
	}
				
    flag = fcntl(fd, F_GETFL);
    if (0 > flag)
	{
		perror("Get flag");
		return ret;
	}

    ret = fcntl(fd, F_SETFL, flag | FASYNC);
    if (0 > ret)
	{
		perror("Set flag");
		return ret;
	}

    wlen = write(fd, wbuf, strlen(wbuf));
    if (wlen < 0)
    {
    	perror("Write error");
    	return wlen;
    }
    printf("Write buf is %s\n", wbuf);

    while (1) 
    {   
        
    }
    close(fd);
    return 0;
}

驱动程序

        进程设置异步通知标记时,要调用驱动的fasync方法,以便把这个变化通知驱动程序,使其能正确响应。文件被打开时,FASYNC标记默认是被清除的,我们要手动设置。

struct file_operations {
    int (*fasync) (int, struct file *, int);
}

        第一个参数是文件描述符。

        第二个参数是文件指针。

        第三个参数是添加或者删除标记。

        内核帮我们封装了一个函数fasync_helper函数,这个函数会构造fasync_struct节点,加入并假如到它的头中,函数原型如下:

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

        fasync的3个参数透传给fasync_helper函数,fasync_helper最后一个参数fasync_struct的二级指针,这里只需要传入fasync_struct的指针就行。

        当驱动准备好资源时,就执行kill_fasync函数通知用户态程序异步读取数据。

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

        第一个参数是和传入fasync_helper的同一个参数。

        第二个参数是信号ID,比如SIGIO。

        第三个参数是读出或者写入的标记,如POLL_IN、POLL_OUT。

        最后,当文件关闭时,需要显示调用驱动实现的fasync接口函数,将节点从链表中移除,这样进程就不会再收到信号。

关键程序如下:

//异步结构定义
struct fasync_struct *my_fapp;

static int my_fasync(int fd, struct file *flip, int mode);

static int my_close(struct inode *pnode, struct file *pfile)
{
    printk("Close cdev.\n");
    my_fasync(-1, pfile, 0);
    return 0;
}

//异步初始化
static int my_fasync(int fd, struct file *flip, int mode)
{
    return fasync_helper(fd, flip, mode, &my_fapp);
}

ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
    if (MAX_LEN < len)
    {
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
    //判断设备缓存是否为空
    if (0 == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
 
	ret =  copy_to_user(ubuf, kbuf, len);
    if (0 != ret)
    {   
        printk("Copy to user failed.\n");
        return -1;
    }
    
    //读出后,把对应的设备数据清空,保留未读出数据
    memcpy(kbuf, kbuf + len, MAX_LEN - len);
    memset(kbuf + MAX_LEN - len, 0, len);
 
    //计算设备缓存长度
    if (kbuf_len > len)
    	kbuf_len -= len;
    else
        kbuf_len = 0; 
    
    //当缓存长度不满时,唤醒写队列
    if (MAX_LEN != kbuf_len)
    {
        wake_up_interruptible(&my_write_queue);
        //发送可写通知
        kill_fasync(&my_fapp, SIGIO, POLL_OUT);
    }
	return len;
}
 
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
 
    //判断是否会写超过
    if (MAX_LEN < len + kbuf_len)
	{
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
	if (MAX_LEN == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
    
    ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
    if (0 != ret)
    {   
        printk("Copy from user failed.\n");
        return -1;
    }
    kbuf_len += len;
    
    //当缓存长度不为空时,唤醒读队列
    if (0 != kbuf_len)
    {
        wake_up_interruptible(&my_read_queue);
        //发送可读通知
        kill_fasync(&my_fapp, SIGIO, POLL_IN);
    }
    return len;
}

struct file_operations cdev_ops = {
    .open    = my_open,
    .release = my_close,
    .read    = my_read,
    .write   = my_write,
    .poll    = my_poll,
    .fasync  = my_fasync,
};

完整驱动程序

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/poll.h>

#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN    64 


//异步结构定义
struct fasync_struct *my_fapp;
static int my_fasync(int fd, struct file *flip, int mode);
 
static int my_open(struct inode *pnode, struct file *pfile)
{
    printk("Open cdev.\n");
    return 0;
}
 
static int my_close(struct inode *pnode, struct file *pfile)
{
    printk("Close cdev.\n");
    my_fasync(-1, pfile, 0);
    return 0;
}

static char kbuf[MAX_LEN] = {0};
static int kbuf_len = 0;
DECLARE_WAIT_QUEUE_HEAD(my_read_queue);
DECLARE_WAIT_QUEUE_HEAD(my_write_queue);
 
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
    if (MAX_LEN < len)
    {
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
    //判断设备缓存是否为空
    if (0 == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_read_queue, 0!= kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
 
	ret =  copy_to_user(ubuf, kbuf, len);
    if (0 != ret)
    {   
        printk("Copy to user failed.\n");
        return -1;
    }
    
    //读出后,把对应的设备数据清空,保留未读出数据
    memcpy(kbuf, kbuf + len, MAX_LEN - len);
    memset(kbuf + MAX_LEN - len, 0, len);
 
    //计算设备缓存长度
    if (kbuf_len > len)
    	kbuf_len -= len;
    else
        kbuf_len = 0; 
    
    //当缓存长度不满时,唤醒写队列
    if (MAX_LEN != kbuf_len)
    {
        wake_up_interruptible(&my_write_queue);
        //发送可写通知
        kill_fasync(&my_fapp, SIGIO, POLL_OUT);
    }
	return len;
}
 
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
	int ret = -1;
 
    //判断是否会写超过
    if (MAX_LEN < len + kbuf_len)
	{
        printk("len is large than %d.\n", MAX_LEN);
		return -1;
	}
	
	if (MAX_LEN == kbuf_len)
	{
        //如果是非阻塞操作,返回-EAGAIN,用户态轮询
		if (pf->f_flags& O_NONBLOCK)
			return -EAGAIN;
        else
        {
            //如果是阻塞操作,等待
            if (wait_event_interruptible(my_write_queue, MAX_LEN != kbuf_len))
                return -ERESTARTSYS; //信号唤醒,通知文件系统层做相应处理
        }
	}
    
    ret = copy_from_user(kbuf + kbuf_len, ubuf, len);
    if (0 != ret)
    {   
        printk("Copy from user failed.\n");
        return -1;
    }
    kbuf_len += len;
    
    //当缓存长度不为空时,唤醒读队列
    if (0 != kbuf_len)
    {
        wake_up_interruptible(&my_read_queue);
        //发送可读通知
        kill_fasync(&my_fapp, SIGIO, POLL_IN);
    }
    return len;
}

static unsigned int my_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    
    poll_wait(filp, &my_write_queue, wait);//加写等待队列头
    poll_wait(filp, &my_read_queue, wait);//加读等待队列头

    if (0 != kbuf_len)//可读
    {   
            mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
    }   
    
    if (MAX_LEN != kbuf_len)//可写
    {   
            mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
    }   

    return mask;
}


//异步初始化
static int my_fasync(int fd, struct file *flip, int mode)
{
    return fasync_helper(fd, flip, mode, &my_fapp);
}
 
struct cdev cdevice;
 
struct file_operations cdev_ops = {
    .open    = my_open,
    .release = my_close,
    .read    = my_read,
    .write   = my_write,
    .poll    = my_poll,
    .fasync  = my_fasync,
};
 
//加载
static int hello_init(void)
{ 
    dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
    int ret = -1;
	printk(KERN_ALERT "Hello World.\n");
    //up kernel
        //1、注册设备号
        ret = register_chrdev_region(devno, 1, "hello");
        if (0 != ret)
        {
            printk("Register char device failed.\n");
            return ret;
        }
    
        //2、初始化字符设备结构体
        cdev_init(&cdevice, &cdev_ops);
    
        cdevice.owner = THIS_MODULE;
	
        //3、添加字符设备结构体给内核
        ret = cdev_add(&cdevice,devno , 1);
        if (0 != ret)
        {   
            //注意释放设备号
            unregister_chrdev_region(devno,1);
            printk("Unregister char device.\n");
            return ret;
        }
 
        printk("Register char device success.\n");
    //down hardware
 
    return 0;
} 
 
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
    dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
 
    printk(KERN_ALERT "Goodbye World.\n");
    // down hardware
 
    // up kernel
        //1、从内核中删除字符设备结构体
        cdev_del(&cdevice);
 
        //2、注销设备号
        unregister_chrdev_region(devno, 1);
}
 
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
 
//license(必须)
MODULE_LICENSE("GPL");
 
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");

运行结果

$ sudo mknod /dev/mydev c 100 0
$ sudo insmod cdev.ko
$ sudo ./a.out
Write buf is abcdefg
Read buf is abcdefg

tobecontinue

​ 每周三、周六更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值