系统编程day05-进程间通信-信号
1.进程间通信
1.1什么是进程间通信
用于在操作系统中不同进程之间交换信息或协作。由于进程通常运行在独立的内存空间中,无法直接访问彼此的数据,因此需要通过操作系统提供的通信机制来实现信息的传递和共享。
1.2进程间通信的方式
2.信号
2.1信号的概念
例如:教室铃响了,就知道下课了。如果手机响了,就知道来信息或者来电话。
以上这些就是信号。信号已经在生活中离不开了。特别是物联网行业。
2.2信号的特点
- 信号是传递信息的,但是不传递具体的信息,知识一个标志
- 信号是一种软件中断机制(写代码触发,与硬件中断有所区别)
2.3信号的编号
linux中,信号编号的查看,用 kill -l来具体查看。 1-31 号信号称之为常规信号(也叫普通信号或标准信号),34-64称为实时信号。
2.4信号的四要素
编号
信号名称(和信号编号都一样,后续调用函数传参数的时候可以传编号也可传名称。)
对应事件
默认处理动作(终止进程,忽略,暂停)
后续可以使用man 7 signal查看signal信号
3.信号的产生与处理
产生
当用户按某些终端键时,将产生信号。 终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT 终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT 终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。
硬件异常将产生信号。 除数为0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
软件异常将产生信号。 当检测到某种软件条件已发生(如:定时器 alarm),并将其通知有关进程时,产生信号。
调用系统函数(如:kill、raise、abort)将发送信号。
运行 kill /killall 命令将发送信号。 此程序实际上是使用 kill 函数来发送信号。
信号的处理
信号的处理:忽略、终止进程、暂停进程,(自定义处理)
4.未决信号集与阻塞信号集
信号是一种异步通信方式,信号发出,不用管是否被接收或者处理。
Linux 内核的进程控制块 PCB 是一个结构体,task_struct, 除了包含进程 id,状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
未决信号集:未决信号集 信号产生,未决信号集中描述该信号的位立刻翻转为 1,表示信号处于未决状态。当信号被处理对应位翻转回为 0。这一时刻往往非常短暂。
阻塞信号集:将某些信号加入集合,对他们设置屏蔽,当屏蔽 x 信号后,再收到该信号,该信号的处理将推后。(注意,不是不处理,而是推后处理)
如果信号在阻塞信号集内,那么信号得不到执行,未决信号集里这个信号的位图一直是1,直到该信号从阻塞集剔除,执行完毕才会变成0,从未决信号集中剔除。
5.信号相关API函数
5.1kill函数(向其他进程发送信号)
函数原型:
#include <sys/types.h>
#include <signal.h>
//向pid发送一个信号sig
int kill(pid_t pid, int sig);
参数:
pid : 取值有 4 种情况 :
pid > 0: 将信号传送给进程 ID 为 pid 的进程。
pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给系统内所有的进程。(慎用)
pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。(如果传入-9527,其实是发送给9527进程组的所有进程)
sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
返回值:
成功:0
失败:-1
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid == -1){perror("fork");}else if (pid == 0) // 子进程{for (int i = 1; i <= 5; i++){printf("我还能再玩%ds\n", 5 - i);sleep(1);}while (1){/* code */}}else if (pid > 0) // 父进程{int status = 0;sleep(5);kill(pid, SIGINT); //向pid 发送一个sigint信号(2号信号)也就相//当于按下了ctrl+cwait(&status); //等待资源回收printf("WIFEXITED(status):%d\n", WIFEXITED(status)); // 结果为0 因为非正常退出if (WIFEXITED(status)) //此处不会执行{printf("退出状态%d\n", WEXITSTATUS(status));}}return 0;
}
5.2raise(自己给自己发送信号)
函数原型:
#include <signal.h>
int raise(int sig);
参数:sig ,向自己发送一个sig信号 等价于 kill(getpid(),sig);
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid == -1){perror("fork");}else if (pid == 0) // 子进程{for (int i = 1; i <= 5; i++){printf("我还能再玩%ds\n", 5 - i);sleep(1);}raise(SIGINT); //向自己发送信号while (1){/* code */}}else if (pid > 0) // 父进程{int status = 0;sleep(5);// kill(pid, SIGINT); //向pid 发送一个sigint信号(2号信号)wait(&status); //等待资源回收printf("WIFEXITED(status):%d\n", WIFEXITED(status)); // 结果为0 因为非正常退出if (WIFEXITED(status)) //此处不会执行{printf("退出状态%d\n", WEXITSTATUS(status));}}return 0;
}
5.3abort
函数原型:
#include <stdlib.h>
void abort(void);
功能:给自己发送异常终止信号 6) SIGABRT,并产生 core 文件,等价于 kill(getpid(), SIGABRT);
4.4alarm(定时发送信号)
函数原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
设置定时器(闹钟)。在指定 seconds 后,内核会给当前进程发送 14)SIGALRM 信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
取消定时器 alarm(0),返回旧闹钟余下秒数。
参数:
seconds:指定的时间,以秒为单位
返回值:
返回 0 或剩余的秒数
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char const *argv[])
{pid_t pid = fork();if (pid == -1){perror("fork");}else if (pid == 0) // 子进程{alarm(5); //5s以后向该进程发送一个14号新号for (int i = 1; i <= 5; i++){printf("我还能再玩%ds\n", 5 - i);sleep(1);if (i==3){int ret = alarm(0); //3s以后,定时器取消,返回值为剩余时间 2sprintf("ret:%d\n",ret);}}while (1){/* code */}}else if (pid > 0) // 父进程{int status = 0;sleep(5);// kill(pid, SIGINT); //向pid 发送一个sigint信号(2号信号)wait(&status); //等待资源回收printf("WIFEXITED(status):%d\n", WIFEXITED(status)); // 结果为0 因为非正常退出if (WIFEXITED(status)) //此处不会执行{printf("退出状态%d\n", WEXITSTATUS(status));}}return 0;
}
5.5setitimer(可循环定时器)
函数原型:
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
可见这个函数参数非常的多
功能:设置定时器(闹钟)。 可代替 alarm 函数。精度微秒 us,可以实现周期定时。
which:指定定时方式
自然定时:ITIMER_REAL → 14)SIGALRM 计算自然时间(时钟时间)
虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用 cpu 的时间
运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF 计算占用 cpu 及执行系统调用的时间
struct itimerval *new_value 结构体指针,而且传入后不能修改。
结构体1
struct itimerval {struct timerval it_interval; // 闹钟触发周期 ,后续每一次触发的时间间隔 struct timerval it_value; //闹钟触发时间 从现在开始,到闹钟第一次触发的时间
};
结构体2
struct timeval {long tv_sec; // 秒long tv_usec; // 微秒
}
old_value: 存放旧的 timeout 值,一般指定为 NULL
具体理解呢就是:在12.50定了一个13.00的闹钟,这中间的10分钟就是第一次触发的时间(it_value)。而后面每24小时就触发一次13.00的闹钟,这个24小时就是循环触发闹钟的间隔时间(it_interval)。
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>int main(int argc, char const *argv[])
{struct itimerval new, old;new.it_interval.tv_sec = 5; // 后续每次触发是5snew.it_interval.tv_usec = 0;new.it_value.tv_sec = 10; // 第一次触发是10s以后new.it_value.tv_usec = 0;setitimer(ITIMER_REAL, &new, &old);//按照new规定的时间进行定时器设置,包括循环时间和第一次执行的时间//old 用来保存上一次定时器状态,例如剩余时间(在此处是0,因为上次没有调用定时器)sleep(2);setitimer(ITIMER_REAL, &new, &old); ////按照new规定的时间进行定时器设置,包括循环时间和第一次执行的时间//old 此时,保存上一个定时器计数剩下的时间,大约是8s,可以用于后续恢复这个计时器状态printf("%ld\n", old.it_interval.tv_sec);printf("%ld\n", old.it_interval.tv_usec);printf("%ld\n", old.it_value.tv_sec);printf("%ld\n", old.it_value.tv_usec);setitimer(ITIMER_REAL, &old, NULL);int count = 1;while (1){sleep(1);printf("过了%ds\n", count);count++;}return 0;
}
6.给信号注册自定义函数
进程收到信号后的操作:
1.执行默认动作,例如结束进程,或者是暂停进程。
2.直接忽略
3.执行自定义信号处理函数(捕获) 用用户定义的信号处理函数处理该信号。
6.1信号自定义函数的作用
6.2signal函数
函数原型:
#include <signal.h> //头文件
typedef void(*sighandler_t)(int); //函数指针 int
sighandler_t signal(int signum, sighandler_t handler);
函数功能:注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。不再按照系统默认方式处理。
参数:
signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母)进行相应查看。
handler : 取值有 3 种情况:
SIG_IGN:忽略该信号
SIG_DFL:执行系统默认动作
信号处理函数名:自定义信号处理函数。
代码案例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <stdlib.h>char *p = NULL;void my_deal(int signal) //后续这个signal 可以用于监控是哪个信号触发了这个函数
{if (p !=NULL ){free(p);p = NULL;}_exit(0);
}
int main(int argc, char const *argv[])
{p = (char *)malloc(128);signal(SIGINT,my_deal); //我想在终端 按下 ctrl + c的时候,将这个p释放掉while (1){printf("%p\n",p);sleep(1);}free(p);return 0;
}
6.3sigaction
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction*oldact);
函数功能:检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
signum:要操作的信号。一般传宏
act: 要设置的对信号的新处理方式(传入参数)。
oldact:原来对信号的处理方式(传出参数)。
如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
struct sigaction 结构体:
struct sigaction {//两个信号处理函数指针 选择一个即可
void(*sa_handler)(int); //旧 信号处理函数指针 相当于 signal函数的第二参数。
void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
sigset_t sa_mask; //信号阻塞集
int sa_flags; //信号处理的方式
void(*sa_restorer)(void); //已弃用
};//sa_flags:用于指定信号处理的行为,通常设置为 0,表使用默认属性。它可以是以下值的“按位或”组合
/*SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃) SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。 SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。 SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。 SA_RESETHAND:信号处理之后重新设置为默认的处理方式。 SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
*/
代码案例:
#define _POSIX_C_SOURCE 199309L
#define _XOPEN_SOURCE 700#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/time.h>//倒计时10s,10s之内不允许使用ctrl+c打断函数,过了10s之后就可以使用ctrl+c结束运行
void my_deal() {printf("使用了ctrl+c,但是没用\n");
}int main() {struct sigaction act;act.sa_handler = my_deal;//处理方式为自己定义的处理方式sigemptyset(&act.sa_mask);//将信号集置空act.sa_flags = 0;//使用默认属性sigaction(SIGINT, &act, NULL); int i = 1;while (i <= 10) {printf("还剩%ds\n", 10 - i);sleep(1);i++;}act.sa_handler = SIG_DFL;sigaction(SIGINT, &act, NULL);while (1) {sleep(1);printf("现在可以中断\n");}return 0;
}
7.信号集
函数:
#include <signal.h>
int sigemptyset(sigset_t *set); //将 set 集合置空
int sigfillset(sigset_t *set); //将所有信号加入 set 集合
int sigaddset(sigset_t *set, int signo); //将 signo 信号加入到 set 集合
int sigdelset(sigset_t *set, int signo); //从 set 集合中移除 signo 信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在
并没有结束,先休息两天,下次再更新。