代码编织梦想

进程间通信

IPC(Inter-Process Communication)进程间通信,提供了各种进程间通信的方法。在Linux C编程中有几种方法
(1) 半双工Unix管道
(2) FIFOs(命名管道)
(3) 消息队列
(4) 信号量
(5) 共享内存
(6) 网络Socket

信号

什么是信号?信号是给程序提供一种可以处理异步事件的方法,它利用软件中断来实现。不能自定义信号,所有信号都是系统预定义的。进程间是通过信号进行交流的.
 信号由谁产生?
(1)由shell终端根据当前发生的错误(段错误、非法指令等)Ctrl+c而产生相应的信号
比如: (和通信一样 需要有发送端和接收端)
socket通信或者管道通信,如果读端都已经关闭,再去执行写操作(或者发送数据),
将导致执行写操作的进程收到SIGPIPE信号(表示管道破裂)
该信号的默认行为:终止该进程。
(2)在shell终端,使用kill或killall命令产生信号
(3)进程之间是通过信号来交流的

main1.cc

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig) {//信号的代号
	printf("Catch a signal : %d\n", sig);
}
int main(void) {
	//如果发生这个信号,就调用这个信号进行处理
	signal(SIGINT, myhandle);//设置信号捕捉函数  //SIGINT信号 Ctrl+C
	while (1) {
		sleep(1);
		printf("sleep 1 sencond.");
	}
	return 0;
}

ps -ef | grep single.out
kill -9 59579(id) 可以杀掉所有进程

./a.out &
kill -HUP 13733 /* 向PID为13733的进程发送SIGHUP */
3) 在程序代码中,调用kill系统调用产生信号
 有哪些信号
-------------------------------------------
信号名称 说明
-------------------------------------------
SIGABORT 进程异常终止
SIGALRM 超时告警
SIGFPE 浮点运算异常
SIGHUP 连接挂断
SIGILL 非法指令
SIGINT 终端中断 (Ctrl+C将产生该信号)
SIGKILL *终止进程
SIGPIPE 向没有读进程的管道写数据
SIGQUIT 终端退出(Ctrl+\将产生该信号)
SIGSEGV 无效内存段访问
SIGTERM 终止
SIGUSR1 *用户自定义信号1
SIGUSR2 *用户自定义信号2
-------------------------------------->以上信号如果不被捕获,则进程接受到后都会终止!
SIGCHLD 子进程已停止或退出
SIGCONT *让暂停的进程继续执行
SIGSTOP *停止执行(即“暂停")
SIGTSTP 中断挂起
SIGTTIN 后台进程尝试读操作
SIGTTOU 后台进程尝试写
-------------------------------------------
 信号的处理
忽略此信号
捕捉信号,指定信号处理函数进行处理
执行系统默认动作,大多数都是终止进程
 信号的捕获
信号的捕获,是指,指定接受到某种信号后,去执行指定的函数。
注意:SIGKILL和SIGSTOP不能被捕获,即,这两种信号的响应动作不能被改变。
信号的安装
1) 使用signal
用法:man 2 signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
注:signal的返回类型,和它的第二个参数,都是函数指针类型

    signal的参数2可去以下特殊值:  也可以是自定义函数
    SIG_IGN     忽略信号
    SIG_DFL     恢复默认行为
    实例:main2.c  改变终端中断信号的行为
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig)
{
	printf("Catch a signal : %d\n", sig);
}

int main(void)
{
	signal(SIGINT, myhandle);
	while (1) {
		sleep(1);
	}
	return 0;
}

此时就不能结束该进程了!
只能通过其他终端,给该进程发送一个其他信号,使它终止
#ps ax | grep ./a.out //查询进程号
#kill -HUP 进程号
恢复信号的默认行为main3.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig)
{
	static int cnt = 0;
	printf("Catch a signal : %d\n", sig);

	signal(SIGINT, SIG_DFL); //等同于signal(sig, SIG_DFL);
}

int main(void)
{
	signal(SIGINT, myhandle);

	while (1) {
		sleep(1);
	}
	return 0;
}

使用SIG_DFL时,仅当第一次调用自定义的行为后马上使用SIG_DFL就可恢复,如果连续捕获多次后,就不确定。 SIG_IGN不理会
2) 使用sigaction (项目实战强烈推荐使用)
sigaction与signal的区别: sigaction比signal更“健壮”,建议使用sigaction
用法:man 2 sigaction
结构struct sigaction
struct sigaction {
void (sa_handler)(int); / 信号的响应函数 /
sigset_t sa_mask; /
屏蔽信号集 /
int sa_flags; /
当sa_flags中包含 SA_RESETHAND时,接受到该信号并调用指定的信号处理函数执行之后,把该信号的响应行为重置为默认行为SIG_DFL */

}
补充:
当sa_mask包含某个信号A时,则在信号处理函数执行期间,如果发生了该信号A,
则阻塞该信号A(即暂时不响应该信号),直到信号处理函数执行结束。
即,信号处理函数执行完之后,再响应该信号A
实例:main4.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig)
{
	printf("Catch a signal : %d\n", sig);
}

int main(void)
{
	struct sigaction act;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	sigaction(SIGINT, &act, 0);

	while (1) {

	}
	return 0;
}

用sigaction改变响应动作
main5.c 用sigaction改写main2.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myhandle(int sig)
{
	printf("Catch a signal : %d\n", sig);
}

int main(void)
{
	struct sigaction act;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	//act.sa_flags = 0;
	act.sa_flags = SA_RESETHAND;

	sigaction(SIGINT, &act, 0);

	while (1) {

	}

	return 0;
}

用sigaction恢复默认动作
用sigaction改写main3.c
 信号的发送
信号的发送方式:
在shell终端用快捷键产生信号
使用kill,killall命令。
使用kill函数和alarm函数
1) 使用kill函数
给指定的进程发送指定信号
用法:man 2 kill
注意:
给指定的进程发送信号需要“权限”:
普通用户的进程只能给该用户的其他进程发送信号
root用户可以给所有用户的进程发送信号
kill失败
失败时返回-1
失败原因:
权限不够
信号不存在
指定的进程不存在
实例:main6.c创建一个子进程,子进程每秒中输出字符串“child process work!",父进程等待用户输入,如果用户按下字符A, 则向子进程发信号SIGUSR1, 子进程的输出字符串改为大写; 如果用户按下字符a, 则向子进程发信号SIGUSR2, 子进程的输出字符串改为小写.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int workflag = 0;

void work_up_handle(int sig)
{
	workflag = 1;
}

void work_down_handle(int sig)
{
	workflag = 0;
}

int main(void)
{
	pid_t pd;
	char c;

	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	}
	else if (pd == 0) {
		char* msg;
		struct sigaction act;
		act.sa_flags = 0;
		act.sa_handler = work_up_handle;
		sigemptyset(&act.sa_mask);
		sigaction(SIGUSR1, &act, 0);

		act.sa_handler = work_down_handle;
		sigaction(SIGUSR2, &act, 0);

		while (1) {
			if (!workflag) {
				msg = "child process work!";
			}
			else {
				msg = "CHILD PROCESS WORK!";
			}
			printf("%s\n", msg);
			sleep(1);
		}
	}
	else {
		while (1) {
			c = getchar();
			if (c == 'A') {
				kill(pd, SIGUSR1);
			}
			else if (c == 'a') {
				kill(pd, SIGUSR2);
			}
		}
	}

	return 0;
}

实例:main7.c “闹钟”,创建一个子进程,子进程在5秒钟之后给父进程发送一个SIGALR,父进程收到SIGALRM信号之后,“闹铃”(用打印模拟)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

int wakeflag = 0;

void wake_handle(int sig)
{
	wakeflag = 1;
}

int main(void)
{
	pid_t pd;
	char c;

	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	}
	else if (pd == 0) {
		sleep(5);
		kill(getppid(), SIGALRM);
	}
	else {
		struct sigaction act;
		act.sa_handler = wake_handle;
		act.sa_flags = 0;
		sigemptyset(&act.sa_mask);

		sigaction(SIGALRM, &act, 0);

		pause(); //把该进程挂起,直到收到任意一个信号

		if (wakeflag) {
			printf("Alarm clock work!!!\n");
		}
	}
	return 0;
}

2)使用alarm函数
作用:在指定时间之内给该进程本身发送一个SIGALRM信号。
用法:man 2 alarm
注意:时间的单位是“秒”
实际闹钟时间比指定的时间要大一点。
如果参数为0,则取消已设置的闹钟。
如果闹钟时间还没有到,再次调用alarm,则闹钟将重新定时
每个进程最多只能使用一个闹钟。
返回值:
失败:返回-1
成功:返回上次闹钟的剩余时间(秒)
实例:“闹铃”
main8.c用alarm改写main7.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

int wakeflag = 0;

void wake_handle(int sig)
{
	wakeflag = 1;
}

int main(void)
{
	int ret;

	struct sigaction act;
	act.sa_flags = 0;
	act.sa_handler = wake_handle;
	sigemptyset(&act.sa_mask);
	sigaction(SIGALRM, &act, 0);

	printf("time =%ld\n", time((time_t*)0));

	ret = alarm(5);
	if (ret == -1) {
		printf("alarm error!\n");
		exit(1);
	}

	//挂起当前进程,直到收到任意一个信号
	pause();

	if (wakeflag) {
		printf("wake up, time =%ld\n", time((time_t*)0));
	}
	return 0;
}
  1. 使用raise kill可以给别人发信号
    给本进程自身发送信号。
    原型: int raise (int sig)
     发送多个信号
    某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),如果此时,该进程又多次收到同一个信号(同一种信号值的信号),
    则:如果该信号是不可靠信号(<32),则只能再响应一次。
    如果该信号是可靠信号(>32),则能再响应多次(不会遗漏)。但是,都是都必须等该次响应函数执行完之后,才能响应下一次。

    某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),如果此时,该进程收到另一个信号(不同信号值的信号),则:
    如果该信号被包含在当前信号的signaction的sa_mask(信号屏蔽集)中,则不会立即处理该信号。直到当前的信号处理函数执行完之后,才去执行该信号的处理函数。
    否则:
    则立即中断当前执行过程(如果处于睡眠,比如sleep, 则立即被唤醒)而去执行这个新的信号响应。新的响应执行完之后,再在返回至原来的信号处理函数继续执行。
    例:main4_2.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig)
{
	printf("Catch a signal : %d\n", sig);
	int i;
	for (i = 0; i < 10; i++) {
		sleep(1);
	}
	printf("Catch end.%d\n", sig);
}

int main(void)
{
	struct sigaction act, act2;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask, SIGUSR1);
	act.sa_flags = 0;

	sigaction(SIGINT, &act, 0);

	act2.sa_handler = myhandle;
	sigemptyset(&act2.sa_mask);
	act2.sa_flags = 0;
	sigaction(SIGUSR1, &act, 0);

	while (1) {

	}
	return 0;
}
  1. 信号集
    1). 什么是信号集
    信号集,用sigset_t类型表示,实质是一个无符号长整形。
    用来表示包含多个信号的集合。
    2). 信号集的基本操作
    sigemptyset 把信号集清空
    sigfillset 把所有已定义的信号填充到指定信号集
    sigdelset 从指定的信号集中删除指定的信号
    sigaddset 从指定的信号集中添加指定的信号
    sigismember 判断指定的信号是否在指定的信号集中
    如果是, 返回 1
    如果不是, 返回 0
    信号无效, 返回-1
    详细用法见 man
    3) 进程的“信号屏蔽字”
    进程的“信号屏蔽字”是一个信号集
    想目标进程发送某信号时,如果这个信号在目标进程的信号屏蔽字中,
    则目标进程将不会捕获到该信号,即不会执行该信号的处理函数。
    当该进程的信号屏蔽字不再包含该信号时,则会捕获这个早已收到的信号(执行对应的函数)
    修改进程的“信号屏蔽字”
    使用sigprocmask
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    参数:
    how:
    SIG_BLOCK 把参数set中的信号添加到信号屏蔽字中
    SIG_UNBLOCK 把参数set中的信号从信号屏蔽字中删除
    SIG_SETMASK 把参数set中的信号设置为信号屏蔽字
    oldset
    返回原来的信号屏蔽字
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void myhandle(int sig)
{
	printf("Catch a signal : %d\n", sig);
	printf("Catch end.%d\n", sig);
}

int main(void)
{
	struct sigaction act, act2;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, 0);

	sigset_t proc_sig_msk, old_mask;
	sigemptyset(&proc_sig_msk);
	sigaddset(&proc_sig_msk, SIGINT);

	sigprocmask(SIG_BLOCK, &proc_sig_msk, &old_mask);
	sleep(5);
	printf("had delete SIGINT from process sig mask\n");
	sigprocmask(SIG_UNBLOCK, &proc_sig_msk, &old_mask);

	while (1) {

	}
	return 0;
}
  1. 获取未处理的信号
    当进程的信号屏蔽字中信号发生时,这些信号不会被该进程响应,
    可通过sigpending函数获取这些已经发生了但是没有被处理的信号

    用法: man sigpending
    返回值:成功则返回0
    失败则返回-1

  2. 阻塞式等待信号
    (1) pause
    阻塞进程,直到发生任一信号后
    (2) sigsuspend
    用指定的参数设置信号屏蔽字,然后阻塞时等待信号的发生。
    即,只等待信号屏蔽字之外的信号

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46376834/article/details/129671331

【Linux】IPC进程间通信:管道、共享内存、消息队列、信号量-爱代码爱编程

进程间通信,英文简称IPC,本质上是两个进程之间的数据交换。因为进程具有独立性,每个进程都有自己的虚拟地址空间,访问的都是自己的虚拟地址,因此进程间无法直接通信,需要操作系统提供中间媒介进行通信。 进程间通信分为四种方式:管道、共享内存、消息队列、信号量。其中消息队列和信号量在平时应用较少,在本文中简略介绍。 管道 管道的本质是内核中的一块缓冲区,管道

进程间通信(IPC)的几种方式-爱代码爱编程

进程间通信(IPC) 1.常见的通信方式2.低级IPC方法文件3.常用于本机的IPC机制3.1管道(无名管道)3.2FIFO(命名管道)3.3消息队列MessageQueue3.4共享存储SharedMemory3.5信号量Semaphore3.6信号Signal3.7unix域套接字4.不同计算机上的IPC机制套接字Socket 1.常见的通

进程间通信:信号量-爱代码爱编程

进程间通信的机制,它们最初由AT&T System V.2版本的UNIX引IPC (Inter-Process Communication,进程间通信)机制,或被更常见的称为System V IPC。正如我们所看到的,它们并不是进程间通信的唯一方法,但人们通常把这些特定的机制称为System V IPC。 信号量 当我们编写的程序使用了线程时,

Linux IPC 进程间通信——信号量sem-爱代码爱编程

        目录 函数接口 示例一:用一个信号量实现互斥访问 运行效果: 示例二:用连个信号量实现有序访问 运行效果:  信号量有区别于管道、消息队列等IPC,它主要是通过访问一个计数器,通过判断它的值来决定是否可以访问一个公共资源(可以是全局变量、文件等)。 函数接口 定义:sem_t sem; 始化信号量:sem_init (se

IPC(进程间通信)-爱代码爱编程

IPC(进程间通信) 进程是操作系统分配资源的基本单位,也就是说进程间的资源是独立的。一个进程无法直接访问另一个进程的资源。 但是进程并不是孤立存在的,进程间需要进行数据传输、进程控制、通知事件、资源共享,所以进程间需要通信。 进程的用户空间对于每个进程而言是独立的,所有内核空间都是共享的。进程的内核空间是所有进程共享的。其实是mmu映射的物理内存属

IPC进程间通信C++开发:共享内存-爱代码爱编程

不同进程之间通信,通常可以用共享内存/消息队列/信号量/管道等方法,在Linux系统下提供了相关的库函数来方便使用。 A. 共享内存 共享内存就相当于开辟了一块物理内存空间,不同的进程通过虚拟地址的映射都访问到同一块物理内存,这样就能直接在内存读写数据。下面说一下具体用到的函数: # 查看当前系统的共享内存状态。 ipcs -m 1. shmget

进程间通信(ipc)_一言难尽、的博客-爱代码爱编程

为什么需要进程间通信 1).数据传输 一个进程需要将它的数据发送给另一个进程。 2).资源共享 多个进程之间共享同样的资源。 3).通知事件 一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。 4).进程控制 有些进程希望完全控制另一个进程的执行(如Debug进程),该控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的

进程间通信(ipc)介绍_纪录先生的博客-爱代码爱编程

达者为先  师者之意 进程间通信(IPC)介绍 1 管道2 创建命名管道(FIFO)3 消息队列4 共享内存5 Linux 信号 进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。 IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储

进程间通信ipc(三)之linux信号-爱代码爱编程

Linux信号: 1. 在Linux中,为了响应各种事件,提供了几十种信号,可以通过kill -l命令查看。 2. 对于Linux来说,实际信号是软中断,许多重要的程序都需要处理信号,信号为Linux提供了一种处理异步事件的方法,比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序 信号概述:

system v ipc进程间通信机制一网打尽-爱代码爱编程

目录 必备IPCS命令解析 ipcs   ipcrm    Linux IPC消息队列 msgget msgsnd msgrcv msgctl Linux IPC信号量 理解信号量 semget semop semctl Linux IPC共享内存 shmget shmat shmdt​  shmctl