进程通信方式之管道通信,信号(信号集)

目录

1、为什么要实现进程之间的通信

2、进程之间通信方式

3、无名管道 

4、无写者、无读者的概念

 5、有名管道

6、信号

7、信号集

1、为什么要实现进程之间的通信

通过学习进程之间的通信,使得不同的进程之间能够实现数据的交换,例如test进程发送数据给project进程,project进程收到数据之后,

根据数据做出相应的事情。 (test进程控制project进程)

2、进程之间通信方式

以下几种进程之间的通信有一个共通的特点,都是只能在同一台主机内部的进程使用

1)管道通信-->pipe 管道通信分为有名管道与无名管道,管道是一个特殊的文件,进程通过将数据写入到管道中,另外一个进程从管道中读取数据出来。

2)信号 在linux下,有非常多的信号,例如:暂停,继续,停止...,某一个进程通过发送信号给另外一个进程,从而控制另外一个进程的运行状态。

3)消息队列 -->message 某一个进程把消息发送到队列上,另外一个进程就可以读取队列上的数据,消息队列好处:进程可以读取队列上某一个特定的数据。

4)共享内存 -->share mem 多个进程访问同一片内存空间。

5)信号量

3、无名管道 

无名管道只能作用于亲缘关系的进程之间的通信,例如父子进程

无名管道就是一个没有名字的管道文件,相当于一个队列结构

fd[1]为写入端(入队),fd[0]为读出端(出队)

信息读出后即删除,再次读取即为下一个信息

无名管道函数接口的说明如下:

#include <unistd.h>

int pipe(int pipefd[2]);
执行这个函数之后,得到两个文件描述符
int* pipefd
参数说明:
        pipefd:一个具有两个int类型变量的数组(两个文件描述符)
返回值:成功:0;
        失败:-1

注意:

1、pipefd[0]为读端,pipefd[1]为写端

2、没有名字,不能使用open()

3、只能用于亲缘进程间(比如父子进程,兄弟进程,祖孙进程...)通信,因为它只能在一个进程中被创建出来,然后通过继承的方式将它的文件描述符传递给子进程

4、半双工(单方向)工作方向

5、操作无原子性

6、无法使用lseek定位操作位置

说明:无名管道文件描述符的值,是从1024个值里面按照顺序拿取

无名管道的应用场景:

1、根据调用第三方程序的特点,文件描述符会被继承过去第三方程序中,如果你的第三方程序想要引用也是可以的(不方便)(不具备第三方调度程序的实用性)

2、守护进程的特点是将进程变成一个后台服务,并非同步运行多个进程(不具备引用匿名管道的实用性)

3、灵活使用: 管道的特点在于他本身拥有读写阻塞特性,你可以利用这一个特性制造出属于自己的特殊的缓冲区(缓冲区拥有流缓冲及阻塞特殊)

测试代码如下:

//无名管道

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

int main(int argc,char** argv)
{
    int fd[2];
    int ret = 0;    

    pipe(fd);

    pid_t id = fork();
    if(id > 0)    //父进程
    {
        char buf[1024] = { 0 };
        while(1)
        {
            bzero(buf,sizoef(buf));

            scanf("%s",byebye);

            write(fd[1],buf,strlen(buf));

            if(strlen(buf,"byebye") == 0)
            {
                printf("父进程退出程序,阻塞等待子进程退出,回收资源\n");
                break;
            }
        }
        wait(NULL);
    }
    else if(id == 0)    //子进程
    {
        cahr buf[1024] = { 0 }; 
        while(1)
        {
            bzero(buf,sizoef(buf));
            
            read(fd[0],buf,sizeof(buf));

            printf("%s\n",buf);

            if(strlen(buf,"byebye"))
            {
                printf("子进程退出\n");
                exit(0);
            }
        }
    }
    else if(id == 1)
    {
        perror("foek fail");
        return -1;
    }


    return 0;
}

4、无写者、无读者的概念

管道

有写者

无写者

有数据

无数据

有数据

无数据

读操作read

正常读取

阻塞等待

正常读取

立即返回

管道

有读者

无读者

有数据

无数据

有数据

无数据

写操作write

正常写入

阻塞等待

立即收到SIGPIPE

 5、有名管道

有名管道文件就是一个有名字的管道文件

在linux下,所有的进程都是可以看到这个文件

有名管道作用范围是整个Linux系统下任意的两个进程

有名管道的应用场景:

1、适用于进程间通信(小数据通信场景)

2、灵活场景:因为管道通信操作有原子性,可以实现普通文件的操作原子性

有名管道函数接口的说明如下:

mkfifo() --->  man 3 mkfifo
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);
函数作用:创建一个有名管道文件
参数:pathname有名管道文件的路径+名字
      mode管道文件的权限0777
返回值:成功:0
        失败:-1
        
access()  --->  man 2 access
#include<unistd.h>
int access(const char* path,int amode);
函数作用:判断文件是否存在
参数:path:文件路径
    amode:F_OK(常用) F_OK,R_OK,W_OK,X_OK
返回值:成功:0
        失败:-1

测试代码如下:

//有名管道发送端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int ret = 0;
    char buf[1024] = { 0 };

    //FIFO_PATH为有名管道文件的路径
    if(access(FIFO_PATH,F_OK) == -1)
    {
        if(mkkfifo(FIFO_PATH,0666) == -1)
        {
            perror("mkfifo fail");
            return -1;
        }
    }

    int fd_pipe = open(FIFO_PATH,O_RDWR);
    if(fd_pipe == -1)
    {
        perror("open fd_pipe fail");
        return -1;
    } 

    while(1)
    {
        bzero(buf,sizeof(buf));
        
        scanf("%s",buf);

        write(fd_pipe,buf,strlen(buf));

        if(strlen(buf,""byebye") == 0)
        {
            break;
        }   
    }

    close(fd_pipe)    

    return 0;
}

//接收端
int main()
{
    int ret = 0;
    char buf[1024] = { 0 };

    //FIFO_PATH为有名管道文件的路径
    if(access(FIFO_PATH,F_OK) == -1)
    {
        if(mkkfifo(FIFO_PATH,0666) == -1)
        {
            perror("mkfifo fail");
            return -1;
        }
    }

    int fd_pipe = open(FIFO_PATH,O_RDWR);
    if(fd_pipe == -1)
    {
        perror("open fd_pipe fail");
        return -1;
    } 

    while(1)
    {
        bzero(buf,sizeof(buf));
  
        read(fd_pipe,buf,sizeof(buf));

        printf("%s\n",buf);        

        if(strlen(buf,""byebye") == 0)
        {
            break;
        }   
    }

    close(fd_pipe)    

    return 0;
}

 管道通信的总结

1、管道是创建在内存中,进程结束空间释放,管道不复存在

2、无名管道和有名管道都是半双工通信(单方向),实现双向通信需要建立两个管道

3、无名管道是linux特殊文件

4、无名管道只用于父子进程之间,有名管道可用于任意两个进程之间

5、用有名管道读取的时候,里面有什么就读什么,进程无法决定自己想读什么就读什么 (消息队列可以决定自己想读取的内容)

6、无名管道具备无原子性,有名管道具备原子性

7、不能使用lseek进行定位

6、信号

信号是一种异步通信机制,一般情况下,进程什么时候会收到信号,收到什么信号是无法事先预料到的

在linux,使用kill -l查看linux的所有信号(信息值) + 信号名字)

信息值和信号的名字是等价的,宏定义,被定义在/usr/include/asm-generic/signal.h

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

总共62个信号,前31为非实时信号,后面的为非实时信号,没有32,33信号

非实时信号利用标志位记录,实时信号利用数值计算

1、非实时信号

1-31的信号值的信号

特点:1、每一个非实时信号一般对应着一个默认执行动作(缺省动作)

           2、每一个非实时信号都有自己的名字

           3、每一个非实时信号都有自己触发的系统时间

           4、信号可以被嵌套执行

           5、信号丢失(非实时信号的记录信号是用一个标志位来记录)

2、实时信号

34-64的信号值的信号

特点:1、每个信号都没有自己默认的执行动作

           2、每一个实时信号不一定都有自己的名字

           3、信号可以被嵌套执行

           4、信号不会丢失

系统当中有2个信号就算设置也会按照缺省动作执行动作

SIGKILL:杀死进程(9)

SIGSTOP:暂停进程(19)

信号的发送

1、由系统来发出

14) SIGALRM -> 当在程序中调用alarm()时,如果到点了,就会自动发出这个信号。

17) SIGCHLD -> 当子进程退出时,自动发出这个信号给父进程。SIGCHLD = signal child 2、信号也可以由用户来发送

kill / killall 这两个命令

方法一: 1)首先查看目标进程的pid号

                2)通过kill命令发送9号信号给该进程,杀死进程

kill是给pid值发送信号 (kill -s 9 id号)

方法二: 通过killall命令给进程名字发送信号

killall是给进程名发送信号 (killall -s 9 程序名)

信号的函数接口如下:

利用SIGUSR1 SIGUSR2
发送信号给另外一个进程
kill()  -> man 2 kill
#include <sys/types.h>
#include <signal.h>    -> linux信号的专属头文件
int kill(pid_t pid, int sig);
函数作用:
向指定进程或者进程组,发送一个指定的信号
参数:
pid:进程号
    sig:信号值
 
捕捉信号
signal()  -> man 2 signal
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针
sighandler_t signal(int signum, sighandler_t handler);
函数作用:
捕捉一个指定的信号,即预先为某信号的到来做好准备
参数:
signum     需要捕捉的信号
    handler    
SIG_IGN    忽略该信号
        SIG_DFL    执行该信号的默认动作
void (*p)(int)    执行由 p 指向的信号响应函数

返回值:
成功返回   最近一次调用该函数时第二个参数的值
    失败返回   SIG_ERR (#define SIG_ERR    -1)
注意:
>1.所谓的捕捉信号就是获取当这个信号来之后,去执行信号响应函数,原本的信号默认动作就不会执行了。
>2.当调用signal函数之后 ,程序不会阻塞,而是往下面代码执行,但是这个捕捉设置是全局有效
>3.SIGKILL 、SIGSTOP 不能被捕捉,只能执行默认的动作

挂起进程,直到收到一个信号为止。
#include <unistd.h>
int pause(void);
参数:无
返回值:
收到非致命信号或者已经被捕捉的信号 -1
    收到致命信号导致进程异常退出 不返回

注意:pause( )是在响应函数返回之后,随后再返回的

自己给自己发送信号
raise()
#include <signal.h>
int raise(int sig);
参数:
sig: 发送的信号。
返回值:成功:0
        失败:非0

信号的处理

signum通常使用SIGUSR1(10)或SIGUSR2(12)

1.忽略(将信号丢弃) signal(signum,SIG_IGN); //signal ignore

2.缺省(执行默认动作) signal(signum,SIG_DFL); //signal default

3.捕捉(去执行指定的函数) signal(signum,function);

4.阻塞(信号挂起) 设置阻塞之后,来了阻塞的指定信号,并不是将信号丢弃,而是将信号挂起,等到解除阻塞之后才去响应这个信号

注意: 9) SIGKILL和 19) SIGSTOP 这两个信号不能被忽略,阻塞、捕捉。必须是执行默认动作

7、信号集

信号集是一个集合,而每一个成员都是一个信号,通过将信号加入到信号集中,再设置阻塞状态给信号集 那么整个信号集里面所有的信号都会变成阻塞的状态

信号阻塞和信号忽略的区别

信号响应: 收到信号之后,会响应信号的动作。 signal(signum,function);

信号忽略: 收到信号之后,直接丢弃这个信号。signal(signum,SIG_IGN); //signal ignore

信号阻塞: 进程在阻塞某一个信号前提下,收到了这个信号,不会马上响应,而是要等到解除阻塞之后,才会响应这个信号。

(这个信号没有被响应时,不会丢弃,而是放在一个挂起队列中)

信号集处理函数接口如下:

信号集其实就是一个变量,数据类型时:sigset_t 
定义信号集:sigset_t set
#include<signal.h>
int sigemptyset(sigset_t *set);   清空信号集
int sigfillset(sigset_t *set); 将linux下所有的信号都加入到信号集中(很少见)
int sigaddset(sigset_t *set, int signum); 在指定的信号集set中,添加一个指定的信号signum
int sigdelset(sigset_t *set, int signum);在指定的信号集set中,删除一个指定的信号signum
int sigismember(const sigset_t *set, int signum); 测试某一个信号是不是在集合中

参数:
set:需要判断的信号集的地址
signum:需要测试的信号
返回值:成功:0
        失败:-1
 sigismember ()函数在集合中返回1,不在集合中返回0,失败返回 -1
 
 先定义,后清空,再添加、判断,选择性删除

//信号集阻塞函数
sigprocmask()  --->  man 2 sigprocmask
#include<signal.h>
int sigprocmask(int how,const sigset_t* set,sigset_t* oldest);
参数:how:SIG_BLOCK   -> 设置为阻塞的属性
           SIG_UNBLOCK -> 解除阻塞
      set: 你要设置哪个信号集,将这个信号集的地址传递过来
      oldset:保留之前状态的指针,如果不关心,则填NULL。
返回值:成功:0
        失败:-1
 
 说明:阻塞属性会被子进程继承

信号的总结:

1,普通操作当中,所有的信号都会被新来的信号打断自己的执行,所有不存在优先级的概念

2,挂起的信号是有优先级的

实时信号>非实时信号

实时信号按照信号值数字排列优先级

非实时信号没有优先级

3,子进程会继承父进程的基本上所有的东西(信号的设置),唯一不会被继承的是被挂起的信号

信号的安全体制:

1,为了程序的逻辑是正常的,一定要在每个函数的调用当中加入判断该函数是否调用成功

2,信号函数会打断你原本的逻辑,所以我们应该注意,不要在信号的响应当中操作共有资源,如果要操作请加入同步互斥机制

 信号集的测试代码如下:

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

void handle1(int arg)
{
	printf("捕捉到%d信号,111.....\n",arg);
} 

void handle2(int arg)
{
	printf("捕捉到%d信号,222.....\n",arg);
} 

void handle3(int arg)
{
	printf("捕捉到%d信号,333.....\n",arg);
}

int main(int argc, char*argv[]) 
{
	//自定义3个信号的响应动作(捕捉信号立即响应)
	signal(SIGUSR1,handle1); 
	signal(SIGUSR2,handle2);
	signal(SIGINT,handle3); //ctrl+c相当于SIGINT,此时被修改了,所以ctrl+c无法终止进程
	
    //1)先定义一个信号集变量(不要定义成指针变量)
    sigset_t  set;
    //2) 初始化(清空)
    sigemptyset(&set); //清空信号集
    //3)将信号 添加到集合中 SIGUSR1  SIGUSR2
    sigaddset(&set,SIGUSR1); //在指定的信号集set中,添加一个指定的信号signum到集合中
    sigaddset(&set,SIGUSR2); 
    sigaddset(&set,SIGINT); 
	
/*     //4) 判断 SIGUSR2信号是否在集合中,如果在打印yes  ,否则 打印 no
    // sigismember 信号在集合中 返回1  否则 返回 0
    if(sigismember(&set, SIGUSR1))
        printf("yes\n");
    else 
        printf("no\n");
    
	//将SIGUSR1从集合中删除了
	sigdelset(&set,SIGUSR1);
	
	//删除之后再测试的时候就不在集合中
    if(sigismember(&set, SIGUSR1))
        printf("yes\n");
    else 
        printf("no\n"); */
	
	//将信号集设置为阻塞状态(此时信号集里面所有的信号不会立即相应)
	sigprocmask(SIG_BLOCK, &set,NULL);
	int cnt=0;
	while(1)
	{
		
		printf("[%d]我正在中路超神...%d\n",getpid(),cnt);	
		sleep(1);
		
		//20秒之后将集合里面的信号设置为非阻塞状态,它才能够响应你发送的信号
		if(cnt == 20)
			sigprocmask(SIG_UNBLOCK, &set,NULL);
		
		//40秒之后将集合里面的信号又设置为阻塞状态,此时的信号又不能够立即相应
		if(cnt == 40)
			sigprocmask(SIG_BLOCK, &set,NULL);
		
		cnt++;
	}	
	
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值