开发后端
在上一篇文章中,我们讨论的是:在应用层怎样发送指令来控制驱动层的GPIOLinux驱动实践:怎么编撰【GPIO】设备的驱动程序。明天我为大家儿解说的技术知识点是:【驱动层中,怎样发送讯号给应用程序】
[[438633]]
他人的经验,我们的阶梯!
你们好,我是道哥,明天我为大家儿解说的技术知识点是:【驱动层中,怎样发送讯号给应用程序】。
在上一篇文章中,我们讨论的是:在应用层怎样发送指令来控制驱动层的GPIOLinux驱动实践:怎么编撰【GPIO】设备的驱动程序?。控制的方向是从应用层到驱动层:
这么,假如想让程序的执行路径从下往上,也就是从驱动层传递到应用层,应当怎样实现呢?
最容易、最简单的方法,就是通过发送讯号!
这篇文章继续以完整的代码实例来演示怎么实现这个功能。
关于Linux操作系统的讯号,每个程序员都晓得这个指令:使用kill工具来“杀死”一个进程:
复制
$ kill -9
- 1.
这个指令的功能是:向指定的某个进程发送一个讯号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 be
used directly at user level. */
#define __SIGRTMIN 32
#define __SIGRTMAX (_NSIG - 1)
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
讯号9对应着SIGKILL,而讯号11(SIGSEGV)就是最令人厌恶的Segmentfault!
这儿还有一个地方须要注意一下:实时讯号和非实时讯号,它俩的主要区别是:
非实时讯号:操作系统不确保应用程序一定能接收到(即:讯号可能会遗失);实时讯号:操作系统确保应用程序一定能接收到;
假如我们的程序设计,通过讯号机制来完成一些功能,这么为了确保讯号不会遗失,肯定是使用实时讯号的。
从文件signum.h中可以见到,实时讯号从__SIGRTMIN(数值:32)开始。
多线程中的讯号
我们在编撰应用程序时,即使没有接收并处理SIGKILL这个讯号,然而一旦他人发送了这个讯号,我们的程序就被操作系统停止掉了,这是默认的动作。
这么,在应用程序中,应当可以主动申明接收并处理指定的讯号linux 驱动通知应用层,下边就来写一个最简单的实例。
在一个应用程序中,可能存在多个线程;
当有一个讯号发送给此进程时,所有的线程都可能接收到,并且只能有一个线程来处理;
在这个示例中,只有一个主线程来接收并处理讯号;
讯号注册和处理函数
依照惯例,所有应用程序文件都创建在~/tmp/App目录中。
复制
// 文件:tmp/App/app_handle_signal/app_handle_signal.c
#include
#include
#include
#include
#include
// 信号处理函数
static void signal_handler(int signum, siginfo_t *info, void *context)
{
// 打印接收到的信号值
printf("signal_handler: signum = %d n", signum);
}
int main(void)
{
int count = 0;
// 注册信号处理函数
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = &signal_handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
// 一直循环打印信息,等待接收发信号
while (1)
{
printf("app_handle_signal is running...count = %d n", ++count);
sleep(5);
}
return 0;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
这个示例程序接收的讯号是SIGUSR1和SIGUSR2,也就是数值10和12。
编译、执行:
复制
$ gcc app_handle_signal.c -o app_handle_signal
$ ./app_handle_signal
- 1.
- 2.
此时,应用程序开始执行,等待接收讯号。
在另一个终端中,使用kill指令来发送讯号SIGUSR1或则SIGUSR2。
kill发送讯号,须要晓得应用程序的PID,可以通过指令:ps-au|grepapp_handle_signal来查看。
其中的15428就是进程的PID。
执行发送讯号SIGUSR1指令:
复制
$ kill -10 15428
- 1.
此时,在应用程序的终端窗口中,能够看见下边的复印信息:
说明应用程序接收到了SIGUSR1这个讯号!
注意:我们是使用kill命令来发送讯号的,kill也是一个独立的进程,程序的执行路径如下:
在这个执行路径中,我们可控的部份是应用层,至于操作系统是怎样接收kill的操作,之后怎样发送讯号给app_handle_signal进程的,我们不得而知。
下边就继续通过示例代码来看一下怎样在驱动层主动发送讯号。
驱动程序代码示例:发送讯号
功能需求
在昨天的简单示例中linux系统应用,可以得出下边这种信息:
讯号发送方:必须晓得向谁[PID]发送讯号,发送那个讯号;讯号接收方:必须定义讯号处理函数,而且向操作系统注册:接收什么讯号;
发送方其实就是驱动程序了,在示例代码中,继续使用SIGUSR1讯号来测试。
这么,驱动程序怎样能够晓得应用程序的PID呢?可以让应用程序通过oictl函数,把自己的PID主动告诉驱动程序:
驱动程序
这儿的示例代码,是在上一篇文章的基础上更改的,改动部份的内容查看系统版本linux,使用宏定义MY_SIGNAL_ENABLE控制上去,便捷查看和比较。
以下所有操作的工作目录,都是与上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。
复制
$ cd ~/tmp/linux-4.15/drivers/
$ mkdir my_driver_signal
$ cd my_driver_signal
$ touch my_driver_signal.c
- 1.
- 2.
- 3.
- 4.
my_driver_signal.c文件的内容如下(不须要手敲,文末有代码下载链接):
复制
#include
#include
#include
#include
#include
// 新增的头文件
#include
#include
#include
#include
#include
// GPIO 硬件相关宏定义
#define MYGPIO_HW_ENABLE
// 新增部分,使用这个宏控制起来
#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 MYGPIO_HW_ENABLE
// 硬件初始化函数,在驱动程序被加载的时候(gpio_driver_init)被调用
static void gpio_hw_init(int gpio)
{
printk("gpio_hw_init is called: %d. n", gpio);
}
// 硬件释放
static void gpio_hw_release(int gpio)
{
printk("gpio_hw_release is called: %d. n", gpio);
}
// 设置硬件GPIO的状态,在控制GPIO的时候(gpio_ioctl)被调研
static void gpio_hw_set(unsigned long gpio_no, unsigned int val)
{
printk("gpio_hw_set is called. gpio_no = %ld, val = %d. n", gpio_no, val);
}
#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)
{
// 说明应用程序没有设置自己的 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");
}
}
#endif
// 当应用程序打开设备的时候被调用
static int gpio_open(struct inode *inode, struct file *file)
{
printk("gpio_open is called. n");
return 0;
}
#ifdef MY_SIGNAL_ENABLE
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;
}
#else
// 当应用程序控制GPIO的时候被调用
static long gpio_ioctl(struct file* file, unsigned int val, unsigned long gpio_no)
{
printk("gpio_ioctl is called. n");
if (0 != val && 1 != val)
{
printk("val is NOT valid! n");
return 0;
}
if (gpio_no >= MYGPIO_NUMBER)
{
printk("dev_no is invalid! n");
return 0;
}
printk("set GPIO: %ld to %d. n", gpio_no, val);
#ifdef MYGPIO_HW_ENABLE
gpio_hw_set(gpio_no, val);
#endif
return 0;
}
#endif
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);
}
#ifdef MYGPIO_HW_ENABLE
for (i = 0; i < MYGPIO_NUMBER; ++i)
{
// 初始硬件GPIO
gpio_hw_init(i);
}
#endif
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);
#ifdef MYGPIO_HW_ENABLE
for (i = 0; i < MYGPIO_NUMBER; ++i)
{
gpio_hw_release(i);
}
#endif
// 注销设备号
unregister_chrdev_region(MKDEV(gpio_major, gpio_minor), MYGPIO_NUMBER);
}
MODULE_LICENSE("GPL");
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
这儿大部份的代码,在上一篇文章中早已描述的比较清楚了,这儿把重点关注置于这两个函数上:gpio_ioctl和send_signal。
(1)函数gpio_ioctl
当应用程序调用ioctl()的时侯,驱动程序中的gpio_ioctl都会被调用。
这儿定义一个简单的合同:当应用程序调用参数中cmd为100的时侯,就表示拿来告诉驱动程序自己的PID。
驱动程序定义了一个全局变量g_pid,拿来保存应用程序传入的参数PID。
须要调用函数copy_from_user(&g_pid,pArg,sizeof(int)),把用户空间的参数复制到内核空间中;
成功取得PID以后,就调用函数send_signal向应用程序发送讯号。
这儿仅仅是用于演示目的,在实际的项目中,可能会按照接收到硬件触发以后再发送讯号。
(2)函数send_signal
这个函数主要做了3件事情:
构造一个讯号结构体变量:structsiginfoinfo;
通过应用程序传入的PID,获取任务信息:pid_task(find_vpid(g_pid),PIDTYPE_PID);
发送讯号:send_sig_info(sig_no,&info,my_task);
驱动模块Makefile
复制
$ touch Makefile
- 1.
内容如下:
复制
ifneq ($(KERNELRELEASE),)
obj-m := my_driver_signal.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
编译驱动模块
复制
$ make
- 1.
得到驱动程序:my_driver_signal.ko。
加载驱动模块
复制
$ sudo insmod my_driver_signal.ko
- 1.
通过dmesg指令来查看驱动模块的复印信息:
由于示例代码是在上一篇GPIO的基础上更改的,因而创建的设备节点文件,与下篇文章是一样的:
应用程序代码示例:接收讯号
注册讯号处理函数
应用程序一直置于~/tmp/App/目录下。
复制
$ mkdir ~/tmp/App/app_mysignal
$ cd ~/tmp/App/app_mysignal
$ touch mysignal.c
- 1.
- 2.
- 3.
文件内容如下:
复制
#include
#include
#include
#include
#include
#include
#include
#define MY_GPIO_NUMBER 4
char gpio_name[MY_GPIO_NUMBER][16] = {
"/dev/mygpio0",
"/dev/mygpio1",
"/dev/mygpio2",
"/dev/mygpio3"
};
// 信号处理函数
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/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 = &signal_handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
// set PID
printf("call ioctl. pid = %d n", pid);
ioctl(fd, 100, &pid);
// 休眠1秒,等待接收信号
sleep(1);
// 关闭设备
close(fd);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
可以看见,应用程序主要做了两件事情:
(1)首先通过函数sigaction()向操作系统注册了讯号SIGUSR1和SIGUSR2linux 驱动通知应用层,它俩的讯号处理函数是同一个:signal_handler()。
不仅sigaction函数,应用程序还可以使用signal函数来注册讯号处理函数;
(2)之后通过ioctl(fd,100,&pid);向驱动程序设置自己的PID。
编译应用程序:
复制
$ gcc mysignal.c -o mysignal
- 1.
执行应用程序:
复制
$ sudo ./mysignal
- 1.
按照刚刚驱动程序的代码,当驱动程序接收到设置PID的命令以后,会立即发送两个讯号:
先来看一下dmesg中驱动程序的复印信息:
可以看见:驱动把这两个讯号(10和12),发送给了应用程序(PID=6259)。
应用程序的输出信息如下:
可以看见:应用程序接收到讯号10和12,而且正确复印出讯号中携带的一些信息!