您好,欢迎访问三七文档
当前位置:首页 > 行业资料 > 能源与动力工程 > c语言多进程多线程编程
C语言多进程编程一.多进程程序的特点进程是一个具有独立功能的程序关于某个数据集合的一次可以并发执行的运行活动,是处于活动状态的计算机程序。进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本实体。进程是资源管理的最小单位,线程是程序执行的最小单位。进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持多处理器系统和减小上下文切换开销。进程的状态系统为了充分的利用资源,对进程区分了不同的状态.将进程分为新建,运行,阻塞,就绪和完成五个状态.新建表示进程正在被创建,运行是进程正在运行,阻塞是进程正在等待某一个事件发生,就绪是表示系统正在等待CPU来执行命令,完成表示进程已经结束了系统正在回收资源.由于UNIX系统是分时多用户系统,CPU按时间片分配给各个用户使用,而在实质上应该说CPU按时间片分配给各个进程使用,每个进程都有自己的运行环境以使得在CPU做进程切换时不会忘记该进程已计算了一半的半成品”.以DOS的概念来说,进程的切换都是一次DOS中断处理过程,包括三个层次:1)用户数据的保存:包括正文段(TEXT),数据段(DATA,BSS),栈段(STACK),共享内存段(SHAREDMEMORY)的保存.2)寄存器数据的保存:包括PC(programcounter,指向下一条要执行的指令的地址),PSW(processorstatusword,处理机状态字),SP(stackpointer,栈指针),PCBP(pointerofprocesscontrolblock,进程控制块指针),FP(framepointer,指向栈中一个函数的local变量的首地址),AP(augumentpointer,指向栈中函数调用的实参位置),ISP(interruptstackpointer,中断栈指针),以及其他的通用寄存器等.3)系统层次的保存:包括proc,u,虚拟存储空间管理表格,中断处理栈.以便于该进程再一次得到CPU时间片时能正常运行。既然系统已经处理好所有这些中断处理的过程,我们做程序还有什么要担心的呢?我们尽可以使用系统提供的多进程的特点,让几个程序精诚合作,简单而又高效地把结果给它搞出来。另外,UNIX系统本身也是用C语言写的多进程程序,多进程编程是UNIX的特点,当我们熟悉了多进程?将会对UNIX系统机制有一个较深的认识.首先我介绍一下多进程程序的一些突出的特点:1.1并行化一件复杂的事件是可以分解成若干个简单事件来解决的,这在程序员的大脑中早就形成了这种概念,首先将问题分解成一个个小问题,将小问题再细分,最后在一个合适的规模上做成一个函数.在软件工程中也是这么说的.如果我们以图的方式来思考,一些小问题的计算是可以互不干扰的,可以同时处理,而在关键点则需要统一在一个地方来处理,这样程序的运行就是并行的,至少从人的时间观念上来说是这样的.而每个小问题的计算又是较简单的.1.2简单有序这样的程序对程序员来说不亚于管理一班人,程序员为每个进程设计好相应的功能,并通过一定的通讯机制将它们有机地结合在一起,对每个进程的设计是简单的,只在总控部分小心应付(其实也是蛮简单的),就可完成整个程序的施工.1.3.互不干扰这个特点是操作系统的特点,各个进程是独立的,不会串位.1.4.事务化比如在一个数据电话查询系统中,将程序设计成一个进程只处理一次查询即可,即完成一个事务.当电话查询开始时,产生这样一个进程对付这次查询;另一个电话进来时,主控程序又产生一个这样的进程对付,每个进程完成查询任务后消失.这样的编程多简单,只要做一次查询的程序就可以了.二.常用的多进程编程的系统调用2.1.fork()创建一个新的进程.功能:创建一个新的进程.语法:#includeunistd.h#includesys/types.hpid_tfork();说明:本系统调用产生一个新的进程,叫子进程,是调用进程的一个复制品.调用进程叫父进程,子进程继承了父进程的几乎所有的属性。进程:代码段(程序代码)堆栈段(局部变量、函数返回地址、函数参数)数据段(全局变量、常数等)在Linux系统中,系统调用fork后,内核为完成系统调用fork要进行几步操作:第一步,为新进程在进程表中分配一个表项。系统对一个普通用户可以同时运行的进程数是有限制的,对超级用户没有该限制,但不能超过进程表的最大表项的数目。第二步,给子进程一个唯一的进程标识号(PID)。该进程标识号其实就是该表项在进程表中的索引号。第三步,复制一个父进程的进程表项的副本给子进程。内核初始化子进程的进程表项时,是从父进程处拷贝的。所以子进程拥有与父进程一样的uid、当前目录、当前根、用户文件描述符表等。第四步,把与父进程相连的文件表和索引节点表的引用数加1。这些文件自动地与该子进程相连。第五步,内核为子进程创建用户级上下文。内核为子进程的代码段分配内存,并复制父进程的区内容,生成的是进程的静态部分。第六步,生成进程的动态部分,然后对父进程返回子进程的pid,对子进程返回0。从父进程拷贝的内容主要有:●用户标识符,包括实际用户号(real)和有效用户号(effective);●环境变量●打开的文件描述符、套接字描述符●信号处理设置●堆栈●目录●进程组标志(processID)●会晤组标志(sessionID)●正文子进程特有内容:●进程号●父进程号●进程执行时间●未处理的信号被处理为空●不继承异步的输入输出操作简述:fork()调用成功时,分别返回两个整数,对父进程返回〉0的整数,对子进程返回0,函数执行过程:①内核在系统进程表中,创建一个新条目;②复制父进程内容(已打开的文件描述符、堆栈、正文等);③修改两者的堆栈,给父进程返回子进程号,给子进程返回0(父进程知道每个子进程的标志号,而子进程可根据需要调用getppid()来获得父进程的标志号)。例子:pid_tfork(void)#includeunistd.hpid_tpid;if((pid=fork())==0){//子进程代码exit(0);}elseif(pid0){//父进程代码exit(0);}else{printf(Error);exit(1);}2.2.system()子进程执行指定的命令功能:产生一个新的进程,子进程执行指定的命令.语法:#includestdio.h#includestdlib.hintsystem(string)char*string;说明:本调用将参数string传递给一个命令解释器(一般为sh)执行,即string被解释为一条命令,由sh执行该命令.若参数string为一个空指针则为检查命令解释器是否存在.该命令可以同命令行命令相同形式,但由于命令做为一个参数放在系统调用中,应注意编译时对特殊意义字符的处理.命令的查找是按PATH环境变量的定义的.命令所生成的后果一般不会对父进程造成影响.返回值:当参数为空指针时,只有当命令解释器有效时返回值为非零.若参数不为空指针,返回值为该命令的返回状态(同waitpid())的返回值.命令无效或语法错误则返回非零值,所执行的命令被终止.其他情况则返回-1.例子:charcommand[81];inti;for(i=1;i8;i++){sprintf(command,psttty%02i,i);system(command);应用程序fork()父进程子进程1子进程2}2.3.exec()执行一个文件功能:执行一个文件语法#includeunistd.hintexecve(constchar*path,char*const*argv,char*const*envp);intexecl(constchar*path,char*arg,...);intexecp(constchar*file,char*arg,...);intexecle(constchar*path,constchar*argv,...,char*const*envp);intexecv(constchar*path,char*const*arg);intexecvp(constchar*file,char*const*arg);说明:exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似三十六计中的金蝉脱壳。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。fork()和exec()这两个函数,前者用于并行执行,父、子进程执行相同正文中的不同部分;后者用于调用其他进程,父、子进程执行不同的正文,调用前,一般应为子进程创造一个干净的环境。fork()以后,父、子进程共享代码段,并只重新创建数据有改变的页(段页式管理)exec()以后,建立新的代码段,用被调用程序的内容填充。前者的子进程执行后续的公共代码,后者的子进程不执行后续的公共代码。父、子进程以及各个子进程执行的顺序不定。.例子:printf(nowthisprocesswillbepscommand\n);execl(/bin/ps,ps,-ef,NULL);2.4.popen()初始化从/到一个进程的管道功能:初始化从/到一个进程的管道.语法:#includestdio.hFILE*popen(command,type)char*command,type;说明:本系统调用在调用进程和被执行命令间创建一个管道.参数command做为被执行的命令行.type做为I/O模式,r为从被执行命令读,w为向被执行命令写.返回一个标准流指针,做为管道描述符,向被执行命令读或写数据(做为被执行命令的STDIN或STDOUT)该系统调用可以用来在程序中调用系统命令,并取得命令的输出信息或者向命令输入信息.返回值:不成功则返回NULL,成功则返回管道的文件指针.2.5.pclose()关闭到一个进程的管道功能:关闭到一个进程的管道.语法:#includestdio.hintpclose(strm)FILE*strm;说明:本系统调用用于关闭由popen()打开的管道,并会等待由popen()激活的命令执行结束后,关闭管道后读取命令返回码.返回值:若关闭的文件描述符不是由popen()打开的,则返回-1.例子:printf(nowthisprocesswillcallpopensystemcall\n);FILE*fd;if((fd=popen(ps-ef,r))==NULL){printf(callpopenfailed\n);return;}else{charstr[80];while(fgets(str,80,fd)!=NULL)printf(%s\n,str);}pclose(fd);2.6.wait()等待一个子进程返回并修改状态功能:等待一个子进程返回并修改状态语法:#includesys/types.h#includesys/wait.hpid_twait(stat_loc)int*stat_loc;说明:允许调用进程取得子进程的状态信息.调用进程将会挂起直到其一个子进程终止.返回值:等待到一个子进程返回时,返回值为该子进程号,否则返回值为-1.同时stat_loc返回子进程的返回值.例子:/*父进程*/if(fork()0){wait((int*)0);/*父进程等待子进程的返回*/}else{/*子进程处理过程*/exit(0);}2.7.waitpid()等待指定进程号的子进程的返回并修改状态功能:等待指定
本文标题:c语言多进程多线程编程
链接地址:https://www.777doc.com/doc-5400322 .html