linux多进程编程总结

2024-08-16

linux多进程编程总结(通用3篇)

篇1:linux多进程编程总结

一、理解Linux下进程的结构 Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,其实学过汇编语言的人一定知道,一般的CPU象I386,都有上述三种段寄存器,以方便操作系统的运行,“代码段”,顾名思义,就是存放了程序代码的

一、理解Linux下进程的结构

Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,其实学过汇编语言的人一定知道,一般的CPU象I386,都有上述三种段寄存器,以方便操作系统的运行。“代码段”,顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段。

堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

二、如何使用fork

在Linux下产生新的进程的系统调用就是fork函数,这个函数名是英文中“分叉”的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了,所以这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:

void main{

int i;

if ( fork() == 0 ) {

/* 子进程程序 */

for ( i = 1; i <1000; i ++ )

printf(“This is child process ”);

}

else {

/* 父进程程序*/

for ( i = 1; i <1000; i ++ )

printf(“This is process process ”);

}

}

程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。

那么调用这个fork函数时发生了什么呢?一个程序一调用fork函数,系统就为一个新的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。

读者也许会问,如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?其实UNIX自有其解决的办法,大家知道,一般CPU都是以“页”为单位分配空间的,象INTEL的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。

一个小幽默:下面演示一个足以“搞死”Linux的小程序,其源代码非常简单:

void main()

{

for(;;) fork();

}

这个程序什么也不做,就是死循环地fork,其结果是程序不断产生进程,而这些进程又不断产生新的进程,很快,系统的进程就满了,系统就被这么多不断产生的进程“撑死了”,

用不着是root,任何人运行上述程序都足以让系统死掉。哈哈,但这不是Linux不安全的理由,因为只要系统管理员足够聪明,他(或她)就可以预先给每个用户设置可运行的最大进程数,这样,只要不是root,任何能运行的进程数也许不足系统总的能运行和进程数的十分之一,这样,系统管理员就能对付上述恶意的程序了。

三、如何启动另一程序的执行

下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec类的函数,exec类的函数不止一个,但大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp为例,其它函数究竟与execlp有何区别,请通过manexec命令来了解它们的具体情况。

一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。)

那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:

char command[256];

void main()

{

int rtn; /*子进程的返回数值*/

while(1) {

/* 从终端读取要执行的命令 */

printf( “>” );

fgets( command, 256, stdin );

command[strlen(command)-1] = 0;

if ( fork() == 0 ) {

/* 子进程执行此命令 */ execlp( command, command );

/* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/

perror( command );

exit( errorno );

}

else {

/* 父进程, 等待子进程结束,并打印子进程的返回值 */

wait ( &rtn );

printf( “ child process return %d ”,. rtn );

}

}

}

共2页: 1 [2] 下一页

原文转自:www.ltesting.net

篇2:linux多进程编程总结

(一)作者:不详

阅读人次:2138

文章来源:vczx.com

发布时间:2007-8-29

友评论(0)条

一.多进程程序的特点

由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在实质上 应该

说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境以使得在CPU 做进程

切换时不会“忘记”该进程已计算了一半的“半成品”.以DOS的概念来说, 进程的切 换都

是一次“DOS中断”处理过程, 包括三个层次:

(1)用户数据的保存: 包括正文段(TEXT), 数据段(DATA,BSS), 栈段(STACK), 共享

内存段(SHARED MEMORY)的保存.(2)寄存器数据的保存: 包括PC(program counter,指向下一条要执行的 指

令的地址), PSW(processor status word,处理机状态字), SP(stack pointer,栈 指

针), PCBP(pointer of process control block,进程控制块指针), FP(frame pointer,指向栈中一个函数的local变量的首地址), AP(augument pointer,指向 栈中函

数调用的实参位置), ISP(interrupt stack pointer,中断栈指针), 以及其他的 通用寄 存器等.(3)系统层次的保存: 包括proc,u,虚拟存储空间管理表格,中断处理栈.以便于

该进程再一次得到CPU时间片时能正常运行下去.既然系统已经处理好所有这些中 断处理 的过程, 我们做程序还有什么要担心的呢? 我们尽可以使用系统提供的多进程的 特点, 让几个程序精诚合作, 简单而又高效地把结果给它搞出来.另外,UNIX系统本身也是用C语言写的多进程程序,多进程编程是UNIX的特点, 当我们

熟悉了多进程编程后,将会对UNIX系统机制有一个较深的认识.首先我介绍一下多 进程程

序的一些突出的特点:

1.并行化

一件复杂的事件是可以分解成若干个简单事件来解决的, 这在程序员

的大脑中早就形成了这种概念, 首先将问题分解成一个个小问题, 将小问

题再细分, 最后在一个合适的规模上做成一个函数.在软件工程中也是这

么说的.如果我们以图的方式来思考, 一些小问题的计算是可以互不干扰

的, 可以同时处理, 而在关键点则需要统一在一个地方来处理, 这样程序

的运行就是并行的, 至少从人的时间观念上来说是这样的.而每个小问题

的计算又是较简单的.2.简单有序

这样的程序对程序员来说不亚于管理一班人, 程序员为每个进程设计

好相应的功能, 并通过一定的通讯机制将它们有机地结合在一起, 对每个

进程的设计是简单的, 只在总控部分小心应付(其实也是蛮简单的), 就可

完成整个程序的施工.3.互不干扰

这个特点是操作系统的特点, 各个进程是独立的, 不会串位.4.事务化

比如在一个数据电话查询系统中, 将程序设计成一个进程只处理一次

查询即可, 即完成一个事务.当电话查询开始时, 产生这样一个进程对付

这次查询;另一个电话进来时, 主控程序又产生一个这样的进程对付, 每 个进程完成查询任务后消失.这样的编程多简单, 只要做一次查询的程序

就可以了.二.常用的多进程编程的系统调用

1.fork()

功能:创建一个新的进程.语法:#include

#include

pid_t fork();

说明:本系统调用产生一个新的进程, 叫子进程, 是调用进程的一个复

制品.调用进程叫父进程, 子进程继承了父进程的几乎所有的属

性:

.实际UID,GID和有效UID,GID..环境变量..附加GID..调用exec()时的关闭标志..UID设置模式比特位..GID设置模式比特位..进程组号..会话ID..控制终端..当前工作目录..根目录..文件创建掩码UMASK..文件长度限制ULIMIT..预定值, 如优先级和任何其他的进程预定参数, 根据种类不同

决定是否可以继承..还有一些其它属性.但子进程也有与父进程不同的属性:

.进程号, 子进程号不同与任何一个活动的进程组号..父进程号..子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝

并且与父进程和其它子进程共享该资源..子进程的用户时间和系统时间被初始化为0..子进程的超时时钟设置为0..子进程的信号处理函数指针组置为空..子进程不继承父进程的记录锁.返回值: 调用成功则对子进程返回0, 对父进程返回子进程号, 这也是

最方便的区分父子进程的方法.若调用失败则返回-1给父进程,子进程不生成.例子:pid_t pid;

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

/*父进程处理过程*/

}

else if(pid==0){

/*子进程处理过程*/

exit(0);

/*注意子进程必须用exit()退出运行*/

}

else {

printf(“fork errorn”);

exit(0);

}

2.system()

功能:产生一个新的进程, 子进程执行指定的命令.语法:#include

#include

int system(string)

char *string;

说明:本调用将参数string传递给一个命令解释器(一般为sh)执行, 即

string被解释为一条命令, 由sh执行该命令.若参数string为一

个空指针则为检查命令解释器是否存在.该命令可以同命令行命令相同形式, 但由于命令做为一个参数放

在系统调用中, 应注意编译时对特殊意义字符的处理.命令的查

找是按PATH环境变量的定义的.命令所生成的后果一般不会对父

进程造成影响.返回值:当参数为空指针时, 只有当命令解释器有效时返回值为非零.若参数不为空指针, 返回值为该命令的返回状态(同waitpid())

的返回值.命令无效或语法错误则返回非零值,所执行的命令被

终止.其他情况则返回-1.例子:char command[81];

int i;

for(i=1;i<8;i++){ sprintf(command,“ps-t tty%02i”,i);system(command);} 3.exec()功能:执行一个文件 语法:#include

int execl(path,arg0,...,argn,(char*)0)

char *path,*arg0,...,*argn;

int execv(path,argv)

char *path,*argv[];

int execle(path,arg0,...,argn,(char*)0,envp)

char *path,*arg0,...,*argn,*envp[];

int execve(path,argv,envp)

char *path,*argv[],*envp[];

int execvp(file,argv)

char *file,*argv[];

说明:这是一个系统调用族, 用于将一个新的程序调入本进程所占的内

存, 并覆盖之, 产生新的内存进程映象.新的程序可以是可执行

文件或SHELL批命令.当C程序被执行时,是如下调用的:

main(int argc,char *argv[],char *envp[]);

argc是参数个数,是各个参数字符串指针数组,envp是新进程的环

境变量字符串的指针数组.argc至少为1,argv[0]为程序文件名,所以,在上面的exec系统调用族中,path为新进程文件的路径名,file为新进程文件名,若file不是全路径名,系统调用会按PATH环

境变量自动找对应的可执行文件运行.若新进程文件不是一个可

执行的目标文件(如批处理文件),则execlp()和execvp()会将该

文件内容作为一个命令解释器的标准输入形成system().arg0,...等指针指向'结束的字符串,组成新进程的有效参数,且该参数列表以一个空指针结束.反过来,arg0至少必须存在并指

向新进程文件名或路径名.同样,argv是字符串指针数组,argv[0]指向新进程文件名或路径

名,并以一空指针结束.envp是一个字符串指针数组,以空指针结束,这些字符串组成新进

程的环境.在调用这些系统调用前打开的文件指针对新进程来说也是打开的,除非它已定义了close-on-exec标志.打开的文件指针在新进程中

保持不变,所有相关的文件锁也被保留.调用进程设置并正被捕俘的信号在新进程中被恢复为缺省设置,其它的则保持不变.新进程启动时按文件的SUID和SGID设置定义文件的UID和GID为有

效UID和GID.新进程还继承了如下属性:

.附加GID..进程号..父进程号..进程组号..会话号..控制终端..alarm时钟信号剩下的时间..当前工作目录..根目录..文件创建掩码..资源限制..用户时间,系统时间,子进程用户时间,子进程系统时间..记录锁..进程信号掩码..信号屏蔽..优先级..预定值.调用成功后,系统调用修改新进程文件的最新访问时间.返回值:该系统调用一般不会有成功返回值, 因为原来的进程已荡然无

存.例子:printf(“now this process will be ps commandn”);

execl(“/bin/ps”,“ps”,“-ef”,NULL);

4.popen()

功能:初始化从/到一个进程的管道.语法:#include

FILE *popen(command,type)

char *command,type;说明:本系统调用在调用进程和被执行命令间创建一个管道.参数command做为被执行的命令行.type做为I/O模式,“r”为从被

执行命令读,“w”为向被执行命令写.返回一个标准流指针,做为管

道描述符,向被执行命令读或写数据(做为被执行命令的STDIN或

STDOUT)该系统调用可以用来在程序中调用系统命令,并取得命令

的输出信息或者向命令输入信息.返回值:不成功则返回NULL,成功则返回管道的文件指针.5.pclose()

功能:关闭到一个进程的管道.语法:#include

int pclose(strm)

FILE *strm;

说明:本系统调用用于关闭由popen()打开的管道,并会等待由popen()

激活的命令执行结束后,关闭管道后读取命令返回码.返回值:若关闭的文件描述符不是由popen()打开的,则返回-1.例子:printf(“now this process will call popen system calln”);

FILE * fd;

if((fd=popen(“ps-ef”,“r”))==NULL){

printf(“call popen failedn”);

return;

}

else {

char str[80];

while(fgets(str,80,fd)!=NULL)

printf(“%sn”,str);

}

pclose(fd);

6.wait()

功能:等待一个子进程返回并修改状态

语法:#include

#include

pid_t wait(stat_loc)

int *stat_loc;

说明:允许调用进程取得子进程的状态信息.调用进程将会挂起直到其

一个子进程终止.返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为

-1.同时stat_loc返回子进程的返回值.例子:/*父进程*/

if(fork()>0){

wait((int *)0);

/*父进程等待子进程的返回*/

}

else {

/*子进程处理过程*/

exit(0);

}

7.waitpid()

功能:等待指定进程号的子进程的返回并修改状态

语法:#include

#include

pid_t waitpid(pid,stat_loc,options)

pid_t pid;

int *stat_loc,options;

说明:当pid等于-1,options等于0时,该系统调用等同于wait().否则该

系统调用的行为由参数pid和options决定.pid指定了一组父进程要求知道其状态的子进程:

-1:要求知道任何一个子进程的返回状态.>0:要求知道进程号为pid值的子进程的状态.<-1:要求知道进程组号为pid的绝对值的子进程的状态.options参数为以比特方式表示的标志以或运算组成的位图,每个

标志以字节中某个比特置1表示: WUNTRACED:报告任何未知而又已停止运行的指定进程号的子进

程的状态.该子进程的状态自停止运行时起就没有被报告 过.WCONTINUED:报告任何继续运行的指定进程号的子进程的状态,该子进程的状态自继续运行起就没有被报告过.WHOHANG:若调用本系统调用时,指定进程号的子进程的状态目 前并不是立即有效的(即可被立即读取的),调用进程并被 暂停执行.WNOWAIT:保持将其状态设置在stat_loc的进程在可等待状态.该进程将等待直到下次被要求其返回状态值.返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为

-1.同时stat_loc返回子进程的返回值.例子:pid_t pid;int stat_loc;/*父进程*/ if((pid=fork())>0){

waitpid(pid,&stat_loc,0);

/*父进程等待进程号为pid的子进程的返回*/

}

else {

/*子进程的处理过程*/

exit(1);

}

/*父进程*/

printf(“stat_loc is [%d]n”,stat_loc);

/*字符串“stat_loc is [1]”将被打印出来*/

8.setpgrp()

功能:设置进程组号和会话号.语法:#include

pid_t setpgrp()

说明:若调用进程不是会话首进程.将进程组号和会话号都设置为与它的进程号相等.并释放调用进程的控制终端.返回值:调用成功后,返回新的进程组号.例子:/*父进程处理*/

if(fork()>0){

/*父进程处理*/

}

else {

setpgrp();

/*子进程的进程组号已修改成与它的进程号相同*/

exit(0);

}

9.exit()

功能:终止进程.语法:#include

void exit(status)

int status;

说明:调用进程被该系统调用终止.引起附加的处理在进程被终止前全

部结束.返回值:无

10.signal()

功能:信号管理功能

语法:#include

void(*signal(sig,disp))(int)

int sig;

void(*disp)(int);

void(*sigset(sig,disp))(int)

int sig;

void(*disp)(int);

int sighold(sig)

int sig;

int sigrelse(sig)

int sig;

(除了

捉到).SIGILL,SIGTRAP

SIG_DF L,将该

句柄

外,程的

int sigignore(sig)

int sig;

int sigpause(sig)

int sig;说明:这些系统调用提供了应用程序对指定信号的简单的信号处理.signal()和sigset()用于修改信号定位.参数sig指定信号

SIGKILL和SIGSTOP,这两种信号由系统处理,用户程序不能捕

disp指定新的信号定位,即新的信号处理函数指针.可以为

SIG_IGN,SIG_DFL或信号句柄地址.若使用signal(),disp是信号句柄地址,sig不能为

或SIGPWR,收到该信号时,系统首先将重置sig的信号句柄为

然后执行信号句柄.若使用sigset(),disp是信号句柄地址,该信号时,系统首先

信号加入调用进程的信号掩码中,然后执行信号句柄.当信号

运行结束

后,系统将恢复调用进程的信号掩码为信号收到前的状态.另

使用sigset()时,disp为SIG_HOLD,则该信号将会加入调用进

信号掩码中而信号的定位不变.sighold()将信号加入调用进程的信号掩码中.sigrelse()将信号从调用进程的信号掩码中删除.sigignore()将信号的定位设置为SIG_IGN.sigpause()将信号从调用进程的信号掩码中删除,同时挂起调用

进程直到收到信号.若信号SIGCHLD的信号定位为SIG_IGN,则调用进程的子进程在终

止时不会变成僵死进程.调用进程也不用等待子进程返回并做相

应处理.返回值:调用成功则signal()返回最近调用signal()设置的disp的值.否则返回SIG_ERR.例子一:设置用户自己的信号中断处理函数,以SIGINT信号为例:

int flag=0;

void myself()

{

flag=1;

printf(“get signal SIGINTn”);

/*若要重新设置SIGINT信号中断处理函数为本函数则执行以

*下步骤*/

void(*a)();

a=myself;

signal(SIGINT,a);

flag=2;

}

main()

{

while(1){

sleep(2000);/*等待中断信号*/

if(flag==1){

printf(“skip system call sleepn”);

exit(0);

}

if(flag==2){

printf(“skip system call sleepn”);

printf(“waiting for next signaln”);

}

}

}

11.kill()

功能:向一个或一组进程发送一个信号.语法:#include

#include

int kill(pid,sig);

pid_t pid;

int sig;

说明:本系统调用向一个或一组进程发送一个信号,该信号由参数sig指

定,为系统给出的信号表中的一个.若为0(空信号)则检查错误但

实际上并没有发送信号,用于检查pid的有效性.pid指定将要被发送信号的进程或进程组.pid若大于0,则信号将

被发送到进程号等于pid的进程;若pid等于0则信号将被发送到所

有的与发送信号进程同在一个进程组的进程(系统的特殊进程除

外);若pid小于-1,则信号将被发送到所有进程组号与pid绝对值

相同的进程;若pid等于-1,则信号将被发送到所有的进程(特殊系

统进程除外).信号要发送到指定的进程,首先调用进程必须有对该进程发送信

号的权限.若调用进程有合适的优先级则具备有权限.若调用进程

的实际或有效的UID等于接收信号的进程的实际UID或用setuid()

系统调用设置的UID,或sig等于SIGCONT同时收发双方进程的会话

号相同,则调用进程也有发送信号的权限.若进程有发送信号到pid指定的任何一个进程的权限则调用成功,否则调用失败,没有信号发出.返回值:调用成功则返回0,否则返回-1.例子:假设前一个例子进程号为324,现向它发一个SIGINT信号,让它做

信号处理:

kill((pid_t)324,SIGINT);

12.alarm()

一次

功能:设置一个进程的超时时钟.语法:#include

unsigned int alarm(sec)

unsigned int sec;说明:指示调用进程的超时时钟在指定的时间后向调用进程发送一个

SIGALRM信号.设置超时时钟时时间值不会被放入堆栈中,后

设置会把前一次(还未到超时时间)冲掉.若sec为0,则取消任何以前设置的超时时钟.exec()

数则执行

信号*/

leepn“);

leepn”);

fork()会将新进程的超时时钟初始化为0.而当一个进程用

族系统调用新的执行文件时,调用前设置的超时时钟在调用后

有效.返回值:返回上次设置超时时钟后到调用时还剩余的时间秒数.例子:int flag=0;

void myself()

{

flag=1;

printf(“get signal SIGALRMn”);

/*若要重新设置SIGALRM信号中断处理函数为本函

*以下步骤*/

void(*a)();

a=myself;

signal(SIGALRM,a);

flag=2;

}

main()

{

alarm(100);

/*100秒后发超时中断

while(1){

sleep(2000);/*等待中断信号*/

if(flag==1){

printf(“skip system call s

exit(0);

}

if(flag==2){

printf(”skip system call s

printf(“waiting for next s

ignaln”);

}

}

}

13.msgsnd()

功能:发送消息到指定的消息队列中.语法:#include

#include

#include

域应

正文

到系

调用

到下

进程继续

int msgsnd(msqid,msgp,msgsz,msgflg)

int msqid;

void *msgp;

size_t msgsz;

int msgflg;说明:发送一个消息到由msqid指定消息队列标识号的消息队列.参数msgp指向一个用户定义的缓冲区,并且缓冲区的第一个

为长整型,指定消息类型,其他数据放在缓冲区的消息中其他

区内.下面是消息元素定义:

long mtype;

char mtext[];

mtype是一个整数,用于接收进程选择消息类型.mtext是一个长度为msgsz字节的任何正文,参数msgsz可从0

统允许的最大值间变化.msgflg指定操作行为:

.若(msgflg&IPC_NOWAIT)是真的,消息并不是被立即发送而

进程会立即返回..若(msgflg&IPC_NOWAIT)不是真的,则调用进程会被挂起直

面情况之一发生:

* 消息被发送出去.* 消息队列标志被系统删除.系统调用返回-1.* 调用进程接收到一个未被忽略的中断信号,调用

执行或被终止.调用成功后,对应指定的消息队列的相关结构做如下动作:

篇3:linux多进程编程总结

进程间通信(IPC)是指在不同进程之间传播或交换信息。进程间通信的方式主要有以下6钟:(1)管道(Pipe)(2)信号(Singal)(3)信号量(Semaphore)(4)共享内存(shared memory)(5)消息队列(Message Queue)(6)套接字(Socket)下面对6种方式进行详细介绍:

 管道

1、管道分为有名管道和无名管道,通常指无名管道。

2、无名管道特点:

(1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。(2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

(3)对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

3、缺点:速度慢,容量有限,只有父子进程能通讯。

4、管道的创建及使用:

调用pipe()函数创建一个管道,再通过fork()函数创建一个子进程,该子进程继承父进程所创建的管道,为实现父子进程间的读写,只需把无关的读端或写端的文件描述符关闭即可。若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

5、父子进程在运行时,先后顺序并不能保证。可以在进程中添加sleep()函数。

6、标流管道:管道的操作也支持基于文件流的模式,这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另一个进程”也就是一个可以进行一定操作的可执行文件。

7、因此标准流管道就将一系列的创建过程合并到一个函数popen()中完成。它所完成的工作有以下几步:

(1)创建一个管道(2)fork()一个子进程

(3)在父进程中关闭不必要的文件描述符(4)执行exec函数族调用(5)执行函数中所指定的命令

8、popen()函数的使用减少了代码的编写量,但是灵活性不如pipe()函数所创建的管道,并且使用popen()创建的管道必须使用标准I/O函数进行操作,但不能使用read()、write()一类不带缓冲的I/O函数。open()创建的流管道必须使用函数pclose()来关闭该管道流。

9、有名管道特点:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。它是一个设备文件,提供一个路径名与FIFO关联。

10、有名管道缺点:任何进程间都能通讯,但速度慢。

11、使用有名管道需先调用open函数将其打开,设置读写端的权限,若只是以只读或只写方式打开,会阻塞到有读或写方式打开管道为止。若同时以读写方式打开,一定不阻塞。

12、有名管道的阻塞打开和非阻塞打开:(1)读进程

① 若管道阻塞打开,且当前FIFO内没有数据,则读进程一直阻塞到有数据写入。

② 若管道非阻塞打开,不论FIFO内是否有数据,读进程都立刻执行读操作。即FIFO内没有数据,该函数就立刻返回0。(2)写进程

① 若管道阻塞打开,写操作一直阻塞到有数据可以被写入。

② 若管道是非阻塞打开且不能写入全部数据,读操作进行部分写入或者调用失败。

13、除了调用myfifo()函数之外,有名管道还可以通过 “mknod 管道名 p”的方式创建。

 信号

1、信号是软件层次上对中断机制的模拟,是一种异步通信方式。

2、信号值在32之前有不同的名称,在32后都是以“SIGRTMIN”和“SIGRTMAX”开头的,这是两类典型的信号量。

3、信号主要作为进程间以及同一进程不同线程之间的同步手段。

4、一个完整的信号生命周期可以分为3个阶段,这个3个阶段由4个重要事件来表示:信号的产生、信号在进程中注册、信号在进程中注销、执行信号处理函数。

5、发送信号的函数主要有kill()、raise()、alarm()以及pause()。kill()函数:中止进程,向进程发送其他信号。raise()函数:允许进程向自身发送信号。

alarm()函数:在进程中设置一个定时器,当定时器指定时间到时,就会向进程发送SIGALARM信号。pause()函数:将调用进程挂起直至捕捉到信号为止,通常用于判断信号是否移到。

6、信号的处理方法:(1)Singal():

可以用函数signal注册一个信号捕捉函数。原型为: #include

typedef void(*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);

signal 的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回 SIG_ERR。

sighandler_t是信号捕捉函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个参数,表示信号值。(2)Sigaction(): 函数原型:

#include int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);sigaction也用于注册一个信号处理函数。参数signum为需要捕捉的信号;参数 act是一个结构体,里面包含信号处理函数地址、处理方式等信息。

参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息。

如果函数调用成功,将返回0,否则返回-1。

 信号量

1、信号量是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

2、信号量的值是指当前可用资源的数量,通常是对信号量进行PV操作。

P操作:有资源时候(信号量值 > 0)则占用一个资源(信号量减1);若没有资源(信号量 = 0),则被阻塞直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。

V操作:若等待队列中有进程在等待资源,则唤醒一个阻塞进程。如果没有进程等待它,则释放一个资源(给信号量值加1)。

3、缺点:不能传递复杂消息,只能用来同步。

4、信号量的应用步骤:

(1)创建信号量或获得在系统中已存在的信号量--semget()(2)初始化信号量--semctl()的SETVAL操作,信号量初始化为1(3)对信号量进行PV操作--semop()

(4)删除无用的信号量--semclt()的IPC_RMID操作

5、在实例中,通常是先对父进程执行操作,但由于信号量处置为0父进程阻塞,转至执行子进程,在子进程执行结束后方可执行父进程操作。如果不加信号量,则父进程会先执行完毕。

 共享内存

1、共享内存是指两个或多个进程共享一个给定的存储区。是进程间通信最快的方式。

2、特点:

(1)共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。(2)因为多个进程可以同时操作,所以需要进行同步。

(3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

3、使用流程

(1)创建共享内存 shmget()(2)映射共享内存 shmat()(3)撤销映射 shmdt()

4、使用实例:

首先创建一个共享内存区,之后创建子进程,在父子进程中将共享内存分别映射到各自的进程地址空间中。父进程等待用户输入,将键入的数据写入到共享内存中,之后往共享内存的头部写入标识字符串表示父进程成功写入数据。子进程一直等到共享内存的头部字符串为标识字符,然后将其打印在屏幕上。父子进程完成以上操作后,分别解除与共享内存的映射关系。最后在子进程中删除共享内存。

 消息队列

1、消息队列就是消息的列表,用户可以从中添加消息和读取消息。消息存在于内核中,由队列ID来标识。它在系统内核中是以消息链表的形式出现,消息链表中节点的结构用msg声明。

2、消息队列的实现包括打开消息队列、添加消息、读取消息和控制消息队列这4种操作。(1)创建或打开消息队列

msgget()(2)添加消息

msgsnd()(3)读取消息

msgrcv()----同FIFO不同的是,这里可以指定取走某一条消息(4)控制消息队列

msgctl()

3、消息的发送端和接收端不需要额外的实现进程间的同步。发送端发送的消息类型设置为该进程的进程号,接收端根据消息类型确定消息发送者的类型号。

4、优点:消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5、缺点:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

 套接字

1、socket是一种文件描述符,不仅可实现本机上进程的通信,还可以实现不同机器间的进程通信。

2、套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。

3、套接字域指定套接字通信中使用的网络介质。最常见的套接字域是 AF_INET(IPv4)或者AF_INET6(IPV6),它是指 Internet 网络。

4、套接字类型:流套接字、数据报套接字、原始套接字。

5、套接字协议有TCP协议和UDP协议。TCP协议可靠性高可减少错误发生的概率,UDP协议灵活性高可减少网络负荷。

6、通信机制:

首先服务端建立socket套接字可使得其可接受客户端socket套接字请求,然后调用bind绑定函数,将本机IP地址和本地监听与客户端相连的端口号绑在一起,形成半相关的套接字。再运行客户端,也使得本机IP地址和端口号绑定,可向服务端发送请求。然后即可监听客户端向服务端发送的连接请求,若有请求accept,无请求则一直监听。此时服务端和客户端可进行双向通信。通信结束后关闭通道。

7、网络高级编程:

(1)作用:解决I/O多路复用(2)fcntl():

fcntl可实现对指定文件描述符的各种操作,其函数原型如下:int fcntl(int fd, int cmd,.../* arg */);

操作类型由cmd决定。cmd可取如下值: F_DUPFD:复制文件描述符

F_DUPFD_CLOEXEC:复制文件描述符,新文件描述符被设置了close-on-exec F_GETFD:读取文件描述标识 F_SETFD:设置文件描述标识 F_GETFL:读取文件状态标识 F_SETFL:设置文件状态标识

F_GETLK:如果已经被加锁,返回该锁的数据结构。如果没有被加锁,将l_type设置为F_UNLCK F_SETLK:给文件加上进程锁

F_SETLKW:给文件加上进程锁,如果此文件之前已经被加了锁,则一直等待锁被释放。(3)select():

上一篇:描写植物芦荟的作文下一篇:百姓裤子的广告词创意文案