Linux进程间通信.docx

上传人:b****2 文档编号:17228164 上传时间:2023-07-23 格式:DOCX 页数:21 大小:23.09KB
下载 相关 举报
Linux进程间通信.docx_第1页
第1页 / 共21页
Linux进程间通信.docx_第2页
第2页 / 共21页
Linux进程间通信.docx_第3页
第3页 / 共21页
Linux进程间通信.docx_第4页
第4页 / 共21页
Linux进程间通信.docx_第5页
第5页 / 共21页
Linux进程间通信.docx_第6页
第6页 / 共21页
Linux进程间通信.docx_第7页
第7页 / 共21页
Linux进程间通信.docx_第8页
第8页 / 共21页
Linux进程间通信.docx_第9页
第9页 / 共21页
Linux进程间通信.docx_第10页
第10页 / 共21页
Linux进程间通信.docx_第11页
第11页 / 共21页
Linux进程间通信.docx_第12页
第12页 / 共21页
Linux进程间通信.docx_第13页
第13页 / 共21页
Linux进程间通信.docx_第14页
第14页 / 共21页
Linux进程间通信.docx_第15页
第15页 / 共21页
Linux进程间通信.docx_第16页
第16页 / 共21页
Linux进程间通信.docx_第17页
第17页 / 共21页
Linux进程间通信.docx_第18页
第18页 / 共21页
Linux进程间通信.docx_第19页
第19页 / 共21页
Linux进程间通信.docx_第20页
第20页 / 共21页
亲,该文档总共21页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

Linux进程间通信.docx

《Linux进程间通信.docx》由会员分享,可在线阅读,更多相关《Linux进程间通信.docx(21页珍藏版)》请在冰点文库上搜索。

Linux进程间通信.docx

Linux进程间通信

Linux进程间通信

一、进程间通信概述

进程通信有如下一些目的:

A、数据传输:

一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间

B、共享数据:

多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

C、通知事件:

一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

D、资源共享:

多个进程之间共享同样的资源。

为了作到这一点,需要内核提供锁和同步机制。

E、进程控制:

有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

Linux进程间通信(IPC)以下以几部分发展而来:

早期UNIX进程间通信、基于SystemV进程间通信、基于Socket进程间通信和POSIX进程间通信。

UNIX进程间通信方式包括:

管道、FIFO、信号。

SystemV进程间通信方式包括:

SystemV消息队列、SystemV信号灯、SystemV共享内存、

POSIX进程间通信包括:

posix消息队列、posix信号灯、posix共享内存。

现在linux使用的进程间通信方式:

(1)管道(pipe)和有名管道(FIFO)

(2)信号(signal)

(3)消息队列

(4)共享内存

(5)信号量

(6)套接字(socket)

二、管道通信

普通的Linuxshell都允许重定向,而重定向使用的就是管道。

例如:

ps|grepvsftpd.管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。

写进程在管道的尾端写入数据,读进程在管道的道端读出数据。

数据读出后将从管道中移走,其它读进程都不能再读到这些数据。

管道提供了简单的流控制机制。

进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。

同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。

管道主要用于不同进程间通信。

管道创建与关闭

创建一个简单的管道,可以使用系统调用pipe()。

它接受一个参数,也就是一个包括两个整数的数组。

如果系统调用成功,此数组将包括管道使用的两个文件描述符。

创建一个管道之后,一般情况下进程将产生一个新的进程。

系统调用:

pipe();

原型:

intpipe(intfd[2]);

返回值:

如果系统调用成功,返回0。

如果系统调用失败返回-1:

errno=EMFILE(没有空亲的文件描述符)

EMFILE(系统文件表已满)

EFAULT(fd数组无效)

注意:

fd[0]用于读取管道,fd[1]用于写入管道。

图见附件

管道的创建

#include<unistd.h>

#include<errno.h>

#include<stdio.h>

#include<stdlib.h>

intmain()

{

intpipe_fd[2];

if(pipe(pipe_fd)<0){

printf("pipecreateerror\n");

return-1;

}

else

printf("pipecreatesuccess\n");

close(pipe_fd[0]);

close(pipe_fd[1]);

}

管道的读写

管道主要用于不同进程间通信。

实际上,通常先创建一个管道,再通过fork函数创建一个子进程。

图见附件。

子进程写入和父进程读的命名管道:

图见附件

管道读写注意事项:

可以通过打开两个管道来创建一个双向的管道。

但需要在子理程中正确地设置文件描述符。

必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。

当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。

因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。

而在命名管道中却不是这样。

管道实例见:

pipe_rw.c

#include<unistd.h>

#include<memory.h>

#include<errno.h>

#include<stdio.h>

#include<stdlib.h>

intmain()

{

intpipe_fd[2];

pid_tpid;

charbuf_r[100];

char*p_wbuf;

intr_num;

memset(buf_r,0,sizeof(buf_r));数组中的数据清0;

if(pipe(pipe_fd)<0){

printf("pipecreateerror\n");

return-1;

}

if((pid=fork())==0){

printf("\n");

close(pipe_fd[1]);

sleep

(2);

if((r_num=read(pipe_fd[0],buf_r,100))>0){

printf("%dnumbersreadfrombepipeis%s\n",r_num,buf_r);

}

close(pipe_fd[0]);

exit(0);

}elseif(pid>0){

close(pipe_fd[0]);

if(write(pipe_fd[1],"Hello",5)!

=-1)

printf("parentwritesuccess!

\n");

if(write(pipe_fd[1],"Pipe",5)!

=-1)

printf("parentwirte2succes!

\n");

close(pipe_fd[1]);

sleep(3);

waitpid(pid,NULL,0);

exit(0);

}

}

标准流管道

与linux中文件操作有文件流的标准I/O一样,管道的操作也支持基于文件流的模式。

接口函数如下:

库函数:

popen();

原型:

FILE*open(char*command,char*type);

返回值:

如果成功,返回一个新的文件流。

如果无法创建进程或者管道,返回NULL。

管道中数据流的方向是由第二个参数type控制的。

此参数可以是r或者w,分别代表读或写。

但不能同时为读和写。

在Linux系统下,管道将会以参数type中第一个字符代表的方式打开。

所以,如果你在参数type中写入rw,管道将会以读的方式打开。

使用popen()创建的管道必须使用pclose()关闭。

其实,popen/pclose和标准文件输入/输出流中的fopen()/fclose()十分相似。

库函数:

pclose();

原型:

intpclose(FILE*stream);

返回值:

返回系统调用wait4()的状态。

如果stream无效,或者系统调用wait4()失败,则返回-1。

注意此库函数等待管道进程运行结束,然后关闭文件流。

库函数pclose()在使用popen()创建的进程上执行wait4()函数,它将破坏管道和文件系统。

流管道的例子。

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<fcntl.h>

#defineBUFSIZE1024

intmain(){

FILE*fp;

char*cmd="ps-ef";

charbuf[BUFSIZE];

buf[BUFSIZE]='\0';

if((fp=popen(cmd,"r"))==NULL)

perror("popen");

while((fgets(buf,BUFSIZE,fp))!

=NULL)

printf("%s",buf);

pclose(fp);

exit(0);

}

命名管道(FIFO)

基本概念

命名管道和一般的管道基本相同,但也有一些显著的不同:

A、命名管道是在文件系统中作为一个特殊的设备文件而存在的。

B、不同祖先的进程之间可以通过管道共享数据。

C、当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。

管道只能由相关进程使用,它们共同的祖先进程创建了管道。

但是,通过FIFO,不相关的进程也能交换数据。

命名管道创建与操作

命名管道创建

#include<sys/types.h>

#include<sys/stat.h>

intmkfifo(constchar*pathname,mode_tmode);

返回:

若成功则为0,若出错返回-1

一旦已经用mkfifo创建了一个FIFO,就可用open打开它。

确实,一般的文件I/O函数(close,read,write,unlink等)都可用于FIFO。

当打开一个FIFO时,非阻塞标(O_NONBLOCK)产生下列影响:

(1)在一般情况中(没有说明O_NONBLOCK),只读打开要阻塞到某个其他进程为写打开此FIFO。

类似,为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。

(2)如果指一了O_NONBLOCK,则只读打开立即返回。

但是,如果没有进程已经为读而打开一个FIFO,那么只写打开将出错返回,其errno是ENXIO。

类似于管道,若写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。

若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。

FIFO相关出错信息:

EACCES(无存取权限)

EEXIST(指定文件不存在)

ENAMETOOLONG(路径名太长)

ENOENT(包含的目录不存在)

ENOSPC(文件系统余空间不足)

ENOTDIR(文件路径无效)

EROFS(指定的文件存在于只读文件系统中)

fifo_write.c

#include<sys/types.h>

#include<sys/stat.h>

#include<errno.h>

#include<fcntl.h>

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#defineFIFO"/tmp/myfifo"

main(intargc,char**argv)

{

charbuf_r[100];

intfd;

intnread;

if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!

=EEXIST))

printf("cannotcreatefifoserver\n");

printf("Preparingforreadingbytes....\n");

memset(buf_r,0,sizeof(buf_r));

fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);

if(fd==-1)

{

perror("open");

exit

(1);

}

while

(1){

memset(buf_r,0,sizeof(buf_r));

if((nread=read(fd,buf_r,100))==-1){

if(errno==EAGAIN)

printf("nodatayet\n");

}

printf("read%sfromFIFO\n",buf_r);

sleep

(1);

}

pause();

unlink(FIFO);

}

fifo_read.c

#include<sys/types.h>

#include<sys/stat.h>

#include<errno.h>

#include<fcntl.h>

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#defineFIFO_SERVER"/tmp/myfifo"

main(intargc,char**argv)

{

intfd;

charw_buf[100];

intnwrite;

if(fd==-1)

if(errno==ENXIO)

printf("openerror;noreadingprocess\n");

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

if(argc==1)

printf("Pleasesendsomething\n");

strcpy(w_buf,argv[1]);

if((nwrite=write(fd,w_buf,100))==-1)

{

if(errno==EAGAIN)

printf("TheFIFOhasnotbeenreadyet.Pleasetrylater\n");

}

else

printf("write%stotheFIFO\n",w_buf);

}

三、信号

信号概述

信号是软件中断。

信号(signal)机制是Unix系统中最为古老的进程之间的能信机制。

它用于在一个或多个进程之间传递异步信号。

很多条件可以产生一个信号。

A、当用户按某些终端键时,产生信号。

在终端上按DELETE键通常产生中断信号(SIGINT)。

这是停止一个已失去控制程序的方法。

B、硬件异常产生信号:

除数为0、无效的存储访问等等。

这些条件通常由硬件检测到,并将其通知内核。

然后内核为该条件发生时正在运行的进程产生适当的信号。

例如,对于执行一个无效存储访问的进程产生一个SIGSEGV。

C、进程用kill

(2)函数可将信号发送给另一个进程或进程组。

自然,有些限制:

接收信号进和发送信号进程的所有都必须相同,或发送信号进程的的所有者必须是超级用户。

D、用户可用Kill(ID值)命令将信号发送给其它进程。

此程序是Kill函数的界面。

常用此命令终止一个失控的后台进程。

E、当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。

这里并不是指硬件产生条件(如被0除),而是软件条件。

例如SIGURG(在网络连接上传来非规定波特率的数据)、SIGPIPE(在管道的读进程已终止后一个进程写此管道),以及SIGALRM(进程所设置的闹钟时间已经超时)。

内核为进程生产信号,来响应不同的事件,这些事件就是信号源。

主要信号源如下:

(1)异常:

进程运行过程中出现异常;

(2)其它进程:

一个进程可以向另一个或一组进程发送信号;

(3)终端中断:

Ctrl-c,Ctro-\等;

(4)作业控制:

前台、后台进程的管理;

(5)分配额:

CPU超时或文件大小突破限制;

(6)通知:

通知进程某事件发生,如I/O就绪等;

(7)报警:

计时器到期;

Linux中的信号

1、SIGHUP2、SIGINT(终止)3、SIGQUIT(退出)4、SIGILL5、SIGTRAP6、SIGIOT7、SIGBUS8、SIGFPE9、SIGKILL10、SIGUSER11、SIGSEGVSIGUSER12、SIGPIPE13、SIGALRM14、SIGTERM15、SIGCHLD16、SIGCONT17、SIGSTOP18、SIGTSTP19、SIGTTIN20、SIGTTOU21、SIGURG22、SIGXCPU23、SIGXFSZ24、SIGVTALRM25、SIGPROF26、SIGWINCH27、SIGIO28、SIGPWR

常用的信号:

SIGHUP:

从终端上发出的结束信号;

SIGINT:

来自键盘的中断信号(Ctrl+c)

SIGQUIT:

来自键盘的退出信号;

SIGFPE:

浮点异常信号(例如浮点运算溢出);

SIGKILL:

该信号结束接收信号的进程;

SIGALRM:

进程的定时器到期时,发送该信号;

SIGTERM:

kill命令生出的信号;

SIGCHLD:

标识子进程停止或结束的信号;

SIGSTOP:

来自键盘(Ctrl-Z)或调试程序的停止扫行信号

可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。

(1)忽略此信号。

大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。

它们是:

SIGKILL和SIGSTOP。

这两种信号不能被忽略的,原因是:

它们向超级用户提供一种使进程终止或停止的可靠方法。

另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是示定义的。

(2)捕捉信号。

为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。

在用户函数中,可执行用户希望对这种事件进行的处理。

如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态。

(3)执行系统默认动作。

对大多数信号的系统默认动作是终止该进程。

每一个信号都有一个缺省动作,它是当进程没有给这个信号指定处理程序时,内核对信号的处理。

有5种缺省的动作:

(1)异常终止(abort):

在进程的当前目录下,把进程的地址空间内容、寄存器内容保存到一个叫做core的文件中,而后终止进程。

(2)退出(exit):

不产生core文件,直接终止进程。

(3)忽略(ignore):

忽略该信号。

(4)停止(stop):

挂起该进程。

(5)继续(contiune):

如果进程被挂起,刚恢复进程的动行。

否则,忽略信号。

信号的发送与捕捉

kill()和raise()

kill()不仅可以中止进程,也可以向进程发送其他信号。

与kill函数不同的是,raise()函数运行向进程自身发送信号

#include<sys/types.h>

#include<signal.h>

intkill(pid_tpid,intsigno);

intraise(intsigno);

两个函数返回:

若成功则为0,若出错则为-1。

kill的pid参数有四种不同的情况:

(1)pid>0将信号发送给进程ID为pid的进程。

(2)pid==0将信号发送给其进程组ID等于发送进程的进程组ID,而且发送进程有许可权向其发送信号的所有进程。

(3)pid<0将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送信号的所有进程。

如上所述一样,“所有进程”并不包括系统进程集中的进程。

(4)pid==-1POSIX.1未定义种情况

kill.c

#include<stdio.h>

#include<stdlib.h>

#include<signal.h>

#include<sys/types.h>

#include<sys/wait.h>

intmain()

{

pid_tpid;

intret;

if((pid==fork())<0){

perro("fork");

exit

(1);

}

if(pid==0){

raise(SIGSTOP);

exit(0);

}else{

printf("pid=%d\n",pid);

if((waitpid(pid,NULL,WNOHANG))==0){

if((ret=kill(pid,SIGKILL))==0)

printf("kill%d\n",pid);

else{

perror("kill");

}

}

}

}

alarm和pause函数

使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻时间值会被超过。

当所设置的时间被超过后,产生SIGALRM信号。

如果不忽略或不捕捉引信号,则其默认动作是终止该进程。

#include<unistd.h>

unsignedintalarm(unsignedintsecondss);

返回:

0或以前设置的闹钟时间的余留秒数。

参数seconds的值是秒数,经过了指定的seconds秒后产生信号SIGALRM。

每个进程只能有一个闹钟时间。

如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。

以前登记的闹钟时间则被新值代换。

如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。

pause函数使用调用进程挂起直至捕捉到一个信号

#include<unistd.h>

intpause(void);

返回:

-1,errno设置为EINTR

只有执行了一信号处理程序并从其返回时,pause才返回。

alarm.c

#include<unistd.h>

#include<stdio.h>

#include<stdlib.h>

intmain()

{

intret;

ret=alarm(5);

pause();

printf("Ihavebeenwakenup.\n",ret);

}

信号的处理

当系统捕捉到某个信号时,可以忽略谁信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。

信号处理的主要方式有两种,一种是使用简单的signal函数,别一种是使用信号集函数组。

signal()

#include<signal.h>

void(*signal(intsigno,void(*func)(int)))(int)

返回:

成功则为以前的信号处理配置,若出错则为SIG_ERR

func的值是:

(a)常数SIGIGN,或(b)常数SIGDFL,或(c)当接到此信号后要调用的的函数的地址。

如果指定SIGIGN,则向内核表示忽略此信号(有两个信号SIGKILL和SIGSTOP不能忽略)。

如果指定SIGDFL,则表示接到此信号后的动作是系统默认动作。

当指定函数地址时,我们称此为捕捉此信号。

我们称此函数为信号处理程序(signalhandler)或信号捕捉函数(signal-catchingfuncgion).signal函数原型太复杂了,如果使用下面的typedef,则可以使其简化。

typevoidsign(int);

sign*signal(int,handler*);

实例见:

mysignal.c

#include<signal.h>

#include<stdio.h>

#include<stdlib.h>

voidmy_func(intsign_no)

{

if(sign_no==SIGINT)

printf("IhavegetSIGINT\n");

elseif(sign_no==SIGQUIT)

printf("IhavegetSIGQUIT\n");

}

intmain()

{

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 高等教育 > 历史学

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2