从零开始之驱动发开、linux驱动(十八、异步通知机制)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82793954

异步通知的意思是: 一旦设备就绪, 则驱动主动通知应用程序, 这样应用程序根本就不需要查询设备状态, 这一点非常类似于硬件上“中断”的概念, 比较准确的称谓是“信号驱动的异步I/O”。 信号是在软件层次上对中断机制的一种模拟, 在原理上, 一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。 信号是异步的, 一个进程不必通过任何操作来等待信号的到达, 事实上, 进程也不知道信号到底什么时候到达。
 

linux中的信号:

使用信号进行进程间通信(IPC) 是UNIX中的一种传统机制, Linux也支持这种机制。 在Linux中, 异步通知使用信号来实现(可以理解为异步通知是信号的一种情况), Linux中可用的信号及其定义如表:

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

信号的接受(应用层)

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler)

第一个参数指定信号的值。

第二个参数指定针对前面信号值的处理函数, 若为SIG_IGN, 表示忽略该信号; 若为SIG_DFL, 表示采用系统默认方式处理信号; 若为用户自定义的函数, 则信号被捕获到后, 该函数将被执行。
如果signal() 调用成功, 它返回最后一次为信号signum绑定的处理函数的handler值, 失败则返回SIG_ERR。
 

在进程执行时, 按下“Ctrl+C”将向其发出SIGINT信号, 正在运行kill的进程将向其发出SIGTERM信号。

这里先给出信号接收函数。(应用层)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>


int fd; 


void signal_handler(int signo)
{
    char buf[2];

    read(fd, buf, 1); 
    printf("buf = %d\n", buf[0]);

}

int main(int argc,char *argv[])
{
    int flag;
    
    fd = open("/dev/button", O_RDWR);
    if(fd < 0)
    {   
        printf("open /dev/%s fail\n",argv[1]);
        return -1;
    }

    /* 设置信号处理函数 */
    signal(SIGIO, signal_handler);

    /* 设置本进程为fd文件的拥有者 */
    fcntl(fd , F_SETOWN, getpid());

    /* 得到本文件的默认flag */
    flag = fcntl(fd, F_GETFL);
    /* 启动异步通知fd */
    fcntl(fd ,F_SETFL, flag | FASYNC);

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

1.通过signal(SIGIO,signal_handler) 设置对应IO信号的处理函数。

 signal(SIGIO, signal_handler);

2.设置本进程为fd文件的拥有者,没有这一步, 内核不会知道应该将信号发给哪个进程。

fcntl(fd , F_SETOWN, getpid());

3.给本文件的flag增加一个异步通知功能。

flag = fcntl(fd, F_GETFL);
fcntl(fd ,F_SETFL, flag | FASYNC);

上面设置的其实是下面标记的两个文件相关的参数


struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
#define f_dentry	f_path.dentry
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/*
	 * Protects f_ep_links, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		f_lock;
	atomic_long_t		f_count;
	unsigned int 		f_flags;        /* 存放文件的flag */
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
	struct fown_struct	f_owner;        /* 表明该文件当前属于那个进程 */
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;

	u64			f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;
	struct list_head	f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
} __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */


/* 描述文件的从属关系 */
struct fown_struct {
	rwlock_t lock;          /* protects pid, uid, euid fields */
	struct pid *pid;	/* pid or -pgrp where SIGIO should be sent  进行号 */
	enum pid_type pid_type;	/* Kind of process group SIGIO should be sent to */
	kuid_t uid, euid;	/* uid/euid of process setting the owner */
	int signum;		/* posix.1b rt signal to be delivered on IO */
};

为了使设备支持异步通知机制, 驱动程序中涉及3项工作。
1) 支持F_SETOWN命令, 能在这个控制命令处理中设置filp->f_owner为对应进程ID。 不过此项工作已由内核完成, 设备驱动无须处理。
2) 支持F_SETFL命令的处理, 每当FASYNC标志改变时, 驱动程序中的fasync() 函数将得以执行。因此, 驱动中应该实现fasync() 函数。
3) 在设备资源可获得时, 调用kill_fasync() 函数激发相应的信号。

驱动中的上述3项工作和应用程序中的3项工作是一一对应的。
 

其中fcntl函数我们这里不详细分析,这里只列出和上面有关的部分。

SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{	
	struct fd f = fdget_raw(fd);
	long err = -EBADF;

	if (!f.file)
		goto out;

	if (unlikely(f.file->f_mode & FMODE_PATH)) {
		if (!check_fcntl_cmd(cmd))
			goto out1;
	}

	err = security_file_fcntl(f.file, cmd, arg);
	if (!err)
		err = do_fcntl(fd, cmd, arg, f.file);    /* 执行这个 */

out1:
 	fdput(f);
out:
	return err;
}



static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
		struct file *filp)
{
	long err = -EINVAL;

	switch (cmd) {
	......

	case F_GETFL:
		err = filp->f_flags;        /* 得到标志 */
		break;
	case F_SETFL:
		err = setfl(fd, filp, arg);    /* 设置flag */
		break;

    ......
	case F_GETOWN:
		/*
		 * XXX If f_owner is a process group, the
		 * negative return value will get converted
		 * into an error.  Oops.  If we keep the
		 * current syscall conventions, the only way
		 * to fix this will be in libc.
		 */
		err = f_getown(filp);       /**/
		force_successful_syscall_return();
		break;
	case F_SETOWN:
		err = f_setown(filp, arg, 1);  /* 设置拥有者 */
		break;

	......
	default:
		break;
	}
	return err;
}



static int setfl(int fd, struct file * filp, unsigned long arg)
{
	struct inode * inode = file_inode(filp);
	int error = 0;

	/*
	 * O_APPEND cannot be cleared if the file is marked as append-only
	 * and the file is open for write.
	 */
	if (((arg ^ filp->f_flags) & O_APPEND) && IS_APPEND(inode))
		return -EPERM;

	/* O_NOATIME can only be set by the owner or superuser */
	if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME))
		if (!inode_owner_or_capable(inode))
			return -EPERM;

	/* required for strict SunOS emulation */
	if (O_NONBLOCK != O_NDELAY)
	       if (arg & O_NDELAY)
		   arg |= O_NONBLOCK;

	if (arg & O_DIRECT) {
		if (!filp->f_mapping || !filp->f_mapping->a_ops ||
			!filp->f_mapping->a_ops->direct_IO)
				return -EINVAL;
	}

	if (filp->f_op->check_flags)
		error = filp->f_op->check_flags(arg);
	if (error)
		return error;

	/*
	 * ->fasync() is responsible for setting the FASYNC bit.
	 */
	if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
		error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);    /* 在这里调用我们的驱动里面的函数 */
		if (error < 0)
			goto out;
		if (error > 0)
			error = 0;
	}
	spin_lock(&filp->f_lock);
	filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
	spin_unlock(&filp->f_lock);

 out:
	return error;
}

信号发送函数(异步通知的在驱动中发送)

#include <linux/fs.h>       /* 包含file_operation结构体 */
#include <linux/init.h>     /* 包含module_init module_exit */
#include <linux/module.h>   /* 包含LICENSE的宏 */
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/highmem.h> /* For wait_event_interruptible */
#include <linux/poll.h>
#include <asm/gpio.h>
#include <asm/uaccess.h>
#include <linux/sysctl.h>

static unsigned int major;
static struct class *button_class;
static struct device *button_dev;

static unsigned char key_val;

static struct fasync_struct *button_fasync = NULL;

struct pin_desc {
    unsigned int    pin;
    unsigned int    key_val;
};


/* 按下时 值分别是  0x01 , 0x02 */
/* 松开时 值分别是  0x00 , 0x00 */
static struct pin_desc pins_desc[] = {
    {S5PV210_GPH0(2), 0x01},
    {S5PV210_GPH0(3), 0x02},
};


static irqreturn_t irq_handler(int irq, void *dev_id)
{
    struct pin_desc *p = dev_id;
    int pin_val;

    pin_val =  gpio_get_value(p->pin);

    /* 得到键值,判断时按下还是松开 */
    if(pin_val)
    {
        /* 松开 */
        key_val &= ~p->key_val;
    }
    else
    {
        /* 按下 */
        key_val |= p->key_val;
    }

    /* 产生一个异步读信号 */
    kill_fasync(&button_fasync, SIGIO, POLL_IN);


    return IRQ_HANDLED;
}



/* open函数 */
static int button_drv_open(struct inode *inode, struct file *file)
{
    int ret = 0;

    ret = request_irq(IRQ_EINT(2), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint2",&pins_desc[0]);
    if(ret)
    {
        printk(KERN_ERR"request_irq IRQ_EINT(2) fail");
    }
    ret = request_irq(IRQ_EINT(3), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint3",&pins_desc[1]);
    if(ret)
    {
        printk(KERN_ERR"request_irq IRQ_EINT(3) fail");
    }
    return 0;
}


static ssize_t button_drv_read(struct file *file, char __user *array, size_t size, loff_t *ppos)
{
    int len;

    if(size < 1)
    {
        return -EINVAL;
    }

    /* 赋值只是为了消除告警 */
    len = copy_to_user(array , &key_val, 1);

    return 1;
}

int button_drv_fasync (int fd, struct file *file, int on)
{
    printk(KERN_INFO"button_drv_fasync\n");

    /* 增加一个异步通知到到本设备的异步通知队列中 */
    return fasync_helper(fd , file, on, &button_fasync);

}


static int button_drv_close(struct inode *inode, struct file *file)
{
    free_irq(IRQ_EINT(2), &pins_desc[0]);
    free_irq(IRQ_EINT(3), &pins_desc[1]);

    /* 移除一个async */
    fasync_helper(-1, file, 0, &button_fasync);

    return 0;
}

static const struct file_operations button_drv_file_operation = {
    .owner      = THIS_MODULE,
    .open       = button_drv_open,
    .read       = button_drv_read,
    .fasync     = button_drv_fasync,
    .release    = button_drv_close,
};

static int __init button_drv_init(void)
{
    /* 获取一个自动的主设备号 */    
    major =  register_chrdev(0,"button_drv",&button_drv_file_operation);
    if(major < 0)
    {   
        printk(KERN_ERR"register_chrdev button_drv fail \n");    
        goto err_register_chrdev;
    }   

    /* 创建一个类 */
    button_class = class_create(THIS_MODULE, "button_class");   
    if(!button_class)
    {   
        printk(KERN_ERR"class_create button_class fail\n");
        goto err_class_create;
    }   

    /* 创建从属这个类的设备 */
    button_dev = device_create(button_class,NULL,MKDEV(major, 0), NULL, "button");
    if(!button_dev)
    {   
        printk(KERN_ERR"device_create button_dev fail \n");
        goto err_device_create;
    }   

    return 0;

/* 倒影式错误处理机制 */
err_device_create:
    class_destroy(button_class);
err_class_create:
    unregister_chrdev(major,"button_drv");
err_register_chrdev:

    return -EIO;
}


static void __exit button_drv_exit(void)
{
    /* 注销类里面的设备 */
    device_unregister(button_dev);
    /* 注销类 */
    class_destroy(button_class);
    /* 注销字符设备 */
    unregister_chrdev(major,"button_drv");
}

module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");

我们上面程序中,先是应用程序设置当前设备的FASYNC标志,他会调用驱动里面的fasync函数,而驱动中的fasync则继续调用fasync_helper函数,里面申请一个struct fasync_struct,并把申请的struct fasync_struct加入到设备驱动中定义的struct 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.
 * 加入或移除异步通知对于该设备的异步通知链表
 */
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
	if (!on)
		return fasync_remove_entry(filp, fapp);
	return fasync_add_entry(fd, filp, fapp);
}

其中可以看到在一个文件支持异步通知时on传的是非0值,而移除异步通知功能时,是要传入0



/*
 * Add a fasync entry. Return negative on error, positive if
 * added, and zero if did nothing but change an existing one.
 */
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
	struct fasync_struct *new;

	new = fasync_alloc();        /* 申请空间 */
	if (!new)
		return -ENOMEM;

	/*
	 * fasync_insert_entry() returns the old (update) entry if
	 * it existed.
	 *
	 * So free the (unused) new entry and return 0 to let the
	 * caller know that we didn't add any new fasync entries.
        *  如果已经添加过,则返回1,释放掉新申请的 
	 */
	if (fasync_insert_entry(fd, filp, fapp, new)) {   
		fasync_free(new);
		return 0;
	}

	return 1;
}


/*
 * Insert a new entry into the fasync list.  Return the pointer to the
 * old one if we didn't use the new one.
 *
 * NOTE! It is very important that the FASYNC flag always
 * match the state "is the filp on a fasync list".
 */
struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{
        struct fasync_struct *fa, **fp;

	spin_lock(&filp->f_lock);
	spin_lock(&fasync_lock);
	for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
		if (fa->fa_file != filp)        /* 判断有没有已经添加过 */
			continue;

		spin_lock_irq(&fa->fa_lock);
		fa->fa_fd = fd;        
		spin_unlock_irq(&fa->fa_lock);
		goto out;        /* 已经添加过,则不用再添加,直接退出 */
	}

    /* 初始化并绑定到链表上 */
	spin_lock_init(&new->fa_lock);
	new->magic = FASYNC_MAGIC;
	new->fa_file = filp;
	new->fa_fd = fd;
	new->fa_next = *fapp;       
	rcu_assign_pointer(*fapp, new);    /
	filp->f_flags |= FASYNC;    /* 该文件,增加异步通知标志 */

out:
	spin_unlock(&fasync_lock);
	spin_unlock(&filp->f_lock);
	return fa;
}







int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp)
{
	struct fasync_struct *fa, **fp;
	int result = 0;

	spin_lock(&filp->f_lock);
	spin_lock(&fasync_lock);
	for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
		if (fa->fa_file != filp)
			continue;

		spin_lock_irq(&fa->fa_lock);
		fa->fa_file = NULL;    /* 异步通知链表中的该项先取消和文件关联 */
		spin_unlock_irq(&fa->fa_lock);

		*fp = fa->fa_next;        /* 绑定回掉函数 */
		call_rcu(&fa->fa_rcu, fasync_free_rcu);
		filp->f_flags &= ~FASYNC;    /* 取消该文件对应用进程的的异步通知功能 */
		result = 1;
		break;
	}
	spin_unlock(&fasync_lock);
	spin_unlock(&filp->f_lock);
	return result;
}


/**
 * struct callback_head - callback structure for use with RCU and task_work
 * @next: next update requests in a list
 * @func: actual update function to call after the grace period.
 */
struct callback_head {
	struct callback_head *next;
	void (*func)(struct callback_head *head);
};
#define rcu_head callback_head

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,通过fd找到用户具体信号处理函数) */
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
	/* First a quick test without locking: usually
	 * the list is empty.
	 */
	if (*fp) {
		rcu_read_lock();
		kill_fasync_rcu(rcu_dereference(*fp), sig, band);
		rcu_read_unlock();
	}
}

通过异步通知我们可以时应用程序不用阻塞,不用查询,得到按键数据。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82793954
今日推荐