您好,欢迎访问三七文档
进程控制什么是进程进程是一个程序的一次执行的过程,同时也是资源分配的最小单元。程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念。进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整个过程,是程序执行和资源管理的最小单位。后台进程用户在Shell提示符处键入命令,执行一个程序的时候,它将启动一个进程。此后该进程接管终端,Shell中不能再有其它命令,直到进程执行完毕,才终端返回Shell并显示用户提示符。这种进程就是前台进程。如果用户在输入Shell命令时,在命令串后面加入“&”,Shell将不等待进程执行完毕就直接返回,于是就可以同时运行进程和执行Shell操作。这样的进程就是后台进程,后台进程并不接管终端,因此必须是非交互式的。守护进程•守护进程是与终端无关,常驻后台执行的特殊进程。Linux中最著名的两个守护进程是:•sysproc进程。Linux的第一个进程,标识号为0。它能合理地高度系统中运行的进程,负责将进程从硬盘交换区调入内存(换入),或者将进程从内存中调到硬盘交换区(换出)。•init进程。系统初始化进程,标识号为1,是除sysproc外所有进程的祖先。进程状态睡眠调度唤醒系统调用、中断返回进程标识•Linux系统中每个进程都有一个唯一的标识号,操作系统采用一个非负整数标识每个进程。•进程启动时,系统为进程分配标识号(进程ID);进程中止后,标识号可以重新使用。但任意时刻,一个标识号只对应一个进程。•函数getpid当前进程ID;函数getppid返回父进程ID获取进程ID#includestdio.h#includeunistd.h#includestdlib.hintmain(){/*获得当前进程的进程ID和其父进程ID*/printf(ThePIDofthisprocessis%d\n,getpid());printf(ThePPIDofthisprocessis%d\n,getppid());}启动进程•手工启动,由用户输入命令直接启动进程。–前台启动是手工启动一个进程的最常用方式。一般地,当用户键入一个命令如“ls-l”时,就已经启动了一个进程,并且是一个前台的进程。–若进程非常耗时,且用户也不急着需要结果,可使用后台启动。后台启动是在命令后加“&”。•调度启动,系统根据用户的设置自行启动进程。–系统需要进行一些比较费时而且占用资源的维护工作,并且这些工作适合在深夜无人值守的时候进行,这时用户就可以事先进行调度安排,指定任务运行的时间或者场合,到时候系统就会自动完成这一切工作。–使用调度启动进程有几个常用的命令,如at命令在指定时刻执行相关进程,cron命令可以自动周期性地执行相关进程fork函数•在Linux中创建一个子进程的惟一方法是使用fork()函数。fork()函数用于从已存在的进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。•使用fork()函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。fork函数所需头文件#includesys/types.h//提供类型pid_t的定义#includeunistd.h函数原型pid_tfork(void)函数传入值无函数返回值成功子进程:0父进程:子进程ID(大于0的整数)出错-1•和以往遇到的函数有一些区别,fork函数看起来执行一次却返回两个值。创建进程#includesys/types.h#includeunistd.h#includestdio.h#includestdlib.hintmain(void){pid_tresult;intcount=0;result=fork();//通过result的值来判断fork()函数的返回情况,首先进行出错处理if(result==-1)printf(Forkerror\n);elseif(result==0)//返回值为0代表子进程printf(I’mchildprocess!!\nMyPIDis%d\n,getpid());else//返回值大于0代表父进程printf(I’mfatherprocess!!\nMyPIDis%d\n,getpid());count++;printf(thecountis:%d\n,count);returnresult;}fork函数•fork在英文中是叉子,分叉的意思,在函数fork中,取后面的意思。很形象的表示程序从这里分叉,fork函数创建了子进程。•在父进程中执行fork()函数时,父进程会复制出一个子进程,而且父子进程的代码从fork()函数的返回开始分别在两个地址空间中同时运行(其实是cpu分时处理)。•两个进程分别获得其所属fork()的返回值,其中在父进程中的返回值是子进程的进程号,而在子进程中返回0。因此,可以通过返回值来判定该进程是父进程还是子进程。说明•fork()函数使用一次就创建一个进程,所以若把fork()函数放在了ifelse判断语句中则要小心,不能多次使用fork()函数。•由于fork()完整地复制了父进程的整个地址空间,因此执行速度较慢。为了加快fork()的执行速度,有些UNIX系统设计者创建了vfork()。vfork()也能创建新进程,但它不产生父进程的副本,而是通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才复制父进程。这就是著名的“写操作时复制”(copy-on-write)技术。•现在很多嵌入式Linux系统的fork()函数调用都采用vfork()函数的实现方式。思考#includesys/types.h#includeunistd.h#includestdio.h#includestdlib.hintmain(void){inti=0;printf(ison/pappidpidfpid\n);for(i=0;i2;i++){pid_tfpid=fork();if(fpid==0)printf(%dchild%4d%4d%4d\n,i,getppid(),getpid(),fpid);elseprintf(%dparent%4d%4d%4d\n,i,getppid(),getpid(),fpid);}return0;}执行程序•Linux系统提供了exec函数族,用于一个在进程中启动另一个程序执行。•exec函数族可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。exec后面的语句将不执行,系统跳到新进程的main()开始执行。•在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。•这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。•实际上,在Linux中并没有exec()函数,而是有6个以exec开头的函数,它们之间语法有细微差别exec函数族所需头文件#includeunistd.h函数原型intexecl(constchar*path,constchar*arg,...);intexeclp(constchar*file,constchar*arg,...);intexecle(constchar*path,constchar*arg,constchar*envp[]);intexecv(constchar*path,constchar*argv[]);intexecve(constchar*path,constchar*argv[],constchar*envp[];intexecvp(constchar*file,constchar*argv[]);函数传入值execl:第一个参数是包括路径的可执行文件,后面是列表参数,列表的第一个为命令path,接着为参数列表,最后必须以NULL结束。execlp:第一个参数可以使用相对路径或者绝对路径。execle:最后参数是指向自定义环境变量列表的指针,此列表必须以NULL结束。execv:最后参数是指向参数数组的指针,此列表的最后一项必须为NULL。execve:path后面接收一个参数列表向量,并可以指定一个环境变量列表向量。execvp:表示后面接收一个参数列表向量。函数返回值成功:若成功不返回出错:-1环境变量•exec函数族可以默认系统的环境变量,也可以传入指定的环境变量。•上列中,以“e”(environment)结尾的两个函数execle()和execve()就可以在envp[]中指定当前进程所使用的环境变量。•事实上,这6个函数中真正的系统调用只有execve(),其他5个都是库函数,它们最终都会调用execve()这个系统调用。执行程序#includeunistd.h#includestdio.h#includestdlib.hintmain(){if(fork()==0){/*调用execlp()函数,这里相当于调用了ps-ef命令*/if((ret=execlp(ps,ps,-ef,NULL))0){printf(Execlperror\n);}}}进程休眠•函数sleep使进程进入休眠状态:•此函数使调用进程被挂起,直到满足以下条件之一:–已经过了seconds所指定的墙上时钟时间–调用进程捕捉到一个信号并从信号处理程序返回所需头文件#includeunistd.h函数原型unsignedintsleep(unsignedintseconds)函数传入值seconds:休眠时长(秒)函数返回值时间结束:0提前返回:未睡够的时间(所要求的时间减去实际休眠时间)终止进程•程序执行到exit()或_exit()时,进程会无条件地停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。exit()_exit()两种退出函数区别•_exit()函数的作用是:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构;exit()函数则在调用exit系统之前要检查文件的打开情况,把文件缓冲区中的内容写回文件。•由于在Linux的标准函数库中,有一种被称作“缓冲I/O(bufferedI/O)”操作,其特征是对应每一个打开的文件,在内存中都有一片缓冲区。执行读写时仅操作缓冲区,等满足了一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。•这种技术大大增加了文件读写的速度,但也为编程带来了一些麻烦。比如有些数据,可能因为没有满足条件,只是被保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失。因此,若想保证数据的完整性,就一定要使用exit()函数。exit函数所需头文件exit:#includestdlib.h_exit:#includeunistd.h函数原型exit:voidexit(intstatus)_exit:void_exit(intstatus)函数传入值status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束。在实际编程时,可以用wait()系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理函数返回值无终止进程#includestdio.h#includeunistd.hintmain(){printf(Using_exit...\n);printf(“Thisisthecontentinbuffer”);//加上回车符之后结果又如何_exit(0);//若换成exit(0),结果又如何?}僵尸进程•在一个进程调用了exit()之后,该进程并不会
本文标题:进程控制
链接地址:https://www.777doc.com/doc-3844388 .html