1.信号的概念
信号是Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中段,它是在软件层次上对中段机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另外一个正在运行的异步进程中断,转而处理某一个突发事件。
发往进程的诸多信号,通常都源于系统内核。引发系统内核为进程产生信号的各类事件有:
(1)对于前台进程(可以理解为在当前终端运行的进程,此时当前终端无法输入其他命令),用户可以通过输入特殊的终端字符来给这个进程发送信号。比如输入ctrl + c,通常会给进程发送一个中断信号。
(2)硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相关信号给相关进程。比如执行一条异常的机器语言指令,0做除数,或者引用了无法访问的内存区域。
(3)系统状态变化,比如alarm定时器到期将引起SIGALRM信号;进程执行的CPU时间片到时,或者该进程的某个子进程退出。
(4)运行kill命令或者调用kill函数。
2.使用信号的目的、信号特点以及常见信号
目的:
让进程知道已经发生了一个特定的事情;强迫进程执行它自己代码中的信号处理程序。
特点:
- 简单
- 不能携带大量信息
- 满足某个特定条件才发送
- 优先级比较高
查看系统定义的信号列表: kill -l
前31个信号为常规信号,其余为实时信号。
linux信号一览表
查看信号的详细信息: man 7 signal
3.信号的5种默认处理动作以及3中信号状态
- Term: 终止进程
- Ign:当前进程忽略掉这个信号
- Core:终止进程,并生成一个Core文件
- Stop:暂停当前进程
- Cont:继续执行当前被暂停的进程
信号的3种状态: 产生、未决、递达
SIGKILL和SIGSTOP信号不能被捕捉、阻塞或者忽略,只能执行默认动作。
4.与信号相关的系统调用: kill、raise 和 abort 函数
通过man 2 kill 查看kill函数的详细信息
kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给任何的进程或者进程组(pid)发送任何的信号(sig)
参数:
pid:
- >0:将信号发送给指定的进程
- =0:将信号发送给当前进程所在的进程组中的所有进程
- = -1:将信号发送给每一个有权限接收这个信号的进程
- < -1:将信号发送给进程组(-pid)中的所有进程
sig:
需要发送的信号的编号或者是宏值,0表示不发送任何信号
例如:
kill(getppid(), 9); 给父进程发送9号信号
kill(getpid(), 9); 给自己发送9号信号
reise
int raise(int sig);
功能:给当前进程发送信号
参数:sig : 要发送的信号
返回值:成功返回0,失败返回非0
相当于:kill(getpid(), sig);
abort
void abort(void);
功能: 发送SIGABRT信号给当前的进程,杀死当前进程;相当于 kill(getpid(), SIGABRT);
测试代码:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); //创建子进程
if(pid == 0) {
// 子进程,每隔1秒打印child process
int i = 0;
for(i = 0; i < 5; i++) {
printf("child process\n");
sleep(1);
}
} else if(pid > 0) {
// 父进程,在父进程中,这个pid是子进程的pid;
// 父进程运行两秒后杀死子进程
printf("parent process\n");
sleep(2);
printf("kill child process now\n");
kill(pid, SIGINT);
}
return 0;
}
5.终止进程并产生core文件
我们看到某些信号它的默认动作是终止进程并产生core文件,这个core文件是什么东西呢?
测试代码:
#include <stdio.h>
#include <string.h>
int main() {
char * buf;
strcpy(buf, "hello");
return 0;
}
在上述代码中存在一个问题,那就是试图将字符串复制到未初始化的指针变量buf
中。在C语言中,未初始化的指针可能会导致未定义的行为,这可能是程序崩溃、数据损坏或其他不可预测的行为。解决这个问题的方法是给buf
分配足够的内存来存储你要复制的字符串。你可以使用malloc
函数来动态分配内存。
比如:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char * buf = malloc(6 * sizeof(char)); // 分配足够的内存来存储"hello"
if (buf == NULL) { // 检查内存分配是否成功
printf("Memory allocation failed!\n");
return 1;
}
strcpy(buf, "hello"); // 现在可以安全地复制字符串
printf("%s\n", buf); // 输出"hello"
free(buf); // 不要忘记释放分配的内存
return 0;
}
在上面的代码中,我使用了malloc
来分配内存,并且在不再需要buf
时使用free
释放了内存。这是因为在C语言中,你需要手动管理内存。如果你不释放分配的内存,你的程序可能会消耗掉所有的可用内存,这被称为内存泄漏。
当然,我们并不是修改这个程序使它能正常运行,而是想看一下,错误的程序会产生什么样的信号。执行这个程序可以看到会产生段错误,但是没有产生相应的core文件。
通过ulimit -a 命令可以看到,core file size为0,使用ulimit -c 1024将大小改成1024,当然这个值是任意的。
现在,重新编译,注意要加-g,待会要使用GDB。gcc core.c -g 它会成a.out的可执行文件,然后在gdb中打开它。输入run,可以查看core文件中保存的错误信息。
但是没有发现生成的core文件在哪里,参考别人的解决方案:
1.使用两种方式不产生core文件是文件生产目录错误
查看core文件生产目录
cat /proc/sys/kernel/core_pattern
使用下面的命令
sudo bash -c "echo core > /proc/sys/kernel/core_pattern "
就可以解决了~
2.系统是ubuntu,cat /proc/sys/kernel/core_pattern之后输出|/usr/share/apport/apport %p %s %c %d %P %E,查了之后发现ubuntu预装了apport错误收集系统,sudo service apport stop之后就可以了。