【genius_platform软件平台开发】第六十九讲:linux系统驱动开发之-系统和驱动之间的中断处理函数

在中断处理函数中,发送信号给应用层,以此来通知应用层处理响应的中断业务
请添加图片描述

1. 驱动程序

  • 创建相应目录和文件
$ cd /linux-4.15/drivers
$ mkdir my_driver_interrupt_signal
$ touch my_driver_interrupt_signal.c

1.1 my_driver_interrupt_signal.c

  • 文件内容如下
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/cdev.h>

#include <asm/siginfo.h>
#include <linux/pid.h>
#include <linux/uaccess.h>
#include <linux/sched/signal.h>
#include <linux/pid_namespace.h>
#include <linux/interrupt.h>

// 中断号
#define IRQ_NUM			1

// 定义驱动程序的 ID,在中断处理函数中用来判断是否需要处理	
#define IRQ_DRIVER_ID	1234

// 设备名称
#define MYDEV_NAME		"mydev"

// 驱动程序数据结构
struct myirq
{
    int devid;
};
 
struct myirq mydev  ={ IRQ_DRIVER_ID };

#define KBD_DATA_REG        0x60  
#define KBD_STATUS_REG      0x64
#define KBD_SCANCODE_MASK   0x7f
#define KBD_STATUS_MASK     0x80

// 设备类
static struct class *my_class;

// 用来保存设备
struct cdev my_cdev;

// 用来保存设备号
int mydev_major = 0;
int mydev_minor = 0;

// 用来保存向谁发送信号,应用程序通过 ioctl 把自己的进程 ID 设置进来。
static int g_pid = 0;

// 用来发送信号给应用程序
static void send_signal(int sig_no)
{
	int ret;
	struct siginfo info;
	struct task_struct *my_task = NULL;
	if (0 == g_pid)
	{
		// 说明应用程序没有设置自己的 PID
	    printk("pid[%d] is not valid! \n", g_pid);
	    return;
	}

	printk("send signal %d to pid %d \n", sig_no, g_pid);

	// 构造信号结构体
	memset(&info, 0, sizeof(struct siginfo));
	info.si_signo = sig_no;
	info.si_errno = 100;
	info.si_code = 200;

	// 获取自己的任务信息,使用的是 RCU 锁
	rcu_read_lock();
	my_task = pid_task(find_vpid(g_pid), PIDTYPE_PID);
	rcu_read_unlock();

	if (my_task == NULL)
	{
	    printk("get pid_task failed! \n");
	    return;
	}

	// 发送信号
	ret = send_sig_info(sig_no, &info, my_task);
	if (ret < 0) 
	{
	       printk("send signal failed! \n");
	}
}

//中断处理函数
static irqreturn_t myirq_handler(int irq, void * dev)
{
    struct myirq mydev;
    unsigned char key_code;
    mydev = *(struct myirq*)dev;	
	
	// 检查设备 id,只有当相等的时候才需要处理
	if (IRQ_DRIVER_ID == mydev.devid)
	{
		// 读取键盘扫描码
		key_code = inb(KBD_DATA_REG);
	
		if (key_code == 0x01)
		{
			printk("EXC key is pressed! \n");
			send_signal(SIGUSR1);
		}
	}	

	return IRQ_HANDLED;
}

// 当应用程序打开设备的时候被调用
static int mydev_open(struct inode *inode, struct file *file)
{
	printk("mydev_open is called. \n");
	return 0;	
}

// 设置函数
static long mydev_ioctl(struct file* file, unsigned int cmd, unsigned long arg)
{
	void __user *pArg;
	printk("mydev_ioctl is called. cmd = %d \n", cmd);
	if (100 == cmd)
	{
		// 说明应用程序设置进程的 PID 
		pArg = (void *)arg;
		if (!access_ok(VERIFY_READ, pArg, sizeof(int)))
		{
		    printk("access failed! \n");
		    return -EACCES;
		}

		// 把用户空间传过来的进程PID复制到内核驱动空间变量内
		if (copy_from_user(&g_pid, pArg, sizeof(int)))
		{
		    printk("copy_from_user failed! \n");
		    return -EFAULT;
		}
	}

	return 0;
}

static const struct file_operations mydev_ops={
	.owner = THIS_MODULE,
	.open  = mydev_open,
	.unlocked_ioctl = mydev_ioctl
};

// 驱动模块初始化
static int __init mydev_driver_init(void)
{
	int devno;
	dev_t num_dev;

	printk("mydev_driver_init is called. \n");

	// 注册中断处理函数
    if(request_irq(IRQ_NUM, myirq_handler, IRQF_SHARED, MYDEV_NAME, &mydev)!=0)
    {
        printk("register irq[%d] handler failed. \n", IRQ_NUM);
        return -1;
    }

	// 动态申请设备号(严谨点的话,应该检查函数返回值)
	alloc_chrdev_region(&num_dev, mydev_minor, 1, MYDEV_NAME);

	// 获取主设备号
	mydev_major = MAJOR(num_dev);
	printk("mydev_major = %d. \n", mydev_major);

	// 创建设备类
	my_class = class_create(THIS_MODULE, MYDEV_NAME);

	// 创建设备节点
	devno = MKDEV(mydev_major, mydev_minor);
	
	// 初始化cdev结构
	cdev_init(&my_cdev, &mydev_ops);

	// 注册字符设备
	cdev_add(&my_cdev, devno, 1);

	// 创建设备节点
	device_create(my_class, NULL, devno, NULL, MYDEV_NAME);

	return 0;
}

// 驱动模块卸载
static void __exit mydev_driver_exit(void)
{	
	printk("mydev_driver_exit is called. \n");

	// 删除设备节点
	cdev_del(&my_cdev);
	device_destroy(my_class, MKDEV(mydev_major, mydev_minor));

	// 释放设备类
	class_destroy(my_class);

	// 注销设备号
	unregister_chrdev_region(MKDEV(mydev_major, mydev_minor), 1);

	// 注销中断处理函数
	free_irq(IRQ_NUM, &mydev);
}

MODULE_LICENSE("GPL");
module_init(mydev_driver_init);
module_exit(mydev_driver_exit);
  • 以上代码主要做了两件事情:
注册中断号 1 的处理函数:myirq_handler();

创建设备节点 /dev/mydev;
  • 这里的中断号1,是键盘中断。因为它是共享的中断,因此当键盘被按下的时候,操作系统就会依次调用所有的中断处理函数,当然就包括我们的驱动程序所注册的这个函数。
  • 在中断处理函数中,目标是发送信号 SIGUSR1 到应用层,因此驱动程序需要知道应用程序的进程号(PID)。根据之前的文章【genius_platform软件平台开发】第六十八讲:linux系统驱动开发之-驱动程序发送信号给应用程序,应用程序必须主动把自己的 PID 告诉驱动模块才可以。这可以通过 write 或者ioctl函数都可以。知道了应用程序的 PID,驱动程序就可以在中断发生的时候(按下键盘ESC键),发送信号出去了:

1.2 Makefile 文件

ifneq ($(KERNELRELEASE),)
	obj-m := my_driver_interrupt_signal.o
else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
	PWD := $(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.o *.ko *.mod.* modules.* Module.* 
	$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif

1.3 编译、测试

首先查看一下加载驱动模块之前,1号中断的所有驱动程序:

图片

  • 再看一下设备号:
$ cat /proc/devices

在这里插入图片描述

  • 因为驱动注册在创建设备节点的时候,是动态请求系统分配的。根据之前的几篇文章可以知道,系统一般会分配244这个主设备号给我们,此刻还不存在这个设备号。
  • 编译、加载驱动模块:
$ make
$ sudo insmod my_driver_interrupt_signal.ko
  • 首先看一下 dmesg 的输出信息:
    在这里插入图片描述
  • 然后看一下中断驱动程序:
    在这里插入图片描述
  • 可以看到我们的驱动程序( mydev )已经登记在1号中断的最右面。
    最后看一下设备节点情况:
    在这里插入图片描述
    驱动模块已经准备妥当,下面就是应用程序了。

2. 应用程序

  • 应用程序的主要功能就是两部分:
通过 ioctl 函数把自己的 PID 告诉驱动程序;

注册信号 SIGUSR1 的处理函数;

2.1 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>


char *dev_name = "/dev/mydev";

// 信号处理函数
static void signal_handler(int signum, siginfo_t *info, void *context)
{
	// 打印接收到的信号值
    printf("signal_handler: signum = %d \n", signum);
    printf("signo = %d, code = %d, errno = %d \n",
	         info->si_signo,
	         info->si_code, 
	         info->si_errno);
}

int main(int argc, char *argv[])
{
	int fd, count = 0;
	int pid = getpid();

	// 打开GPIO
	if((fd = open(dev_name, O_RDWR | O_NDELAY)) < 0){
		printf("open dev failed! \n");
		return -1;
	}

	printf("open dev success! \n");
	
	// 注册信号处理函数
	struct sigaction sa;
	sigemptyset(&sa.sa_mask);
	sa.sa_sigaction = &signal_handler;
	sa.sa_flags = SA_SIGINFO;
	
	sigaction(SIGUSR1, &sa, NULL);

	// set PID 
	printf("call ioctl. pid = %d \n", pid);
	ioctl(fd, 100, &pid);

	// 死循环,等待接收信号
	while (1)
		sleep(1);

	// 关闭设备
	close(fd);
}
  • 在应用程序的最后,是一个 while(1) 死循环。因为只有在按下键盘上的ESC按键时,驱动程序才会发送信号上来,因此应用程序需要一直存活着。

2.2 编译、测试

  • 新开一个中断窗口,编译、执行应用程序:
$ gcc my_interrupt_singal.c -o my_interrupt_singal
$ sudo ./my_interrupt_singal
open dev success! 
call ioctl. pid = 12907
  • 这里进入 while 循环,由于应用程序调用了 openioctl 这两个函数,因此,驱动程序中两个对应的函数就会被执行。这可以通过 dmesg 命令的输出信息看出来
    在这里插入图片描述

  • 这个时候,按下键盘上的 ESC 键,此时驱动程序中打印如下信息:
    在这里插入图片描述

  • 说明:驱动程序捕获到了键盘上的 ESC 键,并且发送信号给应用程序了。

  • 在执行应用程序的终端窗口中,可以看到如下输出信息:
    在这里插入图片描述

  • 说明:应用程序接收到了驱动程序发来的信号!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值