Linux 驱动.之字符设备框架下按键(kill_fasync,fasync,fasync_helper,signal)中断异步信号通知应用层方式 不带设备树

参考原文链接:https://blog.csdn.net/weixin_45499326/article/details/130447521

一、目录
在这里插入图片描述

二、信号驱动的异步通知
异步通知的意思是:一旦设备就绪,则主动通知应用程序,非常类似于硬件上“中断”的概念,比较准确的的称谓是“信号驱动的异步I/O”。
1、阻塞I/O意味着应用程序需要一直等待设备可访问后才能访问。
2、非阻塞I/O中使用poll(),则需要循环地查询设备是否可访问,如能访问了再访问。
3、而异步通知则意味着设备会主动通知应用程序自身可访问了,之后用户再进行I/O处理。

在这里插入图片描述
不能说阻塞好,还是非阻塞好,使用场景不同,有不同的需求方式,select,poll,或信号异步通知,各有不同使用场景

三、linux异步通知编程

一、应用层
1、 什么是信号
信号是软中断,用于通知进程某个事件已经发生。进程可以选择如何响应信号:忽略、默认处理、自定义处理等。
常见信号有:SIGINT(键盘中断)、SIGKILL(强制终止)、SIGSTOP(暂停进程)、SIGCONT(继续运行进程)等。

信号是很短的信息,可以被发送到一个进程或者一组进程。发送给进程唯一信息通常是一个数,以此来标识信号。在标准信号中,对参数、消息或者其他的相随信息没有给予关注。

首先我们来了解一下Linux下的信号机制。
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源,它的主要问题是信号可能丢失。随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。我们可以使用 kill -l 命令查看当前系统支持的信号,需要注意的是不同的系统支持的信号是不一样的:
在这里插入图片描述

#include <singal.h>
在这里插入图片描述
在这里插入图片描述
信号的工作流程:
产生信号:通过 kill 命令向进程发送信号,或按 Ctrl+C 键盘中断。
信号递送:内核向目标进程递送信号。
信号捕捉:进程通过 signal() 函数捕捉信号,注册相应的信号处理函数。
信号处理:当信号到达进程时,如果该信号已被捕捉,则执行对应的信号处理函数。否则执行缺省处理动作。
信号返回:信号处理函数返回后,进程将继续执行被中断的代码

1、 信号接收函数signal原型
Linux下可以用signal()信号安装的函数, 其中signal()函数的原型如下:

#include <singal.h>
void (*signal(int sig, void (*func)(int)))(int);//函数指针,函数的成员也是一个函数指针

void (*func)(int) : func 是函数指针,指向的参数是 int,返回 void。
signal(int sig, void (func)(int)) : signal() 函数接受两个参数,第一个参数是信号值 sig,第二个参数是函数指针 func。
void (
)(int) : signal() 函数的返回值也是一个函数指针,返回 void。

所以,理解起来比较直观的原型是:如果把上面这个函数声明分解成两个部分就好理解了:

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

第一个参数指定信号的值,
第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

举个例子,我们可以这么调用:

void handler(int sig) { ... }

sign_handler_t  old_handler;

old_handler = signal(SIGINT, handler);

定义了信号处理函数 handler(),是将来要回调的函数,signal() 将 SIGINT 信号和 handler 函数关联
old_handler 保存 signal() 返回的旧信号处理函数指针,这样我们就可以在 handler 函数中调用 old_handler,实现链式处理

(2)代码测试

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

int g_sigstop = 0;

void signal_stop(int signum)
{
	if(SIGTERM == signum)
	{
		printf("SIGTERM signal detected\n");
	}
	else if(SIGALRM == signum)
	{
		printf("SIGALRM signal detected\n");
		g_sigstop = 1;
	}
}

void signal_code(int signum)
{
	if(SIGINT == signum)
	{
		printf("SIGINT(CTRL+C) signal\n");
	}
	else if(SIGSEGV == signum)
	{
		printf("SIGSEGV signal detected\n");
		exit(-1);
	}
}

int main(int argc, char *argv[])
{
	char              *ptr = NULL;

	signal(SIGTERM, signal_stop);//kill命令终止
	signal(SIGALRM, signal_stop);//alarm()

	signal(SIGSEGV, signal_code);//指针非法操作内存问题
	signal(SIGINT, signal_code);//指针非法操作内存问题

	printf("Program start running for 20 seconds...\n");
	alarm(20);

	while(!g_sigstop)
	{
		;
	}

	printf("Program start stop running...\n");
	printf("Invalid pointer operator will raise SIGSEGV signal\n");
	/*这是非法地使用了指针,报错段错误,于是会触发SIGSEGV信号,就会打印相关的调用函数*/
	*ptr = 'h';
	return 0;
}

中间运行的二十秒可以使用CTRL+C来验证SIGINT信号。我们打开新的终端执行killall signal命令可以看到SIGTERM执行结果:
在这里插入图片描述

2 、应用层 fcntl 函数
fcntl() 函数用于操控文件描述符的属性,fcntl() 是 linux系统调用,用于设置和修改文件属性,也常用于文件锁定。它提供了一系列的操作来控制文件,是文件 I/O 过程中重要的系统调用之一。 原型如下:

int fcntl(int fd, int cmd, ... /* arg */ );

fd:文件描述符
cmd:命令,确定要执行的操作
arg:第三个参数,根据cmd的不同,传入各种参数结构
在这里插入图片描述
示例代码:

获取文件描述符标志:
int flag;
flag = fcntl(fd, F_GETFL);

设置文件为非阻塞:
int flag = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);  

文件上锁(阻塞):
struct flock lock;
lock.l_type = F_WRLCK;  
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;  // 锁整个文件
fcntl(fd, F_SETLKW, &lock);

文件解锁:
struct flock lock;
lock.l_type = F_UNLCK;  
lock.l_start = 0; 
lock.l_whence = SEEK_SET;
lock.l_len = 0;   // 解锁整个文件
fcntl(fd, F_SETLK, &lock);

3 应用层信号驱动机制步骤
应用层完成SIGIO信号处理函数的注册后,并把设备文件的IO模式设置成信号驱动模式后,就可以去做其它事情了。驱动部分如果有事件发生,则会向应用层发信号SIGIO,这时应用层再切换到信号处理函数中去处理I/O工作。

第一步,完成信号处理函数
void xxx_handler(int signo){
//处理内容
}

第二步,通过signal函数注册信号处理函数,连接信号和信号处理函数
signal(SIGIO, input_handler);

第三步,通过F_SETOWN 控制命令设置该设备文件描述符 fd 的拥有者为当前进程,这样这个文件描述符就可以接收从设备驱动发出来的 SIGURG 和 SIGIO 信号。
fcntl(fd, F_SETOWN, getpid());

第四步,通过F_SETFL 控制命令设置设备文件以支持FASYNC,即异步通知模式。实质就是设置struct file里的f_flags。
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);

所以应用层的模板如下:

int main()
{
 int fd = open("/dev/xxxx",O_RDONLY);

 fcntl(fd, F_SETOWN, getpid());

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

 signal(SIGIO,xxxx_handler);

 //......
}
    
void xxxx_handle(int signo)
{//读写数据
    
}

二、 驱动层模板

由于信号发出的源头在设备的驱动端,因此,必须在设备驱动程序中增加信号释放的相关代码。使驱动能在合适的时机释放信号。
这样,在驱动程序中涉及3项工作:
1、支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。此项工作已由内核完成,设备驱动无须处理。
2、支持F_SETFL命令处理,每当FASYNC标志改变时,驱动程序中的fasync函数将得以执行。因此,驱动中应该实现fasync()函数。
3、在设备资源可获得时,调用kill_fasync()函数激发相应的信号。

在这里插入图片描述
设备驱动中异步通知编程,主要用到一项数据结构和两个函数。数据结构是fasync_struct结构体,两个函数分别是:
1、处理FSYNC标志变更的函数

#include <linux/fs.h>
int fasync_helper(int fd,  struct file *filp,  int on,   struct fasync_struct  **fapp)

2、释放信号用的函数

#include <linux/fs.h>
void kill_fasync(struct fasync_struct **fp, int sig, int band);

模板
1、在驱动的设备全局变量结构体里添加struct fasync_struct *async_queue
在这里插入图片描述
2、在struct file_operations 结构体内添加.fasync = xxx_fasync
在这里插入图片描述
3、完成xxx_fasync函数
在这里插入图片描述
5、在对应的I/O操作函数里写上信号释放函数kill_fasync(),这里列举xxx_read函数,在设备有数据时,发出SIGIO指令:

在这里插入图片描述

各个函数原型介绍

1、驱动层 实现fasync函数

为了与应用层配合,驱动层需要完成 struct file_operations的成员.fasync函数的实现。其所涉及到的相关函数一一详述如下:
.fasync 成员是file_operations结构体中的一个函数指针,指向一个异步通知函数。
这个函数的原型是:int (*fasync) (int fd, struct file *filp, int on)

fd 是文件的文件描述符
filp 是文件结构体指针
on 是一个布尔值,表示是否要注册/解注册异步通知

这个函数的作用是:
当on为真时,注册一个异步通知。这意味着当对这个文件进行写操作时,内核会通知注册的进程。
当on为假时,解除异步通知的注册。
举个例子,在字符设备驱动中,当读操作results在数据可读时,我们可以调用fasync函数来通知读进程数据已经准备就绪,可以读取了。这样可以避免读进程调用阻塞读函数,提高效率。

一个使用fasync的简单例子:

static int mydev_fasync(int fd, struct file *filp, int on) 
{

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

struct file_operations mydev_fops = {
    .read = mydev_read,
    .fasync = mydev_fasync, // 使用fasync函数
}

那么当有数据可读时,我们可以(比如在.write函数里)调用:kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
来通知注册的读进程数据准备就绪。

2、 fasync_helpe
fasync_helper() 是内核提供的一个方便函数,它简化了fasync() 的实现。它的原型是:

#include <linux/fs.h>
int fasync_helper(int fd,  struct file *filp,  int on,   struct fasync_struct  **fapp)
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为真时,在fapp链表中添加一个异步通知。这通常在file_operations的.fasync方法中调用。
当on为假时,从
fapp链表中删除一个异步通知。这通常在file_operations的.release方法中调用。
它会处理好链表操作,我们只需要传递&dev->async_queue这样的链表指针即可。
成功时返回0,失败返回-EIO。
如果要在struct file_operations结构的.release函数中释放异步通知结构对象: fasync_helper(-1, filp, 0, &dev->pasync_obj);

3、struct fasync_struct
在上面一节,当我们调用fasync_helper(fd, filp, 1, &dev->async_queue)时:

&dev->async_queue 是我们自己定义的一个链表结构体指针,原型:

struct fasync_struct *async_queue;

struct fasync_struct {
	spinlock_t		fa_lock;
	int			magic;                             //magic:用于检查该结构体是否有效,一般设为FASYNC
	int			fa_fd;                              //fa_fd: 触发异步通知的文件描述符
	struct fasync_struct	*fa_next;      /* fa_next: 链表指针,用于链接多个异步通知回调 */
	struct file		*fa_file;                  // fa_file: 关联的文件结构体指针
	struct rcu_head		fa_rcu;           // fa_rcu: RCU机制所需要的结构体,可以忽略
};

struct fasync_struct是用于表示单个异步通知回调的结构体,通过这些结构体,我们可以构建一个异步回调链表,并由内核提供的fasync_helper()和kill_fasync()来管理这个链表。

这个链表存放的是异步通知的回调函数和数据
当我们调用fasync_helper()并且on为1时,它会将filp添加到这个异步通知链表async_queue中
这意味着当我们调用kill_fasync()时,内核会调用该链表中的所有回调函数,并传入我们指定的信号量
所以,当我们说“把链表*fapp加入到异步通知”,意思是:

*fapp指向我们定义的异步通知回调链表,比如async_queue
调用fasync_helper(fd, filp, 1, &async_queue)会将filp (就是struct file )这个文件结构体的回调添加到该链表
这样,当我们调用kill_fasync()时,filp的回调就会被调用,实现异步通知的效果
所以,fasync_helper() 简化了异步通知回调的管理,我们只需要传递一个回调链表的指针,它会处理好回调的添加和删除

** 4、kill_fasync函数**
kill_fasync() 是内核提供的一个函数,用于异步通知注册的进程。
它的原码是:

#include <linux/fs.h>
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
    if (*fp) {
        rcu_read_lock();
        kill_fasync_rcu(rcu_dereference(*fp), sig, band);
        rcu_read_unlock();
    }
}

*fp 是我们的异步通知回调链表,struct fasync_struct ** 指向保存异步通知结构地址的指针,比如 &dev->async_queue
sig 是要发送的信号量,比如SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
band 指定哪种IO操作触发的通知,这里band参数可以填入以下值:
在这里插入图片描述
这个原型主要是调用了如下函数。这段代码定义在内核的fs/fctnl.c文件中。它的作用是遍历异步通知回调链表,并向每个回调发送SIGIO信号。

static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)    //fa指向异步通知回调链表,sig是SIGIO,band指定事件类型。
{

	while (fa) {        //循环遍历链表中的每个entry。
		struct fown_struct *fown;
		unsigned long flags;

		if (fa->magic != FASYNC_MAGIC) {                                     //校验fa的magic号,如果不正确则打印错误并返回。
			printk(KERN_ERR "kill_fasync: bad magic number in "
			       "fasync_struct!\n");
			return;
		}
		spin_lock_irqsave(&fa->fa_lock, flags);                        //获取fa的自旋锁,这是因为异步通知回调同时可能被不同线程访问。
		if (fa->fa_file) {                                                                //如果fa有效且与之关联的文件也存在:
			fown = &fa->fa_file->f_owner;                                  //获取文件fown结构
			
			if (!(sig == SIGURG && fown->signum == 0))     //检查信号是否是SIGURG,如果是但fown->signum为0则跳过(SIGURG有默认机制)
				send_sigio(fown, fa->fa_fd, band);       //调用send_sigio()发送SIGIO信号通知文件owner
		}
		spin_unlock_irqrestore(&fa->fa_lock, flags);       //释放锁,移动到链表下一个entry。
		fa = rcu_dereference(fa->fa_next);
	}
}

当异步事件发生时,驱动调用kill_fasync(),该函数会遍历fp链表中的所有回调,对每个回调,向对应的进程发送sig信号,这样,注册的进程就收到通知,进行后续IO操作

举个简单例子:

struct fasync_struct *async_queue;    

static int mydev_fasync(int fd, struct file *filp, int on)  
{
    if (fasync_helper(fd, filp, on, &async_queue))  
        return -EIO;  
    return 0;  
}

static ssize_t mydev_read(struct file *filp, ...)
{
    // ...
    kill_fasync(&async_queue, SIGIO, POLL_IN);  
    
    // ...
}

在这个例子中:当mydev_read()有数据可读时,它调用mydev_handler(),mydev_handler()调用kill_fasync(),传入&async_queue异步通知链表,kill_fasync()遍历该链表,对每个回调都发送SIGIO信号,注册的进程收到SIGIO信号,知道有数据可读,进行读取
所以,kill_fasync()是用来激活异步通知,通知注册进程某个事件已经发生的关键函数。当驱动有异步事件时,都需要调用kill_fasync()来通知进程。

四、编程实例

1、实例1
hisi芯片gpio接了一个key,通过边沿触发来,处理按键上升沿信号,fasync信号通知的方式,通知应用层。

按键按下,触发上升沿后,内核驱动,跑到中断处理函数button_dev_isr(),并通过kill_fasync()函数向应用发送信号SIGIO,应用注册的信号处理函数signal(SIGIO, button_signal_fun)被回调,读取key值并触发内核read函数执行,实验结果打印如下:

驱动层

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/interrupt.h> 
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/delay.h>

定义一些数据
//0x01  OUT 	
//0x00	 IN
//0x01   HIGH  
//0x00	 LOW
#define on 1
#define off 0

#define BUFFER_SIZE 50
char kbuf[BUFFER_SIZE];


//GPIO2_0    key
#define GPIO2_0_DIR  0x12142400  //gpio的方向寄存器
#define GPIO2_0_DATA 0x12142004  //gpio的数据寄存器

volatile unsigned int *pGPIO2_0_DIR;
volatile unsigned int *pGPIO2_0_DATA;

struct hi3559av100_button
{
	int dev_major ;
	struct class *cls;
	struct device *dev;
	int value;
};
struct hi3559av100_button *button_dev;

下面这些都是hisi的gpio操作方法,偏移量来找到对应的gpio
static unsigned int gpio_chip_num = 2;这个gpio是第二组的
module_param(gpio_chip_num, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_chip_num, "gpio chip num");

static unsigned int gpio_offset_num = 0;
module_param(gpio_offset_num, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_offset_num, "gpio offset num");

static unsigned int gpio_dir = 0;
module_param(gpio_dir, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_dir, "gpio dir");

static unsigned int gpio_out_val = 1;
module_param(gpio_out_val, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_out_val, "gpio out val");

static unsigned int gpio_irq_type = 0;
module_param(gpio_irq_type, uint, S_IRUGO);
MODULE_PARM_DESC(gpio_irq_type, "gpio irq type");

gpio的模式
/*
 * 0 - disable irq
 * 1 - rising edge triggered
 * 2 - falling edge triggered
 * 3 - rising and falling edge triggered
 * 4 - high level triggered
 * 8 - low level triggered
 */
/*
button don't support long press,only support short press
button init status is high
when press button ,vla is from high to low 
only support fall irq
*/


const int button_devid[2] = {1, 2};//if two num of button

static struct fasync_struct *button_async;

按键中断函数
//void button_handler(unsigned long data)
static irqreturn_t button_dev_isr(int irq, void *dev_id)
{
	printk("%s\n", __FUNCTION__);

	int button_num = *((int *)dev_id);
	int button_val[2] = {1,1};                            //init status is up and val is high
	static unsigned long button_val_backup[2]={1,1};      //init status is up and val is high
	unsigned int gpio_num=0;

	gpio_num = gpio_chip_num * 8 + gpio_offset_num;//因为gpio是通过偏移量得到的,具体gpio号,比如gpio2,3,4
	button_val[0] = gpio_get_value(gpio_num);             // interrupt happen ,read  button once

	switch(button_num)
    	{
        	case 1:
			if(button_val[0] != button_val_backup[0])          // old status and new status is different
			{
				button_val_backup[0] = button_val[0];	   // renew current buttton status
				mdelay(10);			           // 											
				button_val[0] = gpio_get_value(gpio_num);  //read buttton once again

				if((button_val[0] == button_val_backup[0]) && (button_val_backup[0] == 0))//new status and old status is same , and old status is low,button is be pressed
				{
					printk("button[0] has be pressed  value: %d\n", button_val[0]);
					
					kill_fasync(&button_async, SIGIO, POLL_IN);//通过内核提供的kill_fasync信号发送函数,发送按键信号给应用层
				
				}
				else if((button_val[0] == 1) && (button_val_backup[0] == 1))//new status and old status is same,and old status is low,button is be up
				{
					printk("button[0] has be up value: %d\n", button_val[0]);
					button_val[0] = 1;         //clean up
					button_val_backup[0] = 1;  //clean up
				}
			}
		break;
			
        	case 2:
		break;

		default :
		break;
	}
return IRQ_HANDLED;	
}

//DECLARE_TASKLET(button_tasklet,button_handler,0);

//static irqreturn_t button_dev_isr(int irq, void *dev_id)
//{
//	printk("%s\n", __FUNCTION__);
	
//	tasklet_schedule(&button_tasklet);
//	return IRQ_HANDLED;
//}

static int button_drv_open(struct inode *inode, struct file *filp)
{
	printk("%s\n", __FUNCTION__);

	unsigned int gpio_num;
	unsigned int irq_num;
    unsigned int irqflags = 0;

	irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
	irqflags |= IRQF_SHARED;
	gpio_num = gpio_chip_num * 8 + gpio_offset_num;//因为gpio是通过偏移量得到的,具体gpio号,比如gpio2,3,4

  if (gpio_request(gpio_num, NULL)) {
                pr_err("[%s %d]gpio_request fail! gpio_num=%d \n", __func__, __LINE__, gpio_num);
                return -1;
  }
 
if (gpio_direction_input(gpio_num)) {
		pr_err("[%s %d]gpio_direction_input fail!\n",__func__, __LINE__);
		gpio_free(gpio_num);
                return -1;
}
//申请中断,绑定中断处理函数
irq_num = gpio_to_irq(gpio_num);
if (request_irq(irq_num, button_dev_isr, irqflags,"gpio_dev_test", &button_devid[0])) { 
             	gpio_free(gpio_num);
                return -1;
}
//if (request_irq(irq_num, button_dev_isr, irqflags,"gpio_dev_test", &button_devid[1])) { 
        //    	gpio_free(gpio_num);
        //       return -1;
      // }
		 
	return 0;
}

static int button_drv_close(struct inode *inode, struct file *filp)
{
	printk("%s\n", __FUNCTION__);

	unsigned int gpio_num = 0;
	gpio_num = gpio_chip_num * 8 + gpio_offset_num;
	free_irq(gpio_to_irq(gpio_num), &button_devid[0]);
	//free_irq(gpio_to_irq(gpio_num), &button_devid[1]);
    gpio_free(gpio_num);
	return 0;
}
 
static ssize_t button_drv_read(struct file *filp,  char __user *ubuf, size_t count, loff_t *ppos)
{
	printk("%s\n", __FUNCTION__);
	int ret = -1;
	unsigned int gpio_num=0;
	unsigned int val=0;

	gpio_num = gpio_chip_num * 8 + gpio_offset_num;//因为gpio是通过偏移量得到的,具体gpio号,比如gpio2,3,4

	memset(kbuf, 0, sizeof(kbuf));
	val = gpio_get_value(gpio_num);
	kbuf[0] = val;
	printk("%s  kbuf:%d\n", __FUNCTION__,kbuf[0]);

	ret = copy_to_user(ubuf, kbuf, count);
	if (ret) {
		printk(KERN_ERR "copy_to_user fail\n");
		return -EINVAL;
	} else if(ret == 0) {
		printk(KERN_INFO "copy_to_user success\n");
	}

	return ret;
}
 
static ssize_t button_drv_write(struct file *file, const char __user *ubuf,size_t count, loff_t *ppos)
{
	int ret = -1;	
	printk("%s\n", __FUNCTION__);
	memset(kbuf, 0, sizeof(kbuf));

	ret = copy_from_user(kbuf, ubuf, count);
	if(ret) {
		printk(KERN_ERR "copy_from_user fail\n");
		return -EINVAL;
	} else if(ret == 0) {
		printk(KERN_INFO "copy_from_user success\n");
	}

//method1
	if (!strcmp(kbuf, "1")) {
		printk("method1 %s  kbuf:%s\n", __FUNCTION__,kbuf);
	} else if (!strcmp(kbuf, "0")) {
		printk("method2 %s  kbuf:%s\n", __FUNCTION__,kbuf);
	} else {	
		printk(KERN_INFO "method1 unknow cmd\n");
	}
		
//method2
	if (!strcmp(kbuf, "on")) {
		printk("method2 %s  kbuf:%s\n", __FUNCTION__,kbuf);
	} else if (!strcmp(kbuf, "off")) {
		printk("method2 %s  kbuf:%s\n", __FUNCTION__,kbuf);
	} else {	
		printk(KERN_INFO "method2 unknow cmd\n");
	}	
	return ret;
}

static long button_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
		case on :
			//button on
			printk("%s button on\n",__FUNCTION__);
			//*pGPIO2_0_DATA |= (0x1<<0);
			break;
	
		case off :
			//button off
			printk("%s button off\n",__FUNCTION__);
			//*pGPIO2_0_DATA &= ~(0x1<<0); 
			break;
		default : 
			printk("%s unkown cmd\n",__FUNCTION__);
			return -EINVAL;		
	}	
	return 0;
}

static int button_drv_fasync (int fd, struct file *filp, int fasyncon)
{
	printk("%s\n", __FUNCTION__);
	return fasync_helper (fd, filp, fasyncon, &button_async);
}

const struct file_operations button_fops = {
	.open = button_drv_open,
	.read = button_drv_read,
	.write = button_drv_write,
	.release = button_drv_close,
	.unlocked_ioctl = button_drv_ioctl,
	.fasync	 =  button_drv_fasync,
};
 
static int __init button_drv_init(void)
{	
	int ret;
 	printk("%s\n", __FUNCTION__);

	button_dev = kzalloc(sizeof(struct hi3559av100_button), GFP_KERNEL);
	if(button_dev == NULL) {
		printk(KERN_ERR,"kzalloc error\n");
		return -ENOMEM;
	}
		
	button_dev->dev_major = 0;
	button_dev->dev_major = register_chrdev(button_dev->dev_major, "button",  &button_fops);
	if(button_dev->dev_major < 0) {
		printk("register_chrdev error\n");
		ret = -EINVAL;
		goto err_free;
	}
 
	button_dev->cls = class_create(THIS_MODULE,"button");
	if(IS_ERR(button_dev->cls)) {
		printk("class_create error\n");
		ret = PTR_ERR(button_dev->cls);
		goto err_unregister;		
	}

	button_dev->dev = device_create(button_dev->cls, NULL,MKDEV(button_dev->dev_major, 0), NULL, "button");
	if(IS_ERR(button_dev->dev)) {
		printk("device_create error\n");
		ret = PTR_ERR(button_dev->dev);
		goto err_class_destroy;		
	}

	return 0;
 
err_class_destroy:
	class_destroy(button_dev->cls);
 
err_unregister:
	unregister_chrdev(button_dev->dev_major, "button");
 
err_free:
	kfree(button_dev);
	return ret;
}
 
 
static void __exit button_drv_exit(void)
{
	printk("%s\n", __FUNCTION__);

	device_destroy(button_dev->cls, MKDEV(button_dev->dev_major, 0));	
	class_destroy(button_dev->cls);	
	unregister_chrdev(button_dev->dev_major, "button"); 
	kfree(button_dev);
}
 
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL");

二:应用
通过按键,依次点亮led灯

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <signal.h>

int fd0 = -1;
int fd1 = -1;
int fd2 = -1;

应用层的,按键处理函数
void button_signal_fun(int signum)
{
	unsigned char key_val = 0;
	static int led_index = 0;
	read(fd2, &key_val, 1);
	printf("app reas key_val: %d\n", key_val);

	if(!key_val)
	{
		led_index ++;
		if(led_index > 2) {
			led_index = 0;
		}
	}

	switch(led_index)
	{
		case 0 :
			write(fd0,"on",2);
			write(fd1,"off",3);
		break;

		case 1 :
			write(fd0,"off",3);
			write(fd1,"on",2);
		break;

		case 2 :
			write(fd0,"on",2);
    		write(fd1,"on",2);
		break;
		default:
		break;
			
	}
}

int main()
{

    char *filename0 = NULL;
	char *filename1 = NULL;
	char *filename2 = NULL;
    char buf[100]={0};
	int ret = -1;
	int Oflags = 0;

    filename0 = "/dev/led_green";
	filename1 = "/dev/led_red";   
	filename2 = "/dev/button";

	//led0
    	fd0 = open(filename0, O_RDWR);
   	if(fd0 < 0) {
        	printf("app open led_green fail\n");
        	return 0;
    	} else {
		printf("app open led_green success\r\n");
    	}
	//led1
	fd1 = open(filename1, O_RDWR);
   	if(fd1 < 0) {
        	printf("app open led_red fail\n");
        	return 0;
    	} else {
		printf("app open led_red success\r\n");
    	}

	//button
    	fd2 = open(filename2, O_RDWR);
   	if (fd2 < 0) {
        	printf("app open %s fail\n", filename2);
        	return 0;
    	} else {
		printf("app open  %s success\r\n",filename2);
    	}
	向内核注册绑定SIGIO信号,应用层捕获收到内核的信号,处理动作
	signal(SIGIO, button_signal_fun);

    内核按键驱动有个fasync函数,fcntl这样设置时,就会FASYNC可以捕获信号SIGIO
	fcntl(fd2, F_SETOWN, getpid());
	Oflags = fcntl(fd2, F_GETFL);
	fcntl(fd2, F_SETFL, Oflags | FASYNC);
	
	//默认点亮led
	write(fd0,"on",2);   //led0 on 
	
	while(1) 
	{
		sleep(1);
	}
	
	close(fd0);
	close(fd1);
	close(fd2);
   	return 0;
}

2、实例2

#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
/* by 面朝大海0902 */
struct gpio_key{
    int gpio;
    struct gpio_desc *gpiod;
    int flag;
    int irq;
};

static struct gpio_key *ptr_gpio_key;

static int major =0;
static struct class *key_class;
static int g_key_value = 0;
struct fasync_struct *key_fasync;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
    return (r == w);
}

static int is_key_buf_full(void)
{
    return (r == NEXT_POS(w));
}

static void put_key(int key)
{
    if (!is_key_buf_full())
    {
        g_keys[w] = key;
        w = NEXT_POS(w);
    }
}

static int get_key(void)
{
    int key = 0;
    if (!is_key_buf_empty())
    {
        key = g_keys[r];
        r = NEXT_POS(r);
    }
    return key;
}

static DECLARE_WAIT_QUEUE_HEAD(key_wait);

static int key_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    wait_event_interruptible(key_wait, !is_key_buf_empty());
    g_key_value = get_key();
    copy_to_user(buf, &g_key_value, 4);
    return 4;
}
/* by 面朝大海0902 */

static unsigned int key_drv_poll(struct file *fp, poll_table * wait)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    poll_wait(fp, &key_wait, wait);//非阻塞函数
    
    return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int key_drv_fasync(int fd, struct file *file, int on)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    if(fasync_helper(fd, file, on, &key_fasync) >= 0)
        return 0;
    else
        return -EIO;
}

static struct file_operations key_drv =
{
    .owner = THIS_MODULE,
    .read  = key_drv_read,
    .poll  = key_drv_poll,
    .fasync = key_drv_fasync,
};
/* by 面朝大海0902 */
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    int value;
    struct gpio_key *ptr_gpio_key_temp = dev_id;
    value = gpiod_get_value(ptr_gpio_key_temp->gpiod);
    g_key_value = (ptr_gpio_key_temp->gpio << 8) | value;
    printk(KERN_INFO "g_key_value is %d \r\n", g_key_value);
    put_key(g_key_value);
    wake_up_interruptible(&key_wait);
    kill_fasync(&key_fasync, SIGIO, POLLIN);
    return IRQ_HANDLED;
}

static int key_probe(struct platform_device *pdev)
{
    int count = 0;
    int i=0;
    enum of_gpio_flags flag;
    struct device_node *node = pdev->dev.of_node;
    
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    count = of_gpio_count(node);
    ptr_gpio_key = kzalloc(sizeof(struct gpio_key)*count, GFP_KERNEL);
    
    for(i=0;i<count;i++)
    {
        ptr_gpio_key[i].gpio = of_get_gpio_flags(node, i, &flag);
        if(ptr_gpio_key[i].gpio < 0)
        {
            printk(KERN_ERR "of_get_gpio_flags is err\r\n");
        }
        ptr_gpio_key[i].gpiod = gpio_to_desc(ptr_gpio_key[i].gpio);
        ptr_gpio_key[i].flag = flag & OF_GPIO_ACTIVE_LOW;
        ptr_gpio_key[i].irq = gpio_to_irq(ptr_gpio_key[i].gpio);
        request_irq(ptr_gpio_key[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_key", &ptr_gpio_key[i]);
    }
    
    major = register_chrdev(0, "my_keydrv", &key_drv);
    key_class = class_create(THIS_MODULE, "my_key_class");
    if(IS_ERR(key_class))
    {
        printk(KERN_ERR "class_create is err\r\n");
    }
    
    device_create(key_class, NULL, MKDEV(major, 0), NULL, "my_key%d", 0);
    
    return 0;    
}

static int key_remove(struct platform_device *pdev)
{
    int count = 0;
    int i = 0;
    struct device_node *node = pdev->dev.of_node;

    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    count = of_gpio_count(node);

    device_destroy(key_class, MKDEV(major, 0));
    class_destroy(key_class);
    unregister_chrdev(major, "my_keydrv");
    for(i=0;i<count;i++)
    {
        free_irq(ptr_gpio_key[i].irq, &ptr_gpio_key[i]);
    }

    kfree(ptr_gpio_key);
    return 0;    
}
/* by 面朝大海0902 */

static const struct of_device_id my_key[] =
{
    {.compatible = "my,key_driver"},
    {},
};

/* by 面朝大海0902 */
static struct platform_driver key_driver =
{
    .probe  = key_probe,
    .remove = key_remove,
    .driver = 
    {
        .name = "key_gpio",
        .of_match_table = my_key,
    },
};


static int __init my_key_init(void)
{
    int result;
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    result = platform_driver_register(&key_driver);
    return result;
}

/* by 面朝大海0902 */
static void __exit my_key_exit(void)
{
    printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    platform_driver_unregister(&key_driver);
}

module_init(my_key_init);
module_exit(my_key_exit);

MODULE_LICENSE("GPL");

3、实例3
驱动层

/*************************************************************************
	> File Name:fasync-memory-1.c
    驱动程序采用信号异步通知的驱动方式
 ************************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/poll.h>
/*1、定义重要的变量及结构体*/

#define  MEM_SIZE 500       //每个虚拟设备内存大小
#define DEV_NUM 3           //创建的设备总个数

struct mem_dev_t{
    struct cdev  my_dev;  //cdev设备描述结构体变量
    char  mem[MEM_SIZE]; //fifo内存池,当成虚拟设备
    int curpos;          //内存当前数据最后位置指示,从0开始记
    struct semaphore sem; //信号量
    wait_queue_head_t write_queue;  //写等待队列
    wait_queue_head_t read_queue;   //读等待队列
    struct fasync_struct *async_queue  ;   //异步通知链表队列元素
};

struct mem_dev_t *mem_dev;

/*所有驱动函数声明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
ssize_t write (struct file *, const char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
int fasync (int, struct file *, int);
unsigned int poll (struct file *, struct poll_table_struct *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
    .open = open,
    .release = release,
    .read = read,
    .write = write,
    .poll = poll,
    .fasync = fasync,
};

/*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
/*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
static int cdev_setup(struct mem_dev_t *mem_dev , dev_t devno ){
    int unsucc =0;
    cdev_init(&mem_dev->my_dev , &fops);
    mem_dev->my_dev.owner = THIS_MODULE;
    /*4、注册cdev结构体到内核链表中*/
    unsucc = cdev_add(&mem_dev->my_dev , devno , 1);
    if (unsucc){
        printk("driver : cdev add faild \n");
        return -1;
    }
    sema_init( &mem_dev->sem,1); //初始化信号量,为1
    mem_dev->curpos = 0;     //初始化缓冲数据位置为0
    init_waitqueue_head(&mem_dev->write_queue);
    init_waitqueue_head(&mem_dev->read_queue);
    return 0;
}

static int __init my_init(void){
    int major , minor;
    dev_t devno;
    int unsucc =0;
    int i=0;
    mem_dev = kzalloc(sizeof(struct mem_dev_t)*DEV_NUM , GFP_KERNEL);
    if (!mem_dev){
        printk(" driver : allocating memory is  failed");
        return  -1;
    }
    /*2、创建 devno */
    unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "select_memory");
    if (unsucc){
        printk(" driver : creating devno  is failed\n");
        return -1;
    }else{
        major = MAJOR(devno);
        minor = MINOR(devno);
        printk("diver : major = %d  ; minor = %d\n",major,minor);
    }
    /*3、 初始化cdev结构体,并联cdev结构体与file_operations.*/
    /*4、注册cdev结构体到内核链表中*/
    for (i=0;i<DEV_NUM;i++){
        devno = MKDEV(major , i);
        if (cdev_setup(mem_dev+i , devno) == 0){
            printk("deiver : the driver select_memory[%d]  initalization completed\n", i);
        } else
            printk("deiver : the driver select_memory[%d]  initalization failed\n", i);
    }
    return 0;
}

static void  __exit my_exit(void)
{
    int i=0;
    dev_t devno;
    devno = mem_dev->my_dev.dev;
    for (i=0 ; i<DEV_NUM ; i++){

        cdev_del(&(mem_dev+i)->my_dev);
        
    }
    unregister_chrdev_region(devno , DEV_NUM);
    printk("***************the driver operate_memory exit************\n");
}

/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/

int open(struct inode *pnode , struct file *pf){
    int minor = MINOR(pnode->i_rdev);
    int major = MAJOR(pnode->i_rdev);
    struct mem_dev_t *p = container_of(pnode->i_cdev , struct mem_dev_t , my_dev);
    pf->private_data = p;    //把全局变量指针放入到struct file结构体里
    

    if (pf->f_flags & O_NONBLOCK){    //非阻塞
        printk("driver : select_memory[%d , %d] is opened by nonblock mode\n",major , minor);
    }else{
        printk("driver : select_memory[%d , %d] is opened by block mode\n",major,minor);
    }
    return 0;
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
    struct mem_dev_t *p = pf->private_data;
    printk("select_memory is closed \n");
    if (&p->async_queue)
        fasync_helper(-1, pf , 0 , &p->async_queue);
    return 0;
}
/*file_operations结构全成员函数.read的具体实现*/
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
    //本例中,因为是fifo,所以ppos参数不用。
    struct mem_dev_t *pdev = pf->private_data;
    int count = 0;   //存储读到多少数据
    int ret = 0;

    DECLARE_WAITQUEUE(wait , current); //定义等待队列项目元素    
    down(&pdev->sem);
    add_wait_queue(&pdev->read_queue , &wait); //把元素加入读等待队列
    while (pdev->curpos == 0){
        if ((pf->f_flags & O_NONBLOCK) == 0){
            //当前没有数据,进入阻塞睡眠
            set_current_state(TASK_INTERRUPTIBLE); //设置当前进程为可中断睡眠态
            up(&pdev->sem);   //退出前释放信号量,V操作
            schedule();    //调度程序
        }else{
            ret = 0;
            goto out;
        }
        down(&pdev->sem);
    }
    //判断能够读到的字节数量
    printk ("begin  size=%d , curpos = %d\n" , size , pdev->curpos);
    if  (size > pdev->curpos){
        count = pdev->curpos;
    }else{
        count = size;
    }
    //copy_from_user返回值大于0失败
    if ( copy_to_user(buf , &pdev->mem , count )){  //读取失败
        ret = 0;
        goto out;
    }else{                                                  //成功读取
        memcpy(&pdev->mem , &pdev->mem[count] , pdev->curpos-count);
        pdev->curpos -= count;
        printk ("begin  count =%d , curpos = %d\n" , count , pdev->curpos);

        up(&pdev->sem); //退出前释放信号量,V操作

        wake_up_interruptible(&pdev->write_queue); //唤醒可能睡眠的write
        ret = count;
    }
    out:
        up(&pdev->sem);   //退出前释放信号量,V操作
        remove_wait_queue(&pdev->read_queue , &wait);
        set_current_state(TASK_RUNNING);
        return ret;
}
/*file_operations结构全成员函数.write的具体实现*/
ssize_t write (struct file * pf, const char __user *buf, size_t size , loff_t *ppos){
    struct mem_dev_t *pdev = pf -> private_data;
    int count =0;
    int ret = 0;
    DECLARE_WAITQUEUE(wait , current); //定义等待队列项目元素    
    down(&pdev->sem);
    add_wait_queue(&pdev->write_queue , &wait); //把元素加入读等待队列
    while (pdev->curpos == (MEM_SIZE -1)){
        if ((pf->f_flags & O_NONBLOCK) == 0){
            set_current_state(TASK_INTERRUPTIBLE);
            up(&pdev->sem);
            schedule();
        }else{
            ret = 0;
            goto out;
        }    
        down(&pdev->sem);
    }
    if (size > (MEM_SIZE-pdev->curpos)){
        count = MEM_SIZE-pdev->curpos;
    }else{
        count = size;
    }
    if (copy_from_user(&pdev->mem[pdev->curpos],buf,count)){
        ret = 0;
        goto out;
    }else{
        pdev->curpos +=count;
        wake_up_interruptible(&pdev->read_queue);
		if (&pdev->async_queue)
        	kill_fasync(&pdev->async_queue , SIGIO, POLL_IN);
        ret = count;
    }
    out:
        up(&pdev->sem);
        remove_wait_queue(&pdev->write_queue, &wait);
        set_current_state(TASK_RUNNING);
        return ret;
}
unsigned int poll (struct file *pf, struct poll_table_struct *  pts){
    struct mem_dev_t *p = pf->private_data;
    unsigned int mark = 0;
    poll_wait(pf, &p->read_queue , pts);
    poll_wait(pf , &p->write_queue , pts);
    //测试这里是否是阻塞还是轮询
    //printk("poll in waiting......or .....poll.");
    //
    if (p->curpos > 0){
        mark |= POLLIN | POLLRDNORM;
    }
    #if 0
    if (p->curpos < MEM_SIZE-1){
        mark |= POLLOUT | POLLWRNORM;
    }
    #endif
    return mark;
}

int fasync (int fd, struct file *pfile, int on){
    struct mem_dev_t *p = pfile->private_data;
    return fasync_helper(fd , pfile , on , &p->async_queue);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");


应用层

/*************************************************************************
	> File Name: op_mem.c
 ************************************************************************/

#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <signal.h>

int fd = 0;

void sign_handler(int sign){
    char buf[20] = {0};
    if (sign = SIGIO){
        read(fd , buf , 20);
        printf ("buf = %s\n", buf);
    }
}
int  main(int argc , char **argv){
    unsigned int flag;
    if  (argc < 2){
        printf("argument is  less!\n");
        return 0;
    }

    fd = open(argv[1] , O_RDWR|O_APPEND  );
    if (fd < 0){
        perror("open ");
    }
    signal(SIGIO , sign_handler);
    fcntl(fd , F_SETOWN , getpid());
    flag = fcntl(fd , F_GETFL);
    flag |= FASYNC;
    fcntl(fd , F_SETFL , FASYNC);

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值