信号可以说是ipc最复杂的的通信方式。在网上也有很多很多讲解得比较好的资料可以参考,我在这里只是初略的对自己的学习的内容做个总结!
信号本质
信号实际上是软中断,既然是中断那么信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号来源
信号事件的发生有两个来源:硬件来源即硬件中断(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
信号分类
- 不可靠信号:Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做”不可靠信号”,信号值小于SIGRTMIN的信号都是不可靠信号。这就是”不可靠信号”的来源。Linux下的不可靠信号问题主要指的是信号可能丢失。
- 可靠信号:原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
注:可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数(对所有信号这一点都成立),而经过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
signal安装响应函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("oh, got a signal %d\n", sig);
int i = 0;
for (i = 0; i < 5; i++)
{
printf("signal func %d\n", i);
sleep(1);
}
// (void) signal(SIGINT, SIG_DFL);
//(void) signal(SIGINT, ouch);
}
int main() {
(void) signal(SIGINT, ouch);
while(1) {
printf("hello world...\n");
sleep(1);
}
}
连续按Ctrl+C时会发现 signal函数会堵塞当前正在处理的signal,但是没有办法阻塞其它signal,比如正在处理SIG_INT,再来一个SIG_INT则会堵塞,但是来SIG_QUIT则会被其中断。
sigaction安装响应函数
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig) {
printf("oh, got a signal %d\n", sig);
int i = 0;
for (i = 0; i < 5; i++)
{
printf("signal func %d\n", i);
sleep(1);
}
}
int main()
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
// act.sa_flags = SA_RESETHAND;
// act.sa_flags = SA_NODEFER;
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
struct sigaction act_2;
act_2.sa_handler = ouch;
sigemptyset(&act_2.sa_mask);
act.sa_flags = 0;
sigaction(SIGQUIT, &act_2, 0);
while(1) {
sleep(1);
}
return;
}
阻塞,sigaction函数有阻塞的功能,比如SIGINT信号来了,进入信号处理函数,默认情况下,在信号处理函数未完成之前,如果又来了一个SIGINT信号,其将被阻塞,只有信号处理函数处理完毕,才会对后来的SIGINT再进行处理,同时后续无论来多少个SIGINT,仅处理一个SIGINT,sigaction会对后续SIGINT进行排队合并处理。
sa_mask,信号屏蔽集,可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号,上面代码中,对信号SIGINT处理时,如果来信号SIGQUIT,其将被屏蔽,但是如果在处理SIGQUIT,来了SIGINT,则首先处理SIGINT,然后接着处理SIGQUIT。
sa_flags如果取值为0,则表示默认行为。还可以取如下俩值,但是我没觉得这俩值有啥用。
SA_NODEFER,如果设置来该标志,则不进行当前处理信号到阻塞
SA_RESETHAND,如果设置来该标志,则处理完当前信号后,将信号处理函数设置为SIG_DFL行为。
信号的实际使用
MTK8685平台有mtk的 thermal管理,实时监控平台的温度,当主芯片的温度过高时,会提示用户cooldown 即重启盒子降温,以免损坏! 温度监控系统也是采用信号的方式实现的。thermald.c 首先设置响应的信号,并设置signal_handler,最终会将此进程的pid通过proc文件系统传递给内核! 内核的驱动检测到温度过高时,会将信号发送给指定的用户态程序,由用户决定下一步的操作!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <cutils/xlog.h>
#include <sys/types.h>
static int debug_on = 0;
#define TM_LOG_TAG "thermald"
#define TM_DBG_LOG(_fmt_, args...) \
do { \
if (1 == debug_on) { \
sxlog_printf(ANDROID_LOG_INFO, TM_LOG_TAG, _fmt_, ##args); \
} \
} while(0)
#define TM_INFO_LOG(_fmt_, args...) \
do { sxlog_printf(ANDROID_LOG_INFO, TM_LOG_TAG, _fmt_, ##args); } while(0)
#define PROCFS_MTK_CL_SD_PID "/proc/driver/mtk_cl_sd_pid"
static void signal_handler(int signo, siginfo_t *si, void *uc)
{
switch(si->si_signo) {
// Add more signo or code to expand thermald
case SIGIO:
if(1 == si->si_code) {
//待收到高温警告后,启动提示对话框
system("am start com.mediatek.thermalmanager/.ShutDownAlertDialogActivity");
TM_INFO_LOG("thermal shutdown signal received, si_signo=%d, si_code=%d\n", si->si_signo, si->si_code);
}
break;
default:
TM_INFO_LOG("what!!!\n");
break;
}
}
int main(int argc, char *argv[])
{
int fd = open(PROCFS_MTK_CL_SD_PID, O_RDWR);
int pid = getpid();
int ret = 0;
char pid_string[32] = {0};
struct sigaction act;
TM_INFO_LOG("START+++++++++ %d", getpid());
/* Create signal handler */
memset(&act, 0, sizeof(act));
act.sa_flags = SA_SIGINFO;//发送额外的信息给signal_handler
//act.sa_handler = signal_handler;
act.sa_sigaction = signal_handler;
sigemptyset(&act.sa_mask);
sigaction(SIGIO, &act, NULL);
/* Write pid to procfs */
sprintf(pid_string, "%d", pid);
ret = write(fd, pid_string, sizeof(char) * strlen(pid_string)); //将当前的进程pid写入proc文件系统,供内核使用
if (ret <= 0) {
TM_INFO_LOG("Fail to write %d to %s %x\n", pid, PROCFS_MTK_CL_SD_PID, ret);
} else {
TM_INFO_LOG("Success to write %d to %s\n", pid, PROCFS_MTK_CL_SD_PID);
}
close(fd);
TM_INFO_LOG("Enter infinite loop");
while(1) {
sleep(100);
}
TM_INFO_LOG("END-----------");
return 0;
}
下面看看内核是如何将信号发送到指定的用户程序的,相关代码如下:
驱动在初始化时会创建proc文件节点:
static int __init mtk_cooler_shutdown_init(void){
......
entry = proc_create("driver/mtk_cl_sd_pid", S_IRUGO | S_IWUSR | S_IWGRP, NULL, &_cl_sd_pid_fops);
.....
}
_cl_sd_pid_fops定义如下:
static const struct file_operations _cl_sd_pid_fops = {
.owner = THIS_MODULE,
.open = _mtk_cl_sd_pid_open,
.read = seq_read,
.llseek = seq_lseek,
.write = _mtk_cl_sd_pid_write,
.release = single_release,
};
用户态下write /proc/driver/mtk_cl_sd_pid文件时,kernel会调用_mtk_cl_sd_pid_write。_mtk_cl_sd_pid_write实现如下:
static ssize_t _mtk_cl_sd_pid_write(struct file *filp, const char __user *buf, size_t len, loff_t *data)
{
int ret = 0;
char tmp[MAX_LEN] = {0};
/* write data to the buffer */
if ( copy_from_user(tmp, buf, len) ) {
return -EFAULT;
}
ret = kstrtouint(tmp, 10, &tm_input_pid);//通过proc文件系统,用户程序吧pid悄悄滴传送到了内核
if (ret)
WARN_ON(1);
mtk_cooler_shutdown_dprintk("[%s] %s = %d\n", __func__, tmp, tm_input_pid);
return len;
}
static int _mtk_cl_sd_send_signal(void)
{
int ret = 0;
if (tm_input_pid == 0) {
mtk_cooler_shutdown_dprintk("[%s] pid is empty\n", __func__);
ret = -1;
}
mtk_cooler_shutdown_dprintk("[%s] pid is %d, %d\n", __func__, tm_pid, tm_input_pid);
if (ret == 0 && tm_input_pid != tm_pid) {
tm_pid = tm_input_pid;
pg_task = get_pid_task(find_vpid(tm_pid), PIDTYPE_PID);//根据pid获取task_struct结构体
}
if (ret == 0 && pg_task) {
siginfo_t info;
info.si_signo = SIGIO;
info.si_errno = 0;
info.si_code = 1;
info.si_addr = NULL;
ret = send_sig_info(SIGIO, &info, pg_task);//将SIGIO信号发送给指定的用户进程
}
if (ret != 0) mtk_cooler_shutdown_dprintk("[%s] ret=%d\n", __func__, ret);
return ret;
}
源码路径mediatek/kernel/drivers/thermal/mtk_cooler_shutdown.c
较详细的linux 信号参考资料:
Linux环境进程间通信(二): 信号(上)
Linux环境进程间通信(二): 信号(下)
Linux signal那些事儿
Linux signal 那些事儿(2)
Linux signal 那些事儿 (3)