进程间的通信

1进程间的通信

        每个进程都有自己的地址空间,进程之间不能直接访问彼此,因此,进程之间的通信需要一个媒介,它就是内核,在内核开辟出一块缓冲区,P1进程把数据从用户空间拷贝到内核缓冲区,P2进程再把数据从内核缓冲区中读走。内核提供的这种机制称为进程间的通信。能够实现进程间的通信的方法有:信号(signal)、管道(pipe)、套接字(socket)和System V IPC机制。

                                                                              


2.信号

        信号是Linux系统中用于进程之间相互通信或操作的一种机制。信号可以在任何时候发送给某一进程,而无需知道该进程的状态。如果该近侧很难过当前并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。信号之间依靠它们的值来区分,但是通常在程序中使用信号的名称来表示一个信号。信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以在用户空间和内核之间直接交互,内核也可以通过信号来通知用户空间的进程发生了哪些系统事件。信号事件主要分为硬件来源和软件来源。

        使用系统命令kill列出linux系统所支持的所有信号列表:

                                                                              

        信号分为可靠信号和不可靠信号:

                可靠信号(实时信号):支持排队,发送用户进程一次就注册一次,发现相同信号已经在进程中注册,也要再注册。

                不可靠信号(非实时信号):不支持排队,发送用户进程判断后注册,发现相同信号已经在进程中被注册,就不再注册,忽略该信号。

        一个完整的信号周期如下:  

                                                                             

        一旦有信号产生,用户进程对信号的响应方式有三种:

  • 执行默认操作。Linux对每种信号都规定了默认操作。
  • 捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数。
  • 忽略信号。当不希望接收到的信号对进程执行产生影响,而让进程继续执行时,可以忽略该信号,即不对信号进程作任何处理。

        但有两个信号是应用程序无法捕捉和忽略的,即SIGKILL和SIGSTOP。这是为了使系统管理员能在任何时候中断或结束某一特定的进程。

        Linux常见的信号的含义及默认操作

       

信号名含义默认操作
SIGHUP该信号在用户终端连接结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。终止
SIGINT该信号在用户按下中断键时,系统便会向该终端相关的进程发送此信号终止
SIGQUIT该信号在用户按下退出键时,系统会发送此信号,造成进程非正常终止终止
SIGILL该信号在一个进程企图执行一条非法指令时发出终止
SIGFPE该信号在发生致命的算数运算时发出终止
SIGKILL该信号是一个特殊的信号,用来立即结束程序的运行,并且不能被阻塞终止
SIGALARM该信号在一个定时器计时完成时发出,定时器可用进程调用alarm函数来设置终止
SIGSTOP该信号是一个特殊信号,用于暂停一个进程,并且不能被阻塞、处理或忽略暂停进程
SIGTSTP在用户按下挂起键时,系统会发送此信号,会造成进程挂起停止进程
SIGCHLD该信号在子进程结束时,向父进程发出。当进程中有子进程时,若运行了exit函数,就会向父进程发送此信号。此时,如果父进程正在运行wait函数,则它会被唤醒;但如果父进程没有在运行wait函数,它就不会捕捉此信号,此时子进程就会变成僵尸进程忽略
      

        信号操作中常用的函数如下:

函数功能
kill发送信号SIGKILL给进程或进程组
raise发送信号给进程或自身
alarm定时器时间到时,向进程发送SIGALARM信号
pause没有捕捉信号前一直将进程挂起
signal捕捉信号SIGINT、SIG_IGN、SIG_DFL或SIGQUIT时执行信号处理函数
sigemptyset初始化信号集合为空
sigfillset初始化信号集合为所有信号集合
sigaddset将指定信号加入到指定集合
sigdelset将指定信号从信号集中删除
sigismember查询指定信号是否在信号集合之中
sigprocmask判断检测或更改信号屏蔽字

        信号发送:信号发送的关键是使系统直到向哪个进程发送信号以及发送什么信号。能否向某一进程发送某一特定信号是和用户的权限密切相关的。     

        下面的程序应用函数alarm(10)在运行10秒后发送信号SIGALARM,程序接受到SIGALARM信号就终止。

                                                                                       

                                                                                       

        下面的程序中用户进程创建一个子进程,父进程向子进程发出SIGKILL信号,子进程接收到此信号,结束子进程的运行。

                                                                                       

                                                                                       

        信号处理: 当某个信号被发送到一个正在运行的进程时,该进程即对此特定信号注册相应的信号处理函数,以完成所需处理。也就是说,在编写程序代码时,对需要进程捕获的信号给出相应的处理程序代码。一旦接受到此信号,则通知系统调用相应信号处理函数做出处理。

        下面的程序运行后进入无限循环,当用户按下中断键(Ctrl+C)时,进入程序的自定义信号处理函数,当用户再次按下中断键(Ctrl+C)后,结束程序运行。

                                                                                       

                                                                                       

       

                函数功能:设置信号处理方式

                函数原型:void (*signal(int signum,void(* handler)(int)))(int);

                函数传入值:signal()会依据参数signum指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是:a.SIG_IGN忽略参数signum指定的信号  b.SIG_DFL将参数signum指定的信号重设为核心预设的信号处理方式

                 函数返回值:返回先前的信号处理函数的指针,如果有错误则返回SIG_ERR(-1)

        信号阻塞:有时既不希望进程在接收到信号时立即中断进程的执行,也不希望此信号完全被忽略掉,而是新网延迟一段时间再去调用信号处理函数,这个时候就需要信号阻塞来完成。

        设计一个程序,要求主程序运行时,即使用户按下中断键(Ctrl+C),也不能影响正在运行的程序,即让信号处于阻塞状态,当主体程序运行完毕后才进入自定义信号处理函数。

                                                                                      

                                                                                      

        下列的程序能处理三种不同的信号,其中信号SIGINT(Ctrl+C键)和SIGTSTP(Ctrl+Z键)是可以阻塞的,而信号SIGQUIT(Ctrl+、键)是不可阻塞的。

                                                                                      

                                                                                      

                                                                                      

                                                                                      

3.管道

        管道允许在进程之间按先进先出的方式发送数据,是进程间通信的一种常见方式。

        管道分为pipe(无名管道)和FIFO(命名管道)两种。出了建立、打开、删除的方式不同外,这两种管道几乎是一样的。它们都是通过内核缓冲区实现数据传输。pipe用于相关进程之间的通信,如父进程和子进程。它通过pipe()系统调用来创建并打开,当最后一个使用它上网进程关闭对它的引用时,pipe将自动撤销。FIFO即命名管道,在磁盘上有对应的节点,但没有数据块。换言之,只是拥有一个名字和相应的访问权限。它是通过哦mknod()系统调用或mkfifo()函数来建立的。一旦建立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程。当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。

        管道操作中的常用函数如下表所示:

   

函数功能
pipe创建无名管道
popen创建标准流管道
mkfifo判断检测或更改信号屏蔽字

        管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区中存取数据:管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。该缓冲区可以看作是一个循环队列,读和写的位置都是自动增加的,不能随意改变,一个数据只能被读取一次,读出以后在缓冲区中就不复存在了。当缓冲区读空或写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列;当空的缓冲区有新数据写入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。

         无名管道实际上以类似文件的方式与进程交互,但它并不与磁盘打交道,所以效率要比文件操作高很多。建立无名管道用pipe函数,一个管道就如一个代开的文件,主要包括两个file结构----分别用于读和写。使用管道的一个典型的过程如下(以从进程P1向进程P2写数据为例):

                                                    P1:调用pipe()函数,得到上述两个file结构的两个文件描述符;

                                                    P2:调用fork()创建进程P2;

                                                    P2:调用close()关闭与写file结构相关的文件描述符;

                                                    P1:调用close()关闭与读file结构相关的文件描述符;

                                                    P1:调用write()通过写相关的文件描述符向管道写数据;

                                                    P2:调用read()通过读相关的文件描述符从管道读数据;

                                                    P1:调用close()关闭写相关的文件描述符;

                                                    P2:调用close()关闭读相关的文件描述符;

        下面的程序,创建一个管道,复制进程,父进程往管道中写入字符串,子进程从管道中读取前输出字符串。

                                                                                         

                                                                                         

        设计两个程序,要求用命名管道FIFO实现简单的聊天功能

                                                                                        

                                                                                        

                                                                                        

                                                                                        

                                                                                        

        设计一个程序,要求用popen创建管道,实现“ls -l|grep  promsg7.c"的功能。

                                                                                        

                                                                                        

                                                                                        

4.消息队列

        消息队列,就是一个消息的链表,是一系列保存在内核中的消息的列表。用户进程可以向消息队列添加消息,也可以从消息队列读取消息。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按队列次序,而是可以根据自定义条件接收特定类型的消息。可以把消息看作是一个记录,可以向其中添加新消息和读取消息。消息队列的常用函数如下表所示:

函数功能
ftok由于文件路径工程ID生成标准key
msgget创建或打开消息队列
msgsnd添加消息
msgrcv读取消息
msgctl控制消息队列
        下面的程序用函数msgget创建消息队列,接着调用msgsnd函数,把输入的字符串添加到消息队列中,然后调用msgrcv函数读取消息队列中的消息并打印输出,最后调用msgctl函数删除系统内核中的消息队列。

                                                                                         

                                                                                         

                                                                                         

                由上例可以看出,进程间通过消息队列通信,主要是创建或打开消息队列、添加消息、读取消息和控制消息队列这四种操作。

5.共享内存

        共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中。一个进程写入共享内存中的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存操作读出,从而实现了进程间的通信。采用共享内存通信的一个主要的好处就是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户控件进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。一般情况下,共享内存会保持共享区域直到通信完毕才写回文件,通信中数据则一直在内存中。共享内存可以通过内存映射机制实现,也可以通过UNIX System V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号量等同步机制共同使用。共享内存的常用函数如下:

函数功能
mmap建立共享内存映射
munmap解除共享内存映射
shmget获取共享内存区域的ID
shmat建立映射共享内存
shmdt解除共享内存映射
        内存映射机制使进程之间通过映射通一个普通文件实现共享内存,通过mmap()系统调用实现。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read和write等文件操作函数。

        设计一个程序,要求创建进程,父子近侧很难过通过匿名映射实现共享内存。

                                                                                         

                                                                                         

                                                                                        

        UNIX System V IPC的共享内存指的是把所有共享数据放在共享内存区域,任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。和前面的mmap系统调用通过映射一个普通文件实现共享内存不同,UNIX System V共享内存是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。

        设计两个程序,要求通过UNIX System V共享内存通信机制,一个程序写入共享区域,另一个程序读取共享区域。

                                                                                        

                                                                                        

                                                                                        

                                                                                        

        下面的两个程序通过UNIX System V共享内存通信机制,实现简单的聊天程序。

                                                                                        

                                                                                        

                                                                                        

                                                                                        

                                                                                        

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值