Unix/Linux进程间通信的方式

Linux进程间通信的方式

Linux信号

Linux信号是Linux进程间通信的一种古老的方式,是一种异步的通信方式,进程向另一个进程发送一个信号,另一个进程接收到信号之后会触发一个处理事件,比如说Ctrl+C中断。

img

kill -l/-L:查看信号信息

zouren@ubuntu:~$ kill -L
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

编号1到31为传统UNIX信号,为不可靠信号,编号32到64为后来扩充的,为可靠信号,前者不支持排队,后者支持排队,前者一般都有特殊的定义,而后者通常提供给用户定义

信号产生方式

  • 硬件产生,浮点数异常,除数为0,无效内存访问…
  • Ctrl+C等用户操作
  • 软件异常产生信号,当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。
  • kill()函数发送信号
  • kill命令发送信号,常用此命令终止一个失控的后台进程

kill函数

头文件

#include<sys/types.h>
#include<signal.h>

函数原型

int kill(pid_t pid, int signum);

注意:使用kill函数时,接收信号和发送信号的进程的所有者必须相同,或者发送信号进程的所有者是超级用户

参数描述
pidpid > 0 发送信号给进程号为pid的进程,pid = 0 将信号发送给当前进程所在进程组中的所有进程,pid = -1将信号发送给系统内所有的进程,pid < -1信号传递给指定进程组的所有进程,进程组号为pid的绝对值
signum信号编号(可以是整数也可以是宏定义)

返回值

返回值描述
0成功
-1失败

测试代码

从父进程种fork出一个子进程,子进程一直输出信息,3秒后父进程向子进程发送中断信号

#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>

using namespace std;

int main(int argc, char* argv[]){
	
	pid_t pid;
	
	pid = fork();  // create son process
	if(pid < 0){
		perror("error to fork");
		
	}
	
	int i = 0;	

	if(pid == 0){
		// son process;
		while(1){
			cout<<"i am son!"<<endl;
			sleep(1);
		}
	}else if(pid > 0){
		while(1){
			cout<<"i am father"<<endl;
			sleep(1);
			i++;
			if(i == 3){
				kill(pid, SIGINT); // send the SIGINT signal to son process
			}
		}
	}
	return 0;
}
zouren@ubuntu:~/IPC/signal$ ./signal_kill_exe 
i am father
i am son!
i am son!
i am father
i am son!
i am father
i am father
i am father
i am father
i am father
i am father
i am father

等待信号

头文件

#include<unistd.h>

函数原型

int pause(void);

等待信号的到来,再信号到来之前,程序会被阻塞,直到捕捉到信号位置,用于判断信号是否已到

返回值

直到捕获到信号才返回-1,errno被置为EINTR

测试代码

程序输出一个信息,并调用pause,等待信号,在收到Ctrl+C之后退出程序

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

int main(int argc, char *argv[]){
    printf("i am waiting for signal\n");
    pause();
    return 0;
}
zouren@ubuntu:~/IPC/signal$ ./pause_test_exe 
i am waiting for signal
^C
zouren@ubuntu:~/IPC/signal$ 

信号处理的三种方式

进程收到信号之后,会执行三种操作种的一种

  • 执行默认操作,一般是终止进程
  • 忽略该信号
  • 执行自己定义的函数

SIGKILLSIGSTOP不能更改信号的处理方式,因为其向用户提供了终止进程的一种可靠方法

signal()函数连接信号处理函数

头文件

#include<signal.h>

函数原型

sighandler_t signal(signum, sighandler_t handler);

sighandler_t是一个函数,它的形式为

typedef void (*sighandler_t)(int); // 回调函数

signal会返回一个函数地址,handler有三个参数自定义函数地址、SIG_DEF(默认操作)、SIG_IGN(忽略)自定义函数的格式为

void funname(int signal){
 .....
}

这个自定义函数是可重入函数

返回值

情况返回值
成功第一次返回NULL,第二次返回信号处理函数的地址
失败SIG_ERR

测试代码

#include<iostream>
#include<signal.h>
#include<unistd.h>

using namespace std;

// 信号处理函数
void signal_handler(int signal){
   	if(signal == SIGINT){
        cout<<"receive signal SIGINT"<<endl;
    }else if(signal == SIGQUIT){
        cout<<"receive signal SIGQUIT"<<endl;
    }
}

int main(int argc, char *argv[]){
    
    cout<<"Waiting for SIGINT or SIGQUIT"<<endl;
    
    /*
    SIGINT: Ctrl + C
    SIGQUIT: Ctrl + \
    */
    // 信号注册函数
    signal(SIGINT, signal_handler);
    signal(SIGQUIT, signal_handler);
    
    // 等待信号
    pause();
    pause();
    
    return 0;
}
zouren@ubuntu:~/IPC/signal$ ./signal_link_exe 
Waiting for SIGINT or SIGQUIT
^Creceive signal SIGINT
^\receive signal SIGQUIT

测试代码2

测试signal的返回值

#include<unistd.h>
#include<signal.h>
#include<iostream>

using namespace std;

void signal_handler1(int signal){
    cout<<"i am fun1"<<endl;
}

void signal_handler2(int signal){
    cout<<"i am fun2"<<endl;
}

int main(int argc, char* argv[]){
    
    sighandler_t fun_addr = NULL;
    
    fun_addr = signal(SIGINT, signal_handler1);
    
    // 第一次返回NULL
    if(fun_addr == NULL){
        cout<<"return fun addr is NULL"<<endl;
    }
    
    // 第二次返回fun1的地址
    fun_addr = signal(SIGINT, signal_handler2);
    if(fun_addr == signal_handler1){
        cout<<"return fun addr is fun1"<<endl;
    }
    
    // 第三次返回fun2的地址
    fun_addr = signal(SIGINT, signal_handler1);
    if(fun_addr == signal_handler2){
        cout<<"return fun addr is fun2"<<endl;
    }
    
    return 0;
}
zouren@ubuntu:~/IPC/signal$ ./signal_ret_exe 
return fun addr is NULL
return fun addr is fun1
return fun addr is fun2

信号集与信号阻塞集

程序要对多个信号进程处理,这些信号存储在信号集种,信号集是表示多个信号定义的数据类型sigset_t,定义路径为/usr/include/i386-linux-gnu/bits/sigset.h

相关函数

#include<signal.h>
int sigemptyset(sigset_t *set); // 清空信号集
int sigfillset(sigset_t, *set);	// 将参数set进行初始化
int sigismember(const sigset_t *set, int signum); // 判断signum是否在信号集中
int sigaddset(sigset_t *set, int signum); // 在信号集中添加信号signum
int sigdelset(sigset_t *set, int signum); // 在信号集中删除信号signum

测试代码

#include<signal.h>
#include<iostream>

using namespace std;

int main(int argc, char *argv[]){
    
    sigset_t set; // 定义信号集
    int ret = 0;
    
    sigemptyset(&set); // 清空信号集中的内容
    
    // sigismember(); 判断信号是否在信号集中,存在返回1, 不存在返回0
    ret = sigismember(&set, SIGINT);
    if(ret == 0){
        cout<<"SIGINT is not a member of set \nret="<<ret<<endl;
    }else{
        cout<<"SIGINT is a member of set\nret="<<ret<<endl;
    }
    
    // 添加信号集
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);
    
    // 继续判断
    ret = sigismember(&set, SIGINT);
    if(ret == 1){
        cout<<"SIGINT is a member of set\nret="<<ret<<endl;
    }
    
    // 移除SIGQUIT信号
    sigdelset(&set, SIGQUIT);
    
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0){
        cout<<"SIGQUIT is not a member of set\nret="<<ret<<endl;
    }
    
    return 0;
}
zouren@ubuntu:~/IPC/signal$ ./sigset_exe 
SIGINT is not a member of set 
ret=0
SIGINT is a member of set
ret=1
SIGQUIT is not a member of set
ret=0

信号阻塞集

每一个进程都有一个自己的信号阻塞集,又称为(屏蔽集、掩码),用于描述那些信号递送到该进程的时候被阻塞,(信号被记录,等到进程空闲准备好之后再执行它),子进程会继承父进程的信号阻塞集

阻塞不是进制传递,而是延缓传递信号,可以通过sigprocmask()来修改当前的信号掩码要改变信号的阻塞情况

头文件

#include<signal.h>

函数原型

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

根据how的值,对信号信号阻塞集进行检查或修改,set为处理后新的信号阻塞集,oldset为之前的信号阻塞集(如果oldset不为NULL的话)

how:

  • SIG_BLOCK: 向信号阻塞集中添加set集,新的信号掩码是两者的并集set U oldset

  • SIG_UNBLOCK:从信号阻塞集中删除set集

  • SIG_SETMASK:信号阻塞集设置为set,原来的信号阻塞集放到oldset,之前的信号组赛集会清空

set:

  • 需要操作的信号集的地址

oldset:

  • 原来的信号阻塞集的地址

返回值

描述返回值
成功0
失败(how非法)-1EINVAL

不能阻塞SIGKILL和SIGSTOP信号,并且阻塞SIGFPE可能会导致意想不到的后果

测试代码

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

using namespace std;

int main(int argc, char *argv[]){
    
    sigset_t set; // 定义信号集
   	int i = 0;
    
    sigemptyset(&set);		 // 清空信号集
   	sigaddset(&set, SIGINT); // 添加SIGINT信号集
    
    while(1){
        // set集假如阻塞集,没有移除前,SIGINT被阻塞
        sigprocmask(SIG_BLOCK, &set, NULL);
        for(i = 0; i<5 ; ++i){
            cout<<"SIGINT signal is blocked!"<<endl;
            sleep(1);
        }
        
        // 从信号阻塞集中移除SIGINT
        // 假如SIGINT信号在被阻塞时发生了
        // 此时SIGINT信号立马生效,中断进程
        sigprocmask(SIG_UNBLOCK, &set, NULL);
        for(i = 0; i<5 ; ++i){
            cout<<"SIGINT signal is unblocked"<<endl;
            sleep(1);
        }
    }
    
    return 0;
}
zouren@ubuntu:~/IPC/signal$ ./sigset_block_exe 
SIGINT signal is blocked!
SIGINT signal is blocked!
^CSIGINT signal is blocked!
SIGINT signal is blocked!
SIGINT signal is blocked!

zouren@ubuntu:~/IPC/signal$ 

可靠信号(1-32):不支持排队,多次发送相同的信号,但是进程只能收到一次,可能会丢失

不可靠信号(33-64):支持排队,发多少次,接收多少次

signal函数只能提供简单的函数安装操作,主要用于不可靠的信息,不支持传递信息

sigaction函数

sigaction()用来检查和更改信号处理操作,支持可靠,不可靠信号,同时支持信号传递信息

头文件

#include<signal.h>

函数原型

int sigqueue(pid_t pid, int signal, const union sigval value);

给指定进程发送信号

pid:进程号

signal:信号编号

value:传递的信息

sigval是一个共用体

union sigval{
 int sigval_int;
 void *sigval_ptr; // 一个指针地址
}

成功返回0,失败返回-1

int sigcation(int signum, const struct sigcation *act, struct sigaction *oldact);

检查或修改指定信号的设置

signum:信号编号

act: 要设置的对信号的新的处理方式

oldact:原来的对信号的处理方式

成功返回0,失败返回-1

信号设置结构体

struct sigaction{
    /*旧的信号处理函数指针*/
    void (*sa_handler)(int signum);
    
    /*新的信号处理函数指针*/
    void (*sa_sigaction)(int signum, siginfo_t *info, void *context);
    
    /*信号阻塞集*/
    sigset_t sa_mask;
    
    /*信号处理形式*/
    int sa_flags;
}

sig_handler和sa_sigaction:处理方式SIG_DEF,SIG_IGN,自定义

sa_mask:信号阻塞集

sa_flags:指定信号处理的行为,值按照位或组合

描述
SA_RESTART使被信号打断的系统调用自动重新发起(已废弃)
SA_NOCLDSTOP使父进程在它的子进程暂停或继续运行时不会收到SIGCHLD信号
SA_NOCLDWAIT使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程
SA_NODEFER使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
SA_RESETHAND信号处理之后重新设置为默认的处理方式
SA_SIGINFO使用sa_sigaction作为信号处理函数

sa_sigaction信号处理函数:

void sa_sigaction(int signum, siginfo_t *info, void *context)

signum: 不赘述

siginfo_t: 记录信号发送进程信息的结构体


context:可以赋给执行ucontext_t类型的一个对象的指针,以引用在传递信号时被终端的接收进程或线程的上下文


测试代码

一个进程是接收程序,另外一个进程给该进程发送整数数据,默认为100

#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>

/*********************
发送SIGINT信号和携带的值给指定进程
参数	argv[1] 目标进程号
	  argv[2] 发送的值
返回值 0
*********************/

int main(int argc, char *argv[]){
    
	if(argc >= 2){
        pid_t pid, pid_self;
    	union sigval tmp;
    
    	pid = atoi(argv[1]); // 目标进程号
    	if(argc >= 3){
        	tmp.sival_int = atoi(argv[2]);
    	}else{
        	tmp.sival_int = 100;
    	}
    
    	// 给进程pid发送SIGINT信号,并发送数值
    	sigqueue(pid, SIGINT, tmp);
    
   		pid_self = getpid();
    
    	printf("pid = %d, pid_self = %d\n", pid, pid_self);
    }
    
    return 0;
}
// 接收程序
#include<signal.h>
#include<stdio.h>
#include<unistd.h>

void signal_handler(int signum, siginfo_t *info, void *ptr){
    printf("signum = %d\n", signum);
    printf("info->pid = %d\n", info->si_pid);
    printf("info->si_sigval = %d\n", info->si_value.sival_int);
}

int main(int argc, char *argv[]){
    struct sigaction act, oldact;
    
    act.sa_sigaction = signal_handler;
    sigemptyset(&act.sa_mask); // 清空阻塞集
    act.sa_flags = SA_SIGINFO; // 指定调用signal_handler
    
    // 注册
    sigaction(SIGINT, &act, &oldact);
    
    while(1){
        printf("i am waiting for message!\nmy pid is %d\n", getpid());
        pause(); // 等待信号
    }
    
    return 0;
}
# 接收程序
zouren@ubuntu:~/IPC/signal$ ./recv_sigact_exe 
i am waiting for message!
my pid is 29759
signum = 2
info->pid = 29767
info->si_sigval = 3000
i am waiting for message!
my pid is 29759
signum = 2
info->pid = 29768
info->si_sigval = 100
i am waiting for message!
my pid is 29759
^\退出 (核心已转储)
zouren@ubuntu:~/IPC/signal$ ^C
# 发送程序
zouren@ubuntu:~/IPC/signal$ ./send_sigact_exe 29759 3000
pid = 29759, pid_self = 29767
zouren@ubuntu:~/IPC/signal$ ./send_sigact_exe 29759 
pid = 29759, pid_self = 29768

Linux无名管道

Linux下IPC通信的古老形式,所有UNIX都支持这种通信机制

特点

  • 半双工,数据同一时刻只能在一个方向上流动
  • 只能从管道一端写入,一端读出(不能混用)
  • 数据遵循先入先出原则
  • 所传送的数据是无格式的,需要双方遵循一定的规则
  • 管道是由操作系统管理的一小撮内存空间,不存在于文件系统中
  • 数据读取是一次性操作,读完之后,数据就从管道中消失了
  • 没有名字,只能在具有亲缘关系的进程中传输数据(父进程与子进程、兄弟进程)

img

使用管道

头文件

#include<unistd.h>

函数原型

int pipe(int filedes[2]);

创建一个无名管道

filedes:为int型数组的首地址,其存放了管道的文件描述符,filedes[0],filedes[1]

创建两个文件描述符fd[0]和fd[1],fd[0]fd[1]前者固定用于读取管道中的数据,后者固定用于往管道中写入数据。,一般I/O操作都不可以用来操作管道

返回值

返回值描述
成功0
失败-1

测试代码:

#include<iostream>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

using namespace std;

int main(int argc, char *argv[]){
    
    int fd_pipe[2] = {0};
    pid_t pid;
    
    if(pipe(fd_pipe) < 0){
        cerr<<"Pipe create error!"<<endl;
        perror("pipe");
    }else{
        cout<<"Pipe create successfully"<<endl;
    }
    
    pid = fork();
    
    if(pid < 0){
     	perror("fork");
        exit(-1);
    }else if(pid == 0){
        char buf[] = "I am the msg sender";
        // 写数据
        write(fd_pipe[1], buf, strlen(buf));
    }else if(pid > 0){
        wait(NULL); // 等待子进程结束,并回收其资源
        
        char str[50] = {0};
        
        // 读数据
        read(fd_pipe[0], str, sizeof(str));
        
        printf("str = [%s]\n", str);
    }
    
    return 0;
}
zouren@ubuntu:~/IPC/pipe$ ./unname_pipe1_exe 
Pipe create successfully
str = [I am the msg sender]
zouren@ubuntu:~/IPC/pipe$ 

因为管道的模型很类似于生产者与消费者的模型,因为缓冲区是有一定内存空间的,当缓冲区慢了之后,调用write的进程回阻塞到直到缓冲区腾出了空间才继续进行,而当缓冲区中没有数据时,调用read也会阻塞

管道读取也可以设置非阻塞,通过函数fcnt1(....)来进行设置

fcntl(fd, F_SETFL, 0); // 阻塞
fcntl(fd, F_SETFL, O_NONBLOCK);
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>

using namespace std;

int main(int argc, char *argv[]){
    
    int fd_pipe[2] = {0};
    pid_t pid;
    
    if(pipe(fd_pipe) < 0){
        cerr<<"Pipe create error!"<<endl;
        exit(-1);
    }
    
    pid = fork();
    
    if(pid < 0){
        cerr<<"Failed to fork son process!"<<endl;
        exit(-1);
    }
    
    if(pid == 0){
        sleep(3);
        
        char buf[] = "hello csu! i love you";
        write(fd_pipe[1], buf, strlen(buf)); // 写数据
        
        _exit(0);
    }else if(pid > 0){
        fcntl(fd_pipe[0], F_SETFL, strlen(buf)); // 写数据
        
        while(1){
            char str[50] = {0};
            read(fd_pipe[0], str, sizeof(str));
            
            printf("str = [%s]\n", str);
            sleep(1);
        }
    }
    return 0;
}
zouren@ubuntu:~/IPC/pipe$ ./unblock_exe 
str=[]
str=[]
str=[]
str=[hello, edu]
str=[]
str=[]
^C
zouren@ubuntu:~/IPC/pipe$ ^C

在通信过程中,别的进程先结束后,当前进程都端口关闭后,向管道内写数据时,write所在进程会(收到SIGPIPE)退出,收到SIGPIPE默认动作为中断当前进程

#include<iostream>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

using namespace std;

int main(int argc, char *argv[]){
    
    int fd_pipe[2] = {0};
    pid_t pid;
    
    if(pipe(fd_pipe) < 0){
        cerr<<"Failed to create Pipe"<<endl;
        exit(-1);
    }
    
    pid = fork();
    
    if(pid < 0){
        cerr<<"Failed to create son process!"<<endl;
        exit(-1);
    }
    
    if(pid == 0){
        _exit(-1);
    }else if(pid > 0){
        
        wait(NULL); // 等待子进程结束,回收其资源
        
        close(fd_pipe[0]); // 关闭读端口
        
        char buf[50] = "12345";
        
        // 当前进程读端口关闭,write收到SIGPIPE信号,默认动作:中断当前进程
        write(fd_pipe[1], buf, strlen(buf));
        
        while(1);
        
    }
    
    return 0;
}
zouren@ubuntu:~/IPC/pipe$ ./signal_pi_exe 
zouren@ubuntu:~/IPC/pipe$ 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值