传统的进程间通信方式 1.无名管道(pipe) 2.有名管道(fifo) 3.信号(signal); 闹钟信号应用实例

传统的进程间通信方式 1.无名管道(pipe) 2.有名管道(fifo) 3.信号(signal)




linux下进程间的通信方式:
传统的进程间通信方式 1.无名管道(pipe) 2.有名管道(fifo) 3.信号(signal)
System V IPC 对象 1.共享内存(share memory) 2.消息队列(message queue ) 3.信号灯 (semaphore)
BSD
套接字(socket)
下面进行逐一介绍:
无名管道:这里所说的主要只无名管道,它具有如下特点,只能用于具有亲缘关系的进程之间的通信,半双工的通信模式,具有
固定的读端和写端,管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO,如read、write函数。
管道是基于文件描述符的通信方式,当一个管道建立时,他会创建两个文件描述符 fd[0]和fd[1].其中fd[0]固定用于读管道,而
fd[1]固定用于写管道。这样就构成了一个半双工的通道。


管道创建函数:


所需头文件 : #include <unistd.h>
函数原型 : int pipe(int fd[2])
函数参数 : fd 包含两个元素的整型数组
函数返回 : 成功0,出错 -1;
例如 ;


int main()
{
int pfd[2];
if(pipe(pfd)<0)
{
perror("pipe");
return -1;
}
else
{
printf("pipe create success\n");
}
close(fd[0]);
close(fd[1]);
}
当管道中无数据时,读操作会阻塞
向管道写数据时候,linux将不能保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据,
如果读进程不读走管道缓冲区中的数据,那么写操作会一直阻塞。
只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将受到内核传来的SIFPIPE信号(通常Broken pipe错误)
无名管道只能由于具有亲缘关系的进程之间,这就限制了无名管道的使用范围,有名管道可以使互不相关的两个进程互相通信,有名管道可以通过路径来指明,
并且在文件系统中可见。
进程通过文件IO来操作有名管道,fifo原则,不支持lseek操作。


创建管道函数:


#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
函数原型 ;
int mkfifo (const char * filename ,mode_t mode );
函数参数: filename :要创建的管道
mode :指定创建的管道的访问权限,一般用8进制表示
函数返回值 :成功0,失败-1




信号通信:
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它赖通知用户空间进程发生了哪些系统事件。
如果该进程当前并未处于执行态,则该信号就由内核保护起来,知道该进程恢复执行再传递给它,如果一个信号被进程设置为阻塞,则该信号的
传递被延迟,直到其阻塞被取消时菜被传递给进程。
用户进程对信号的响应方式:


忽略信号: 对信号不做任何处理,但是有两个信号不能忽略SIGKILL、SIGSTOP
捕捉信号: 定义信号处理函数,当信号发生时,执行响应的处理函数。
执行缺省操作: Linux对每种信号都规定了默认操作。




信号的发送和捕捉


信号的发送:
kill()和raise()
kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(kill系统命令只是kill函数的一个用户接口)


kill -l 命令查看系统支持的信号列表


raise函数允许进程向自己发送信号。


所需头文件:
#include <signal.h>
#include <sys/types.h>
函数原型:
int kill(pid_t pid,int sig)
函数参数:
pid:正数,要接受的进程的进程号
0,信号被发送到所有和pid进程在同一个进程组的进程
-1,信号发给所有的进程表中的进程(除了进程号最大的进程)
sig:信号
返回 成功 0,失败-1




int raise(int sig);


例子:


int main()
{
 pid_t pid;
 int ret;
 if((pid = fork())<0)
{
perror("fork");
exit(-1);
}
if(pid == 0)
{
raise(SIGSTOP);
printf("");
exit(0);
}
else
{
printf("pid =%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0)
{
kill(pid,SIGKILL);
printf("kill %d\n",pid);
}
}
}




另外两个比较重要的函数


alarm(),pause()
alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到的时候,内核就向进程发送SIGALARM信号。


pause()函数是用于将调用进程挂起,直到收到信号为止。


函数原型;
unsigned int alarm(unsigned int seconds)
函数参数:
seconds 指定秒数


函数返回值: 成功则返回 如果调用alarm()前,进程中已经设置了闹钟时间,则返回上个闹钟时间的剩余时间,否则返回0
失败 -1


int pause(void)


返回值 :-1,并且把error值设为EINTR。


alarm只是定时器,在时间未到的阶段程序会继续往下执行。




那么既然发出了信号,如何进行捕捉处理呢?因此引出了信号的捕捉及处理函数。
信号处理有2种方式:简单的signal()函数,使用信号集函数组
signal()
使用该函数的时候,需要指定要处理的信号和处理信号的函数。
函数原型:


void (*signal(int signum,void(*handler)(int)))(int);
函数参数:
signum :指定的信号
handler:SIG_IGN 忽略该信号
SIG_DFL 采用系统默认的方式处理信号
自定义的信号处理函数指针
函数返回值: 成功 设置之前的信号处理方式。
失败 -1;


例如:


void my_fun(int signo)
{
if(signo == SIGINT)
printf("get SIGINT\n");


}
int main()
{
signal(SIGINT,my_fun);
pause();
exit(0);


}
经典例子:


用信号的知识实现司机售票员问题:
1、售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印("let's gogogog")
2、售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印("stop the bus")
3、司机捕捉SIGTSTP(代表车到终点站)信号,向售票员发送SIGUSR1信号,售票员打印("please get off the bus")




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


pid_t pid;


void conductor(int signo)
{
switch ( signo )
{
case SIGINT:
kill(getppid(), SIGUSR1);
break;
case SIGQUIT:
kill(getppid(), SIGUSR2);
break;
case SIGUSR1:
printf("all get off the bus\n");
exit(0);
}


return;
}


void driver(int signo)
{
switch ( signo )
{
case SIGUSR1:
printf("gogogo\n");
break;
case SIGUSR2:
printf("stop the bus\n");
break;
case SIGTSTP:
kill(pid, SIGUSR1);
wait(NULL);
exit(0);
}


return;
}


int main()
{


if ((pid = fork()) < 0)
{
perror("fail to fork");
exit(-1);
}
else if (pid == 0) // conductor
{
signal(SIGINT, conductor);
signal(SIGQUIT, conductor);
signal(SIGUSR1, conductor);
signal(SIGTSTP, SIG_IGN);
while ( 1 )
{
pause();
}
}
else // driver
{
signal(SIGUSR1, driver);
signal(SIGUSR2, driver);
signal(SIGTSTP, driver);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
while ( 1 )
{
pause();
}
}


return 0;
}


 


 


 


pipe :具有亲缘关系的进程间,单工,数据在内存中
fifo:可用于任意进程间,双工,有文件名,数据在内存
signal:唯一的异步通信方式
msg:常用在cs模式中,按消息类型访问,可有优先级
shm:效率最高(直接访问内存),需要同步、互斥机制
sem:配合共享内存使用,用以实现同步和互斥


 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我来回答你的两个问题。 1. 进程通信信号概念及信号处理 在进程通信中,信号是一种异步通信机制,用于通知进程发生了某种事件。例如,当进程试图访问一个非法内存地址时,操作系统会向该进程发送一个 SIGSEGV 信号,告诉进程发生了段错误。进程可以通过注册信号处理函数来捕获这些信号并采取相应的措施。例如,在收到 SIGSEGV 信号时,进程可以释放一些内存,或者终止程序的执行。 信号处理器是一个函数,用于处理进程收到的信号。当进程收到一个信号时,操作系统会调用该进程注册的信号处理函数来处理该信号信号处理函数可以执行任何操作,例如修改全局变量、发送消息、关闭文件等。 2. 无名管道与命名管道进行通信 无名管道是一种匿名的、单向的、字节流式的管道,只能在具有公共祖先的进程之间使用。无名管道使用 pipe() 系统调用来创建,它返回两个文件描述符,一个用于读取管道内容,一个用于写入管道内容。在无名管道中,写入的数据会被存储在管道缓冲区中,读取数据时会从管道缓冲区中读取。 命名管道也称为FIFO,它是一种有名的、单向的、字节流式的管道,可以在不同的进程之间使用。命名管道使用 mkfifo() 系统调用来创建,它会在文件系统中创建一个特殊的文件,进程可以像读写普通文件一样读写该文件。在命名管道中,写入的数据也会被存储在管道缓冲区中,读取数据时会从管道缓冲区中读取。和无名管道不同的是,命名管道可以在不同的进程之间共享,可以用于跨网络的进程通信
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值