linux进程通信Word文档下载推荐.docx
《linux进程通信Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《linux进程通信Word文档下载推荐.docx(12页珍藏版)》请在冰豆网上搜索。
创建一个简单的管道,可以使用系统调用pipe()。
它接受一个参数,也就是一个包括两个整数的数组。
如果系统调用成功,此数组将包括管道使用的两个文件描述符。
创建一个管道之后,一般情况下进程将产生一个新的进程。
系统调用:
pipe();
原型:
intpipe(intfd[2]);
返回值:
如果系统调用成功,返回0。
如果系统调用失败返回-1:
errno=EMFILE(没有空亲的文件描述符)
EMFILE(系统文件表已满)
EFAULT(fd数组无效)
注意:
fd[0]用于读取管道,fd[1]用于写入管道。
图见附件
管道的创建
#include<
unistd.h>
errno.h>
stdio.h>
stdlib.h>
intmain()
{
intpipe_fd[2];
if(pipe(pipe_fd)<
0){
printf("
pipecreateerror\n"
);
return-1;
}
else
pipecreatesuccess\n"
close(pipe_fd[0]);
close(pipe_fd[1]);
管道的读写
实际上,通常先创建一个管道,再通过fork函数创建一个子进程。
图见附件。
子进程写入和父进程读的命名管道:
管道读写注意事项:
可以通过打开两个管道来创建一个双向的管道。
但需要在子理程中正确地设置文件描述符。
必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。
当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。
因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。
而在命名管道中却不是这样。
管道实例见:
pipe_rw.c
memory.h>
pid_tpid;
charbuf_r[100];
char*p_wbuf;
intr_num;
memset(buf_r,0,sizeof(buf_r));
数组中的数据清0;
if((pid=fork())==0){
\n"
sleep
(2);
if((r_num=read(pipe_fd[0],buf_r,100))>
%dnumbersreadfrombepipeis%s\n"
r_num,buf_r);
exit(0);
}elseif(pid>
if(write(pipe_fd[1],"
Hello"
5)!
=-1)
parentwritesuccess!
Pipe"
parentwirte2succes!
sleep(3);
waitpid(pid,NULL,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()函数,它将破坏管道和文件系统。
流管道的例子。
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)
%s"
buf);
pclose(fp);
命名管道(FIFO)
基本概念
命名管道和一般的管道基本相同,但也有一些显著的不同:
A、命名管道是在文件系统中作为一个特殊的设备文件而存在的。
B、不同祖先的进程之间可以通过管道共享数据。
C、当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。
管道只能由相关进程使用,它们共同的祖先进程创建了管道。
但是,通过FIFO,不相关的进程也能交换数据。
命名管道创建与操作
命名管道创建
sys/types.h>
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
string.h>
#defineFIFO"
/tmp/myfifo"
main(intargc,char**argv)
intfd;
intnread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<
0)&
&
(errno!
=EEXIST))
cannotcreatefifoserver\n"
Preparingforreadingbytes....\n"
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
open"
exit
(1);
while
(1){
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
nodatayet\n"
read%sfromFIFO\n"
buf_r);
sleep
(1);
pause();
unlink(FIFO);
fifo_read.c
#defineFIFO_SERVER"
charw_buf[100];
intnwrite;
if(errno==ENXIO)
openerror;
noreadingprocess\n"
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(argc==1)
Pleasesendsomething\n"
strcpy(w_buf,argv[1]);
if((nwrite=write(fd,w_buf,100))==-1)
TheFIFOhasnotbeenreadyet.Pleasetrylater\n"
else
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、SIGIOT
7、SIGBUS
8、SIGFPE
9、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()函数运行向进程自身发送信号
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
sys/wait.h>
intret;
if((pid==fork())<
perro("
fork"
if(pid==0){
raise(SIGSTOP);
}else{
pid=%d\n"
pid);
if((waitpid(pid,NULL,WNOHANG))==0){
if((ret=kill(pid,SIGKILL))==0)
kill%d\n"
else{
kill"
alarm和pause函数
使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻时间值会被超过。
当所设置的时间被超过后,产生SIGALRM信号。
如果不忽略或不捕捉引信号,则其默认动作是终止该进程。
unsignedintalarm(unsignedintsecondss);
0或以前设置的闹钟时间的余留秒数。
参数seconds的值是秒数,经过了指定的seconds秒后产生信号SIGALRM。
每个进程只能有一个闹钟时间。
如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。
以前登记的闹钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时间,其余留值仍作为函数的返回值。
pause函数使用调用进程挂起直至捕捉到一个信号
intpause(void);
-1,errno设置为EINTR
只有执行了一信号处理程序并从其返回时,pause才返回。
alarm.c
ret=alarm(5);
Ihavebeenwakenup.\n"
ret);
信号的处理
当系统捕捉到某个信号时,可以忽略谁信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。
信号处理的主要方式有两种,一种是使用简单的signal函数,别一种是使用信号集函数组。
signal()
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
voidmy_func(intsign_no)
if(sign_no==SIGINT)
IhavegetSIGINT\n"
elseif(sign_no==SIGQUIT)
IhavegetSIGQUIT\n"
WaitingforsignalSIGINTorSIGQUTI\n"
signal(SIGINT,my_func);
signal(SIGQUIT,my_func);
pasue();
信号集函数组
我们需要有一个能表示多个信号——信号集(signalset)的数据类型。
将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。
信号集函数组包含水量几大模块:
创建函数集、登记信号集、检测信号集。
创建函数集
intsigempty