一. 信号
Ⅰ. 信号的基本概念
信号产生
- 按键产生,
Ctrl + c、Ctrl + z
; - 调用函数,
kill、raise、abort
; - 定时器,
alarm、setitimer
; - 命令产生,
kill
; - 硬件异常,段错误,浮点型错误,总线错误,SIGPIPE。
linux查看信号种类man 7 signal
Ⅱ. kill函数
man 2 kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- pid > 0, 要发送进程ID;
- pid = 0,代表当前调用进程组内所有进程;
- pid = -1,代表有权限发送的所有进程;
- pid < 0,代表-pid对应的组内所有进程;
- sig对应信号 。
简单使用,用子进程结束父进程。
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int i;
for (i = 0; i < 5; ++i) {
pid_t pid = fork();
if (pid == 0) {
break;
}
}
if (i == 2) {
printf("I am a child process\n");
sleep(5);
kill(getppid(), SIGKILL);
while (1) {
;
}
} else if (i == 5) {
printf("I am a father process\n");
sleep(1);
}
return 0;
}
Ⅲ. raise函数
给自己发信号。
man raise
#include <signal.h>
int raise(int sig);
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
sleep(1);
raise(SIGKILL);
return 0;
}
Ⅳ. alarm函数
定时给自己发送SIGALRM。
man 2 alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
- 参数秒,表示几秒后给自己发送信号
- 返回值,闹钟剩余秒数
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
alarm(6);
for (int i = 0; i < 6; ++i) {
printf("i = %d\n", i);
sleep(1);
}
return 0;
}
程序运行可以看到,的确在第六秒发送了信号。
Ⅴ. 捕获信号函数
当有信号的时候调用函数。
man 2 signal
#include <signal.h>
typedef void (*sighandler_t)(int); // 函数指针,返回值void,参数类型int
sighandler_t signal(int signum, sighandler_t handler);
man 2 sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
- 参数
- signum,传入的信号
- act,传入的动作
- oldact,原动作
sigaction结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; // 临时屏蔽的信号集
int sa_flags; // 0使用第一个函数指针, SA_SIGINFO使用第二个函数指针
void (*sa_restorer)(void); // 无效
};
Ⅵ. setitimer函数
周期性的发送信号。
man 2 setitimer
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, truct itimerval *old_value)
- 参数
- which
- ITIMER_REAL,自然定时,信号SIGALRM ;
- TIMER_VIRTUAL,进程执行时间,信号SIGVTALRM;
- ITIMER_PROF,进程执行时间+CPU调度时间,信号SIGPROF。
- new_value
- 传入参数,要设置的闹钟时间。
- old_value
- 传出参数,得到原来的闹钟时间。
- which
struct itimerval结构体定义
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer 周期性时间设置*/
struct timeval it_value; /* Time until next expiration 下次闹钟产生*/
};
struct timeval结构体定义
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds 微秒*/
};
使用:
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
void catch_signal(int num) { printf("catch a %d signal\n", num); }
int main() {
signal(SIGALRM, catch_signal);
struct itimerval myit = {{3, 0}, {5, 0}};// it_interval.tv_sec = 3, it_interval.tv_usec = 0...
setitimer(ITIMER_REAL, &myit, NULL);
while (1) {
printf("...\n");
sleep(1);
}
return 0;
}
通过运行结果,我们可以看到,程序的确是按照我们的预期跑的,5秒后闹钟产生,之后每隔3秒发送一次信号。
Ⅶ. 信号集函数
man 3 sigemptyset
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信号集函
int sigfillset(sigset_t *set); // 填充信号集
int sigaddset(sigset_t *set, int signum); //添加某个信号到信号集
int sigdelset(sigset_t *set, int signum); //从集合中删除某个信号
int sigismember(const sigset_t *set, int signum); //是否为集合里的成员
sigismember()返回1表示signum在集合中,0表示不在,-1失败,设置error。
其余返回0表示成功,-1表示失败,设置error。
设置阻塞或解除阻塞信号集。
man 3 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
- 参数
- how
- SIG_BLOCK 设置阻塞;
- SIG_UNBLOCK 解除阻塞;
- SIG_SETMASK 设置set为新的阻塞信号集。
- set传入的信号集
- oset传出参数,旧的信号集
- how
- 返回0表示成功,-1失败,设置error。
获取未决信号集。
man 3 sigpending
#include <signal.h>
int sigpending(sigset_t *set);
利用SIGCHLD回收子进程
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
void catch_sig(int num) {
pid_t wpid = waitpid(-1, NULL, WNOHANG);
if (wpid > 0) {
printf("wait child %d ok\n", wpid);
}
}
int main() {
int i = 0;
pid_t pid;
// 在创建子进程之前屏蔽SIGCHLD信号
sigset_t myset, oldset;
sigemptyset(&myset);
sigaddset(&myset, SIGCHLD);
// oldset保护现场,阻塞信号SIGCHLD,得到阻塞前的信号SIGCHLD
sigprocmask(SIG_BLOCK, &myset, &oldset);
for (i = 0; i < 10; ++i) {
pid = fork();
if (pid == 0) {
break;
}
}
if (i == 10) {
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = catch_sig;
sigaction(SIGCHLD, &act, NULL);
sigprocmask(SIG_SETMASK, &oldset, NULL);
while (1) {
sleep(1);
}
} else if (i < 10) {
printf("I am %d child, pid = %d\n", i, getpid());
sleep(i);
}
return 0;
}
通过ps -ajx
可以看到,的确杀死了子进程。
二. 守护进程
个人理解守护进程,不用占用终端,程序可以一直进行下去 ,守护进程一般以d结尾,linux下有systemd系统工具来启动守护进程。
创建守护进程步骤:
- 创建子进程,kill父进程;
- 子进程调用setsid当会话组长和进程组长,失去终端;
- 忽略SIGHUP信号;
- 切换工作目录;
- 用umask设置掩码;
- 关闭文件描述符0、1、2,避免资源浪费;
- 执行核心逻辑;
- 守护进程退出,通过
kill pid
。
创建守护进程:
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int i = 0;
void print(int num) { printf("i = %d\n", i++); }
int main() {
// 创建子进程,父进程退出
pid_t pid = fork();
if (pid > 0) {
exit(1);
}
// 当会长
setsid();
// 设置掩码
umask(0);
// 切换目录
chdir(getenv("HOME"));
// 关闭文件描述符
// close(1);
// close(2);
// close(3);
// 执行核心逻辑
struct itimerval myit = {{1, 0}, {1, 0}};
setitimer(ITIMER_REAL, &myit, NULL);
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = print;
sigaction(SIGALRM, &act, NULL);
while (1) {
sleep(1);
}
// 退出
return 0;
}
// 每隔一秒打印一个数字,为了方便,没有关闭文件描述符
可以看到,守护进程创建成功。
三. 总结
本来还打算用信号和线程写一个睡眠排序,发现好像有点困难,就放弃了。
linux下的进程就算粗略学习完了,绝知此事要躬行,还有很多要学习的。