多进程编程的相关知识总结(三)
|
23.semop() 功能:信号量操作. 语法:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(semid,sops,nsops) int semid; struct sembuf *sops; unsigned nsops; 说明:本系统调用用于执行用户定义的在一组信号量上操作的行为集合.
该组信号量与semid相关. 参数sops为一个用户定义的信号量操作结构数组指针. 参数nsops为该数组的元素个数. 数组的每个元素结构包括如下成员: sem_num; /* 信号量数 */ sem_op; /* 信号量操作 */ sem_flg; /* 操作标志 */ 由本系统调用定义的每个信号量操作是针对由semid和sem_num指
定的信号量的.变量sem_op指定三种信号量操作的一种: . 若sem_op为一负数并且调用进程具有修改权限,则下列情况之 一将会发生: * 若semval不小于sem_op的绝对值,则sem_op的绝对值被减去 semval的值.若(semflg&SEM_UNDO)为真则sem_op的绝对值加
上调用进程指定的信号量的semadj值. * 若semval小于sem_op的绝对值同时(semflg&IPC_NOWAIT)为 真,则本调用立即返回. * 若semval小于sem_op的绝对值同时(semflg&IPC_NOWAIT)为 假,则本系统调用将增加指定信号量相关的semncnt值(加一),
将调用进程挂起直到下列条件之一被满足: (1).semval值变成不小于sem_op的绝对值.当这种情况发 生时,指定的信号量相关的semncnt减一,若 (semflg&SEM_UNDO)为真则sem_op的绝对值加上调用 进程指定信号量的semadj值. (2).调用进程等待的semid已被系统删除. (3).调用进程捕俘到信号,此时,指定信号量的semncnt值 减一,调用进程执行中断服务程序. . 若sem_op为一正值,同时调用进程具有修改权限,sem_op的值加
上semval的值,若(semflg&SEM_UNDO)为真,则sem_op减去调用 进程指定信号量的semadj值. . 若sem_op为0,同时调用进程具有读权限,下列情况之一将会发 生: * 若semval为0,本系统调用立即返回. * 若semval不等于0且(semflg&IPC_NOWAIT)为真,本系统调用 立即返回. * 若semval不等于0且(semflg&IPC_NOWAIT)为假,本系统调用 将把指定信号量的 semzcnt值加一,将调用进程挂起直到下列情况之一发生: (1).semval值变为0时,指定信号量的semzcnt值减一. (2).调用进程等待的semid已被系统删除. (3).调用进程捕俘到信号,此时,指定信号量的semncnt值 减一,调用进程执行中断服务程序. 返回值:调用成功则返回0,否则返回-1. 例子:本例将包括上述信号量操作的所有系统调用: #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define SEMKEY 75 int semid; unsigned int count; /*在文件sys/sem.h中定义的sembuf结构 * struct sembuf { * unsigned short sem_num; * short sem_op; * short sem_flg; * }*/ struct sembuf psembuf,vsembuf; /*P和V操作*/ cleanup() { semctl(semid,2,IPC_RMID,0); exit(0); } main(argc,argv) int argc; char *argv[]; { int i,first,second; short initarray[2],outarray[2]; extern cleanup(); if (argc==1) { for (i=0;i<20;i++) signal(i,clearup); semid=semget(SEMKEY,2,0777|IPC_CREAT); initarray[0]=initarray[1]=1; semctl(semid,2,SETALL,initarray); semctl(semid,2,GETALL,outarray); printf("sem init vals %d%d \n", outarray[0],outarray[1]); pause(); /*睡眠到被一软件中断信号唤醒*/ } else if (argv[1][0]=='a') { first=0; second=1; } else { first=1; second=0; } semid=semget(SEMKEY,2,0777); psembuf.sem_op=-1; psembuf.sem_flg=SEM_UNDO; vsembuf.sem_op=1; vsembuf.sem_flg=SEM_UNDO; for (count=0;;xcount++) { psembuf.sem_num=first; semop(semid,&psembuf,1); psembuf.sem_num=second; semop(semid,&psembuf,1); printf("proc %d count %d\n",getpid(),count); vsembuf.sem_num=second; semop(semid,&vsembuf,1); vsembuf.sem_num=first; semop(semid,&vsembuf,1); } } 24.sdenter() 功能:共享数据段同步访问,加锁. 语法:#include <sys/sd.h> int sdenter(addr,flags) char *addr; int flags; 说明:用于指示调用进程即将可以访问共享数据段中的内容. 参数addr为将一个sdget()调用的有效返回码. 所执行的动作取决于flags的值: . SD_NOWAIT:若另一个进程已对指定的段调用本系统调用且还没
有调用sdleave(),并且该段并非用SD_UNLOCK标志创建,则调
用进程不是等待该段空闲而是立即返回错误码. . SD_WRITE:指示调用进程希望向共享数据段写数据.此时,另一 个进程用SD_RDONLY标志联接该共享数据段则不被允许. 返回值:调用成功则返回0,否则返回-1. 25.sdleave() 功能:共享数据段同步访问,解锁. 语法:#include <sys/sd.h> int sdleave(addr,flags) char *addr; 说明:用于指示调用进程已完成修改共享数据段中的内容. 返回值:调用成功则返回0,否则返回-1. 26.sdget() 功能:联接共享数据段到调用进程的数据空间中. 语法:#include <sys/sd.h> char *sdget(path,flags,size.mode) char *path; int flags; long size; int mode; 说明:本系统调用将共享数据段联接到调用进程的数据段中,具体动作 由flags的值定义: . SD_RDONLY:联接的段为只读的. . SD_WRITE:联接的段为可读写的. . SD_CREAT:若由path命名的段存在且不在使用中,本标志的作用
同早先创建一个段相同,否则,该段根据size和mode的值进程
创建.对段的读写访问权限的授予基于mode给的权限,功能与
一般文件的相同.段被初始化为全0. . SD_UNLOCK:若用此标志创建该段,则允许有多个进程同时访问 (在读写中)该段. 返回值:若调用成功则返回联接的段地址.否则返回-1. 27.sdfree() 功能:将共享数据段从调用进程的数据空间中断开联接. 语法:#include <sys/sd.h> int sdfree(addr) char *addr; 说明:本系统调用将共享数据段从调用进程的数据段的指定地址中分离.
若调用进程已完成sdenter()的调用,还未调用sdleave()就调用 本系统调用,则sdleave()被自动调用,然后才做本调用的工作. 返回值:若调用成功则返回联接的段地址.否则返回-1. 28.sdgetv() 功能:同步共享数据访问. 语法:#include <sys/sd.h> int sdgetv(addr) char *addr; 说明:用于同步协调正在使用共享数据段的进程.返回值为共享数据段 的版本号.当有进程对该段做sdleave()操作时,版本号会被修改.
返回值:若调用成功,则返回指定共享数据段的版本号,否则返回-1. 29.sdwaitv() 功能:同步共享数据访问. 语法:#include <sys/sd.h> int sdwaitv(addr,vnum) char *addr; int vnum; 说明:用于同步协调正在使用共享数据段的进程.返回值为共享数据段 的版本号.调用进程会睡眠直到指定段的版本号不再等于vnum; 返回值:若调用成功,则返回指定共享数据段的版本号,否则返回-1. 30.sbrk() 功能:修改数据段空间分配. 语法:char *sbrk(incr) int incr; 说明:用于动态修改调用进程数据段的空间分配.进程将重置进程的分 段值并分配一个合适大小的空间.分段值为数据段外第一次分配 的地址.要分配的空间的增加量等于分段值的增加量.新分配的空
间设置为0.若相同的内存空间重新分配给同一个进程,则空间的 内容不确定. 返回值:若成功调用则返回值为0,否则返回-1. 例子:本例将包括上述共享数据空间操作的所有系统调用: char * area1; char buf[21]; int v; /*取得或创建一个共享数据空间(系统特殊文件),名字为 /tmp/area1,长度为640,用户访问权限为0777*/ area1=sdget("/tmp/area1",SD_WRITE|SD_CREAT,640,0777); if ((int)area1==-1) { printf("get share data segment area1 failed\n"); exit(1); } /*取得共享数据段area1的版本号*/ v=sdgetv(area1); /*申请访问共享数据段area1,若已有进程在访问该段则本进程挂
*起,否则进入访问并将该数据段加写锁*/ sdenter(area1,SD_WRITE); /*对共享数据段访问,写10个a*/ strcpy(area1,"aaaaaaaaaa"); /*申请解除访问权限,若已有进程申请访问则激活该进程*/ sdleave(area1); /*进程处理过程*/ /*等待取共享数据段area1的版本号*/ sdwaitv(area1,v); /*重新申请访问共享数据段area1*/ sdenter(area1,SD_WRITE); /*读取共享数据段中的数据*/ memcpy(buf,area1,20); /*申请解除访问权限,若已有进程申请访问则激活该进程*/ sdleave(area1); printf("the data now in area1 is [%s]\n",buf); 31.getenv() 功能:取得指定环境变量值. 语法:#include <unistd.h> #include <stdlib.h. char *getenv(name) char *name; 说明:本系统调用检查环境字符串(格式如name=value),并在找到有指 定名字的环境值后,返回指向value字符串的指针.否则返回空指 针. 返回值:如前述. 例子:char * value; value=getenv("HOME"); printf("HOME = [%s]\n",value); /*将打印出HOME环境变量的值*/ 32.putenv() 功能:修改或增加环境值. 语法:#include <stdlib.h> int putenv(string) char *string; 说明:参数string指向一个字符串,格式如下: name=value 本系统调用将环境变量name等于值value,修改或增加一个环境变
量,字符串string成为环境的一部分. 返回值:若putenv()不能取得合适的内存空间则返回非0值,否则返回0.
例子:/*父进程处理*/ putenv("HOME=/home/abcdef"); putenv("PATH=/bin"); if (fork()>0) exit(0); /*父进程退出运行*/ /*子进程处理*/ setpgrp(); /*父进程设置的环境变量已传到子进程*/ char * value1; value1=getenv("HOME"); value2=getenv("PATH"); printf("HOME=[%s],PATH=[%s]\n",value1,value2); /*将打印出"HOME=/home/abcdef"和"PATH=/bin"*/
三.多进程编程技巧 1.主要程序结构 (1)事件主控方式 若是应用程序属于事务处理方式,则在主函数中设计为监控事件发生,
当事件发生时,可以生成一个新的进程来处理该事务,事务处理完成后就 可以让子进程退出系统.这种处理方式一般不要消息传递. (2)信息协调方式 若是应用程序需要由多个进程协调处理完成,则可以生成这些进程, 通过消息在进程间的传递,使各个进程能相互协调,共同完成事务.这种处
理方式一般是用fork()生成几个进程后,用exec()调用其它程序文件,使 得不同的程序同时在系统内运行.然后通过IPC机制传送消息,使各个程序
能协调运行. 2.选择主体分叉点 (1)事件初始产生 对应于事件主控方式的程序结构.关键点在于以何种方式选择事件的 初始发生点,如网络程序给出的建链信息.主控程序在收到该消息后就认 为是一个事件开始,则可以产生一个子进程处理后面的事务:接收交易信 息,事务处理,发送返回交易信息,关闭链接等,完成后将子进程退出系统.
(2)主程序自主产生 对应于信息协调方式的程序结构.主控程序只负责生成几个子进程,各
个子进程分别调用exec()将不同的执行文件调入内存运行,主控程序在生
成所有的子进程后即可退出系统,将子进程留在内存中运行. 3.进程间关系处理 (1)父子进程关系 . 进程组处理 进程组的概念是这样的,当系统启动时,第一个进程是init,其进程 组号等于进程号,由它产生的所有子进程的进程组号也相同,子进程
的子进程也继承该进程组号,这样,由init所生成的所有子进程都属
于同一个进程组.但是,同一个进程组的父子进程可能在信号上有相
互通讯,若父进程先于子进程退出系统,则子进程会成为一个孤儿进
程,可能变成僵死进程.从而使该子进程在其不"愿意"的情况下退出
运行.为解决这个问题,子进程可以自己组成一个新的进程组,即调 用setpgrp()与原进程组脱离关系,产生一个新的进程组,进程组号 与它的进程号相同.这样,父进程退出运行后就不会影响子进程的当
前运行. . 子进程信号处理 但是,单做上述处理还不能解决另一个困难,即子进程在退出运行 时,找不到其父进程(父进程已退出,子进程的父进程号改为1).发送
子进程退出信号后没有父进程做出响应处理,该子进程就不可能完 全退出运行,可能进入僵死状态.所以父进程在产生子进程前最好屏
蔽子进程返回信号的处理,生成子进程,在父进程退出运行后,子进 程返回则其进程返回信号的处理会由系统给出缺省处理,子进程就 可以正常退出. (2)兄弟进程关系 . 交换进程号 对于信息协调方式的程序来说,各兄弟进程间十分需要相互了解进 程号,以便于信号处理机制.比较合理的方法是父进程生成一个共享
内存的空间,每个子进程都在启动时在共享内存中设置自己的进程 号.这样,当一个子进程要向另一个子进程发送信号或是因为其他原
因需要知道另一个子进程号时,就可以在共享内存中访问得到所需 要的进程号. 4.进程间通讯处理 (1)共享内存需要锁机制 由于共享内存在设计时没有处理锁机制,故当有多个进程在访问共享
内存时就会产生问题.如:一个进程修改一个共享内存单元,另一个进程在
读该共享内存单元时可能有第三个进程立即修改该单元,从而会影响程序
的正确性.同时还有分时系统对各进程是分时间片处理的,可能会引起不 同的正确性问题.按操作系统的运作方式,则有读锁和写锁来保证数据的 一致性.所以没有锁机制的共享内存,必须和信号量一起使用,才能保证共
享内存的正确操作. (2)消息队列需要关键值 消息队列的操作在进程取得消息队列的访问权限后就必须通过关键 值来读消息队列中的相同关键值的消息,写消息时带入消息关键值.这样 可以通过不同的关键值区分不同的交易,使得在同一个消息队列可以供多
种消息同时使用而不冲突.若读消息队列使用关键值0则读取消息队列中 第一个消息,不论其关键值如何. (3)信号需要信号处理函数设置和再设置 在用户进程需要对某个中断做自己定义的处理时,可以自己定义中断
处理函数,并设置中断处理函数与该中断相关联.这样,用户进程在收到该
中断后,即调用用户定义的函数,处理完成后用户进程从被中断处继续运 行(若用户定义的中断函数没有长跳函数或退出运行等会改变运行指令地
址的系统调用).在中断信号被处理后,该中断的处理函数会恢复成上次缺
省处理函数而不是保持用户定义函数,故在用户定义的中断处理函数中一
般都再定义该中断和函数自己的关联. (4)IPC的权限设置 在消息队列,共享内存和信号量的访问时有用户访问权限设置,类同 于文件的访问权限的设置如(777表示rwxrwxrwx),用命令ipcs即可看到在
系统中生成的消息队列,共享内存和信号量的访问权限.其意义也类似于 文件访问权限.只是执行位无效. 在有名管道和文件方式共享内存中以系统文件的方式定义了用户的 访问权限.用命令ls -l可以看到它们以系统文件方式存在并具有访问权 限值,并可以看到有名管道的文件类型为p,文件方式共享内存的文件类型
为s. (5)信号中断对系统调用一级有效 系统在设计系统调用时就考虑了中断处理问题.当进程运行到一个系
统调用时发生了中断,则进程进入该中断处理,处理完成后,进程会跳过该
系统调用而进入下一条程序指令. 应该注意的是中断发生在系统调用一级而不是子程序或函数一级.比
如一个程序在一个子程序被调用前设置了超时中断,并在子程序中收到超
时中断,系统在处理完超时中断后接着处理该子程序被中断的系统调用之
后的指令,而不是从调用该子程序名指令的后一条指令继续处理. (6)各种IPC方式的特点 . 消息队列: 通过消息队列key值定义和生成消息队列. 任何进程只要有访问权限并知道key即可访问消息队列. 消息队列为内存块方式数据段. 消息队列中的消息元素长度可为系统参数限制内的任何长度. 消息元素由消息类型分类,其访问方式为按类型访问. 在一次读写操作前都必须取得消息队列标识符,即访问权.访问后即
脱离访问关系. 消息队列中的某条消息被读后即从队列中删除. 消息队列的访问具备锁机制处理,即一个进程在访问时另一个进程 不能访问. 操作时要注意系统资源和效率. 在权限允许时,消息队列的信息传递是双向的. . 共享内存 通过共享内存key值定义和生成共享内存. 任何进程只要有访问权限并知道key即可访问共享内存. 共享内存为内存块方式的数据段. 共享内存中的数据长度可为系统参数限制内的任何长度. 共享内存的访问同数组的访问方式相同. 在取得共享内存标识符将共享内存与进程数据段联接后即可开始对
之进行读写操作,在所有操作完成之后再做共享内存和进程数据 段脱离操作,才完成全部共享内存访问过程. 共享内存中的数据不会因数据被进程读取后消失. 共享内存的访问不具备锁机制处理,即多个进程可能同时访问同一 个共享内存的同一个数据单元. 共享内存的使用最好和信号量一起操作,以具备锁机制,保证数据的
一致. 在权限允许时,共享内存的信息传递是双向的. . 信号量 用于生成锁机制,避免发生数据不一致. 没有其他的数据信息. 不需要有父子关系或兄弟关系. . 信号 信号由系统进行定义. 信号的发送只要有权限即可进行. 信号是一个事件发生的信息标志,不带有其它信息. 信号不具备数据块. 信号的处理可由用户自己定义. 信号可能由用户进程,操作系统(软件或硬件原因)等发出. 有一些信号是不可被屏蔽的. 信号中断的是系统调用级的函数. 信号的信息传递是单向的. . 管道 做为系统的特殊设备文件,可以是内存方式的,也可以是外存方式的.
管道的传输一般是单向的,即一个管道一向,若两个进程要做双向传
输则需要2个管道.管道生成时即有两端,一端为读,一端为写,两个 进程要协调好,一个进程从读方读,另一个进程向写方写. 管道的读写使用流设备的读写函数,即:read(),write. 管道的传输方式为FIFO,流方式的.不象消息队列可以按类型读取. * 有名管道 一般为系统特殊文件方式,使用的进程之间不一定要有父子关系 或兄弟关系. * 无名管道 一般为内存方式,使用的进程之间一定要有父子关系或兄弟关系.
. 文件 文件是最简单的进程间通讯方式,使用外部存贮器为中介. 操作麻烦,定位困难. 保密程度低. 容易出现数据不一致问题. 占用硬盘空间. 只要有权限并知道文件名,任何进程都可对之操作. * 特殊处理 为避免出现保密问题,在打开文件,取得文件描述符后,调用 unlink()将硬盘上的文件路径名删除,则硬盘上就没有文件拷贝 了.但在进程中该文件描述符是打开的,由该进程生成的子进程中
该文件描述符也是打开的,就可以利用系统提供的文件缓冲区做 进程间通讯,代价是进程间必须有父子关系或兄弟关系. . 环境变量 信息的传送一般是单向的,即由父进程向子进程传送. 保密性较好. 双方必须约定环境变量名. 只占用本进程和子进程的环境变量区. . 共享数据段 操作比较复杂. 占用硬盘空间,生成系统特殊文件. 其他性质与共享内存相类似. . 流 文件描述符的操作方式. 进程间不一定要有父子关系或兄弟关系. 双向传送信息. 进程各自生成socket,用bind()联接. 其他性质与管道相类似. 流编程为TCP/IP网络编程范围,在本文中暂不阐述. . 传递参数 信息的传送一般是单向的, 即由父进程向子进程传送. 保密性较差,用进程列表即可显示出来. 双方必须约定参数位置. 只占用子进程的参数区. |
|
|
|
|