1. kill 命令和信号
- 简单讲下kill命令和信号,是
Linux 操作系统的信号,来“杀死 ”一个进程的命令:
$ kill -9 <进程的 PID>
- 指令功能是:向指定的某个进程发送一个
信号 9 ,这个信号的默认功能是:是停止进程 。虽然在应用程序中没有主动处理这个信号,但是操作系统默认 的处理动作是终止应用程序的执行 。除了发送信号 9,kill 命令还可以发送其他 的任意信号 。在 Linux 系统中,所有的信号都使用一个整型数值 来表示,可以打开文件 /usr/include/x86_64-linux-gnu/bits/signum.h (你的系统中可能位于其它的目录) 查看一下,比较常见的几个信号是:
#define SIGINT 2
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
...
...
#define SIGSYS 31
#define SIGUNUSED 31
#define _NSIG 65
#define __SIGRTMIN 32
#define __SIGRTMAX (_NSIG - 1)
信号 9 对应着 SIGKILL ,而信号11(SIGSEGV) 就是最令人讨厌的Segmentfault !
2. 实时信号和非实时信号
2.1 非实时信号
- 操作系统不确保应用程序一定能接收到(即:
信号可能会丢失 );从文件 signum.h 中可以看到,实时信号从 __SIGRTMIN(数值:32) 开始。
2.2 实时信号
3. 多线程中信号
4. 信号注册和处理函数
#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;
}
- 这个示例程序接收的信号是
SIGUSR1 和 SIGUSR2 ,也就是数值 10 和 12 。编译、执行:
$ gcc app_handle_signal.c -o app_handle_signal
$ ./app_handle_signal
- 此时,应用程序开始执行,等待接收信号。在
另一个终端中 ,使用kill 指令来发送信号SIGUSR1 或者 SIGUSR2 。kill 发送信号,需要知道 应用程序的 进程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"
#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
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)
{
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_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)
{
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_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 就会被调用。这里定义一个简单的协议:当应用程序调用参数中 cmd 为 100 的时候,就表示用来告诉驱动程序自己的 PID 。
定义全局变量 g_pid 保存应用程序的PID 。函数 copy_from_user(&g_pid, pArg, sizeof(int)) ,把用户空间的参数复制到内核空间中;函数 send_signal 向应用程序发送信号。
5.2.2 send_signal函数
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
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
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 4
char 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();
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 = &my_signal_handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
printf("call ioctl. pid = %d \n", pid);
ioctl(fd, 100, &pid);
sleep(1);
close(fd);
}
-
可以看到,应用程序主要做了两件事情: -
(1)、首先通过函数 sigaction() 向操作系统注册了信号 SIGUSR1 和 SIGUSR2 ,它俩的信号处理函数是同一个: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
|