• 大家好,我是峰哥,今天给大家解说一下:驱动层发送信号给应用程序。在上一篇文章中,我讲过:应用层发送指令来控制驱动层的GPIO状态,【genius_platform软件平台开发】第六十七讲:linux系统驱动开发之-GPIO设备驱动编写,控制的方向是从应用层到驱动层。

1. kill 命令和信号

  • 简单讲下kill命令和信号,是Linux 操作系统的信号,来“杀死”一个进程的命令:
$ kill -9 <进程的 PID>
  • 指令功能是:向指定的某个进程发送一个信号 9,这个信号的默认功能是:是停止进程。虽然在应用程序中没有主动处理这个信号,但是操作系统默认的处理动作是终止应用程序的执行。除了发送信号 9,kill 命令还可以发送其他任意信号。在 Linux 系统中,所有的信号都使用一个整型数值来表示,可以打开文件 /usr/include/x86_64-linux-gnu/bits/signum.h(你的系统中可能位于其它的目录) 查看一下,比较常见的几个信号是:
/* Signals.  */
#define SIGINT      2   /* Interrupt (ANSI).  */
#define SIGKILL     9   /* Kill, unblockable (POSIX).  */
#define SIGUSR1     10  /* User-defined signal 1 (POSIX).  */
#define SIGSEGV     11  /* Segmentation violation (ANSI).  */
#define SIGUSR2     12  /* User-defined signal 2 (POSIX).  */
...
...
#define SIGSYS      31  /* Bad system call.  */
#define SIGUNUSED   31#define   _NSIG       65  /* Biggest signal number + 1(including real-time signals).  *//* These are the hard limits of the kernel.  These values should not beused directly at user level.  */
#define __SIGRTMIN  32
#define __SIGRTMAX  (_NSIG - 1)

信号 9 对应着 SIGKILL,而信号11(SIGSEGV)就是最令人讨厌的Segmentfault

2. 实时信号和非实时信号

2.1 非实时信号

  • 操作系统不确保应用程序一定能接收到(即:信号可能会丢失);从文件 signum.h 中可以看到,实时信号从 __SIGRTMIN(数值:32) 开始。

2.2 实时信号

  • 操作系统确保应用程序一定能接收到;

3. 多线程中信号

  • 我们在编写应用程序时,虽然没有接收并处理 SIGKILL 这个信号,但是一旦别人发送了这个信号,我们的程序就被操作系统停止掉了,这是默认的动作。那么,在应用程序中,应该可以主动声明接收并处理指定的信号,下面就来写一个最简单的实例。

  • 在一个应用程序中存在多个线程时;当有一个信号发送给此进程时,所有的线程都可能接收到,但是只能有一个线程来处理;

4. 信号注册和处理函数

// 文件:app_handle_signal.c#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>// 信号处理函数
static void my_signal_handler(int signum, siginfo_t *info, void *context)
{// 打印接收到的信号值printf("my_signal_handler: signum=[%d] info=[%p] context=[%p]\n", signum, info, context);
}int main(void)
{int count = 0;// 注册信号处理函数struct sigaction sa;sigemptyset(&sa.sa_mask);sa.sa_sigaction = &my_signal_handler;sa.sa_flags = SA_SIGINFO;sigaction(SIGUSR1, &sa, NULL);sigaction(SIGUSR2, &sa, NULL);// 一直循环打印信息,等待接收发信号while (1){printf("my_app_handle_signal is running...count = %d \n", ++count);sleep(5);}return 0;
}
  • 这个示例程序接收的信号是 SIGUSR1SIGUSR2,也就是数值 1012。编译、执行:
$ gcc app_handle_signal.c -o app_handle_signal
$ ./app_handle_signal

  • 此时,应用程序开始执行,等待接收信号。在另一个终端中,使用kill指令来发送信号SIGUSR1或者 SIGUSR2kill 发送信号,需要知道应用程序的 进程PID,可以通过指令: ps -au | grep kill_cmd_and_signal 来查看。执行发送信号SIGUSR1指令:
$ kill -10 34037
  • 此时,在应用程序的终端窗口中,就能看到下面的打印信息
  • 说明应用程序接收到了 SIGUSR1 这个信号!注意:我们是使用kill命令来发送信号的,kill 也是一个独立的进程,程序的执行路径如下:操作系统是如何接收kill的操作,然后如何发送信号给 kill_cmd_and_signal 进程的,我们不得而知

5. 驱动程序

5.1 功能需求

  • 在刚才的简单示例中,可以得出下面这些信息:
信号发送方:必须知道向谁[PID]发送信号,发送哪个信号;
信号接收方:必须定义信号处理函数,并且向操作系统注册:接收哪些信号;
  • 发送方当然就是驱动程序了,在示例代码中,继续使用 SIGUSR1 信号来测试。那么,驱动程序如何才能知道应用程序的PID呢?可以让应用程序通过oictl函数,把自己的PID主动告诉驱动程序:

5.2 驱动程序

$ cd linux-4.15/drivers/
$ mkdir my_driver_signal
$ cd my_driver_signal
$ touch my_driver_signal.c

my_driver_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>// 新增部分,使用这个宏控制起来
#define MY_SIGNAL_ENABLE// 设备名称
#define MYGPIO_NAME         "mygpio"// 一共有4个GPIO
#define MYGPIO_NUMBER       4// 设备类
static struct class *gpio_class;// 用来保存设备
struct cdev gpio_cdev[MYGPIO_NUMBER];// 用来保存设备号
int gpio_major = 0;
int gpio_minor = 0;#ifdef MY_SIGNAL_ENABLE
// 用来保存向谁发送信号,应用程序通过 ioctl 把自己的进程 ID 设置进来。
static int g_pid = 0;
#endif#ifdef MY_SIGNAL_ENABLE
// 用来发送信号给应用程序
static void send_signal(int sig_no)
{int ret;struct siginfo info;struct task_struct *my_task = NULL;if (0 == g_pid){// 说明应用程序没有设置自己的 PIDprintk("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");}
}
#endif// 当应用程序打开设备的时候被调用
static int gpio_open(struct inode *inode, struct file *file)
{printk("gpio_open is called. \n");return 0;
}
static long gpio_ioctl(struct file* file, unsigned int cmd, unsigned long arg)
{void __user *pArg;printk("gpio_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;}// 把用户空间的数据复制到内核空间if (copy_from_user(&g_pid, pArg, sizeof(int))){printk("copy_from_user failed! \n");return -EFAULT;}printk("save g_pid success: %d \n", g_pid); if (g_pid > 0){// 发送信号send_signal(SIGUSR1);send_signal(SIGUSR2);}}return 0;
}static const struct file_operations gpio_ops={.owner = THIS_MODULE,.open  = gpio_open,.unlocked_ioctl = gpio_ioctl
};static int __init gpio_driver_init(void)
{int i, devno;dev_t num_dev;printk("gpio_driver_init is called. \n");// 动态申请设备号(严谨点的话,应该检查函数返回值)alloc_chrdev_region(&num_dev, gpio_minor, MYGPIO_NUMBER, MYGPIO_NAME);// 获取主设备号gpio_major = MAJOR(num_dev);printk("gpio_major = %d. \n", gpio_major);// 创建设备类gpio_class = class_create(THIS_MODULE, MYGPIO_NAME);// 创建设备节点for (i = 0; i < MYGPIO_NUMBER; ++i){// 设备号devno = MKDEV(gpio_major, gpio_minor + i);// 初始化cdev结构cdev_init(&gpio_cdev[i], &gpio_ops);// 注册字符设备cdev_add(&gpio_cdev[i], devno, 1);// 创建设备节点device_create(gpio_class, NULL, devno, NULL, MYGPIO_NAME"%d", i);}return 0;
}static void __exit gpio_driver_exit(void)
{int i;printk("gpio_driver_exit is called. \n");// 删除设备节点for (i = 0; i < MYGPIO_NUMBER; ++i){cdev_del(&gpio_cdev[i]);device_destroy(gpio_class, MKDEV(gpio_major, gpio_minor + i));}// 释放设备类class_destroy(gpio_class);// 注销设备号unregister_chrdev_region(MKDEV(gpio_major, gpio_minor), MYGPIO_NUMBER);
}MODULE_LICENSE("GPL");
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);

5.2.1 gpio_ioctl函数

  • 当应用程序调用 ioctl() 的时候,驱动程序中的 gpio_ioctl 就会被调用。这里定义一个简单的协议:当应用程序调用参数中 cmd100 的时候,就表示用来告诉驱动程序自己的 PID

定义全局变量 g_pid保存应用程序的PID。函数 copy_from_user(&g_pid, pArg, sizeof(int)),把用户空间的参数复制到内核空间中;函数 send_signal 向应用程序发送信号。

5.2.2 send_signal函数

  • 这个函数主要做了3件事情:
1. 构造一个信号结构体变量:struct siginfo info;2. 通过应用程序PID,获取任务信息:pid_task(find_vpid(g_pid), PIDTYPE_PID);3. 给应用进程发送信号:send_sig_info(sig_no, &info, my_task);

5.2.3 驱动Makefile文件

$ touch Makefile

内容如下:

ifneq ($(KERNELRELEASE),)obj-m := my_driver_signal.o
elseKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)
default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif

5.2.4 编译驱动模块

$ make

得到驱动程序: my_driver_signal.ko 。

5.2.5 加载驱动模块

$ sudo insmod my_driver_signal.ko

通过 dmesg 指令来查看驱动模块的打印信息

因为示例代码是在上一篇GPIO的基础上修改的,因此创建的设备节点文件,与上篇文章是一样的:

6. 应用程序接收信号

应用程序放在 ~/tmp/App/ 目录下

$ mkdir ~/tmp/App/app_mysignal
$ cd ~/tmp/App/app_mysignal
$ touch my_signal_app.c

6.1 my_signal_app.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>#define MY_GPIO_NUMBER     4char gpio_name[MY_GPIO_NUMBER][16] = {"/dev/mygpio0","/dev/mygpio1","/dev/mygpio2","/dev/mygpio3"
};// 信号处理函数
static void my_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();// 打开GPIOif((fd = open("/dev/mygpio0", 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 = &my_signal_handler;sa.sa_flags = SA_SIGINFO;// 接收处理SIGUSR1和SIGUSR2信号sigaction(SIGUSR1, &sa, NULL);sigaction(SIGUSR2, &sa, NULL);// 通过ioctl设置应用进程pid给驱动printf("call ioctl. pid = %d \n", pid);ioctl(fd, 100, &pid);// 休眠1秒,等待接收信号sleep(1);// 关闭设备close(fd);
}
  • 可以看到,应用程序主要做了两件事情:

  • (1)、首先通过函数 sigaction() 向操作系统注册了信号 SIGUSR1SIGUSR2,它俩的信号处理函数是同一个:my_signal_handler()。除了 sigaction 函数,应用程序还可以使用 signal 函数来注册信号处理函数;

  • (2)、然后通过 ioctl(fd, 100, &pid); 向驱动程序设置自己的 PID

6.2 编译应用程序

$ gcc my_signal_app.c -o my_signal_app

6.3 执行应用程序:

$ sudo ./my_signal_app

先来看一下 dmesg 中驱动程序的打印信息:

可以看到:驱动把这两个信号(10 和 12),发送给了应用程序(PID=6259)。
应用程序的输出信息如下:
可以看到:应用程序接收到信号 10 和 12,并且正确打印出信号中携带的一些信息!

Linux进程管理内核API:https://www.coolcou.com/linux-kernel/linux-process-management-kernel-api/the-linux-kernel-pid-task.html

【genius_platform软件平台开发】第六十八讲:linux系统驱动开发之-驱动程序发送信号给应用程序相关推荐

  1. 【genius_platform软件平台开发】第七十三讲:linux系统驱动开发之-中断处理之DSB指令

    今天在看power项目的嵌入式代码的时候发现在源代码中有这么一行引起了注意,之前有了解过cpu屏障,但是还真没有实操和应用于项目中,了解一下即可,如图: 1. 概述 这里涉及到了一些汇编的知识,对于一 ...

  2. 【正点原子Linux连载】第六十二章 Linux SPI驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  3. linux i2c adapter 增加设备_「正点原子Linux连载」第六十二章Linux SPI驱动实验(一)...

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南>关注官方微信号公众号,获取更多资料:正点原子 第六十二章Linux SPI驱动实验 上一 ...

  4. 【genius_platform软件平台开发】第九十八讲:嵌入式网络接口(MAC、PHY)

    1. 嵌入式网络简介 1.1 嵌入式下的网络硬件接口 提起网络,我们一般想到的硬件就是"网卡",现在网卡已经是通过一个芯片来完成了,嵌入式网络硬件分为两部分:MAC和PHY,大家都 ...

  5. 【genius_platform软件平台开发】第二十八讲:NEON指令集优化(附实例)

    当在ARM芯片上进行一些例如图像处理等计算的时候,常常会因为计算量太大造成计算帧率较低的情况.因而,需要选择一种更加简单快捷的计算方式以获得处理速度上的提升.ARM NEON就是一个不错的选择. ※ ...

  6. 高等数学学习笔记——第六十八讲——隐函数存在定理

    1. 问题引入--方程两边同时关于x求导数,求解隐函数导数 2. 要研究的问题:方程在什么条件下才能确定隐函数(隐函数的存在性):隐函数的连续性.可微性.求导方法 3. 隐函数存在定理(二元函数) 4 ...

  7. 线性代数学习笔记——第六十八讲——柯西—施瓦兹(Cauchy-Schwarz)不等式

    1. 柯西-施瓦兹不等式(分量形式及积分不等式) 柯西-施瓦兹(Cauchy-Schwarz)积分不等式的证明(2020-02-09补记): 2. 柯西和施瓦兹的简介 3. 三角不等式与柯西不等式是等 ...

  8. 【嵌入式Linux驱动开发】二十四、Linux I2C 驱动上手尝试

      人的前程关于眼界.关乎格局.   志之所趋,无远弗届,穷山复海不能限也:   志之所向,无坚不入,锐兵精甲不能御也. 一.I2C驱动框架简介   Linux内核将 I2C 驱动分为两部分: ①. ...

  9. Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结

    http://blog.csdn.net/zqixiao_09/article/details/51057086 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程 ...

最新文章

  1. vue - check-versions.js for child_process
  2. 修改代码150万行!Apache Flink 1.9.0做了这些重大修改!(附链接)
  3. 人工智能中常见的误区
  4. 《数据库原理与应用(第3版)》——1.4 数据库系统的组成
  5. Refactor?or Patching?
  6. Poj 2676 Sudoku[dfs]
  7. 今晚直播丨一次特殊的 Oralce 硬解析性能问题的技术分享
  8. MyBatis框架学习笔记04:利用MyBatis实现条件查询
  9. 锤子科技回应天猫店商品全线下架:可去京东购买
  10. 【NOIP2014】【Luogu2118】比例简化(枚举)
  11. 联想电脑锁屏界面设置被组织隐藏_Word Clock数字时钟动态屏保,让你的电脑锁屏瞬间科技感十足!...
  12. Java中序列化和打印流
  13. 如何在 macOS 上安装Axure RP
  14. 南京工程学院《DSP技术及应用》期末试卷
  15. 如何使用一键回录游戏视频
  16. vue项目中通过cdn引入资源并配置
  17. 华为--三层交换实验(原理与实验详情)
  18. Cocos Creator2.4.8 资源加载源码阅读
  19. 解决微信群服务管理难题,只需要一个助手
  20. python下划线怎么输入_python中下划线的用法

热门文章

  1. Linux 设全局代理
  2. MYSQL 5.7 数据库备份与恢复
  3. java 修饰器_修饰java
  4. java关闭jframe_java jframe关闭窗口
  5. 模具结构及主要零件设计
  6. 服务器安装虚拟交换机,虚拟交换机配置如何操作?虚拟交换机配置步骤有哪些...
  7. 【Python】使用一行代码实现均值,中位数和众数
  8. 【校招VIP】[推电影项目]商业项目的竞品分析和需求分析
  9. 【图像分类】【深度学习】ViT算法Pytorch代码讲解
  10. html制作摄影社区页面,5个HTML摄影网站——惊人之美!