下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 1492407

1492407.doc

1492407

mingche_god
2018-09-09 0人阅读 举报 0 0 暂无简介

简介:本文档为《1492407doc》,可适用于IT/计算机领域

║嵌入式应用程序开发详解第章进程控制开发║第章进程控制开发本章目标  文件是Linux中最常见最基础的操作对象而进程则是系统调度的单位在上一章学习了文件IO控制之后本章主要讲解进程控制开发部分通过本章的学习读者将会掌握以下内容。掌握进程相关的基本概念掌握Linux下的进程结构掌握Linux下进程创建及进程管理掌握Linux下进程创建相关的系统调用掌握守护进程的概念掌握守护进程的启动方法掌握守护进程的输出及建立方法学会编写多进程程序学会编写守护进程Linux下进程概述进程相关基本概念.进程的定义进程的概念首先是在年代初期由MIT的Multics系统和IBM的TSS系统引入的。经过了多年的发展人们对进程有过各种各样的定义。现列举较为著名的几种。()进程是一个独立的可调度的活动(ECohenDJofferson)()进程是一个抽象实体当它执行某个任务时将要分配和释放各种资源(PDenning)()进程是可以并行执行的计算部分。(SEMadnickJTDonovan)以上进程的概念都不相同但其本质是一样的。它指出了进程是一个程序的一次执行的过程。它和程序是有本质区别的程序是静态的它是一些保存在磁盘上的指令的有序集合没有任何执行的概念而进程是一个动态的概念它是程序执行的过程包括了动态创建、调度和消亡的整个过程。它是程序执行和资源管理的最小单位。因此对系统而言当用户在系统中键入命令执行一个程序的时候它将启动一个进程。.进程控制块进程是Linux系统的基本调度单位那么从系统的角度看如何描述并表示它的变化呢?在这里是通过进程控制块来描述的。进程控制块包含了进程的描述信息、控制信息以及资源信息它是进程的一个静态描述。在Linux中进程控制块中的每一项都是一个taskstruct结构它是在includelinuxschedh中定义的。.进程的标识在Linux中最主要的进程标识有进程号(PIDProcessIdenityNumber)和它的父进程号(PPIDparentprocessID)。其中PID惟一地标识一个进程。PID和PPID都是非零的正整数。在Linux中获得当前进程的PID和PPID的系统调用函数为getpid和getppid通常程序获得当前进程的PID和PPID可以将其写入日志文件以做备份。getpid和getppid系统调用过程如下所示:*processc*#incl交叉编译再将其下载到目标板上运行该程序可以得到如下结果该值在不同的系统上会有所不同:rootlocalhostprocess#processThePIDofthisprocessisTHePPIDofthisprocessis另外进程标识还有用户和用户组标识、进程时间、资源利用情况等这里就不做一一介绍感兴趣的读者可以参见WRichardStevens编著的《AdvancedProgrammingintheUNIXEnvironmen》。.进程运行的状态进程是程序的执行过程根据它的生命期可以划分成种状态。(执行态:该进程正在即进程正在占用CPU。(就绪态:进程已经具备执行的一切条件正在等待分配CPU的处理时间片。(等待态:进程不能使用CPU若等待事件发生则可将其唤醒。它们之间转换的关系图如图所示。图进程种状态的转化关系Linux下的进程结构Linux系统是一个多进程的系统它的进程之间具有并行性、互不干扰等特点。也就是说进程之间是分离的任务拥有各自的权利和责任。其中每一个进程都运行在各自独立的虚拟地址空间因此即使一个进程发生异常它也不会影响到系统中的其他进程。Linux中的进程包含个段分别为“数据段”、“代码段”和“堆栈段”。(“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。(“代码段”存放的是程序代码的数据。(“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量。如图所示。Linux下进程的模式和类型在Linux系统中进程的执行模式划分为用户模式和内核模式。如果当前运行的是用户程序、应用程序或者内核之外的系统程序那么对应进程就在用户模式下运行如果在用户程序执行过程中出现系统调用或者发生中断事件那么就要运行操作系统(即核心)程序进程模式就变成内核模式。在内核模式下运行的进程可以执行机器的特权指令而且此时该进程的运行不受用户的干扰即使是root用户也不能干扰内核模式下进程的运行。用户进程既可以在用户模式下运行也可以在内核模式下运行如图所示。Linux下的进程管理Linux下的进程管理包括启动进程和调度进程下面就分别对这两方面进行简要讲解。.启动进程Linux下启动一个进程有两种主要途径:手工启动和调度启动。手工启动是由用户输入命令直接启动进程而调度启动是指系统根据用户的设置自行启动进程。()手工启动手工启动进程又可分为前台启动和后台启动。(前台启动是手工启动一个进程的最常用方式。一般地当用户键入一个命令如“lsl”时就已经启动了一个进程并且是一个前台的进程。(后台启动往往是在该进程非常耗时且用户也不急着需要结果的时候启动的。比如用户要启动一个需要长时间运行的格式化文本文件的进程。为了不使整个shell在格式化过程中都处于“瘫痪”状态从后台启动这个进程是明智的选择。()调度启动有时系统需要进行一些比较费时而且占用资源的维护工作并且这些工作适合在深夜无人职守的时候进行这时用户就可以事先进行调度安排指定任务运行的时间或者场合到时候系统就会自动完成这一切工作。使用调度启动进程有几个常用的命令如at命令在指定时刻执行相关进程cron命令可以自动周期性地执行相关进程在需要使用时读者可以查看相关帮助手册。.调度进程调度进程包括对进程的中断操作、改变优先级、查看进程状态等在Linux下可以使用相关的系统命令实现其操作下表列出了Linux中常见的调用进程的系统命令读者在需要的时候可以自行查找其用法。表Linux中进程调度常见命令选项参数含义Ps查看系统中的进程Top动态显示系统中的进程Nice按用户指定的优先级运行Renice改变正在运行进程的优先级Kill终止进程(包括后台进程)crontab用于安装、删除或者列出用于驱动cron后台进程的任务。Bg将挂起的进程放到后台执行Linux进程控制编程进程创建.fork()在Linux中创建一个新进程的惟一方法是使用fork函数。fork函数是Linux中一个非常重要的函数和读者以往遇到的函数也有很大的区别它执行一次却返回两个值。希望读者能认真地学习这一部分的内容。()fork函数说明fork函数用于从已存在进程中创建一个新进程。新进程称为子进程而原进程称为父进程。这两个分别带回它们各自的返回值其中父进程的返回值是子进程的进程号而子进程则返回。因此可以通过返回值来判定该进程是父进程还是子进程。使用fork函数得到的子进程是父进程的一个复制品它从父进程处继承了整个进程的地址空间包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等而子进程所独有的只有它的进程号、资源使用和计时器等。因此可以看出使用fork函数的代价是很大的它复制了父进程中的代码段、数据段和堆栈段里的大部分内容使得fork函数的执行速度并不很快。()fork函数语法表列出了fork函数的语法要点。表fork函数语法要点所需头文件#include<systypesh>提供类型pidt的定义#include<unistdh>函数原型pidtfork(void)函数返回值:子进程子进程ID(大于的整数):父进程(:出错()fork函数使用实例*forkc*#include<systypesh>#include<unistdh>#include<stdioh>#include<stdlibh>intmain(void){pidtresult*调用fork函数其返回值为result*result=fork()*通过result的值来判断fork函数的返回情况首先进行出错处理*if(result==(){perror("fork")exit}*返回值为代表子进程*elseif(result==){printf("ThereturnvalueisdnInchildprocess!!nMyPIDisdn",result,getpid())}*返回值大于代表父进程*else{printf("ThereturnvalueisdnInfatherprocess!!nMyPIDisdn",result,getpid())}}rootlocalhostprocess#armlinuxgccforkc–ofork将可执行程序下载到目标板上运行结果如下所示:ThereturnvaludsInfatherprocess!!MyPIDisThereturnvalueis:Inchildprocess!!MyPIDis从该实例中可以看出使用fork函数新建了一个子进程其中的父进程返回子进程的PID而子进程的返回值为。()函数使用注意点fork函数使用一次就创建一个进程所以若把fork函数放在了ifelse判断语句中则要小心不能多次使用fork函数。小知识由于fork完整地拷贝了父进程的整个地址空间因此执行速度是比较慢的。为了加快fork的执行速度有些UNIX系统设计者创建了vfork。vfork也能创建新进程但它不产生父进程的副本。它是通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝当子进程需要改变内存中数据时才拷贝父进程。这就是著名的“写操作时拷贝”(copyonwrite)技术。现在很多嵌入式Linux系统的fork函数调用都采用vfork函数的实现方式实际上uClinux所有的多进程管理都通过vfork来实现。.exec函数族()exec函数族说明fork函数是用于创建一个子进程该子进程几乎拷贝了父进程的全部内容但是这个新创建的进程如何执行呢?这个exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件并用它来取代原调用进程的数据段、代码段和堆栈段在执行完之后原调用进程的内容除了进程号外其他全部被新的进程替换了。另外这里的可执行文件既可以是二进制文件也可以是Linux下任何可执行的脚本文件。在Linux中使用exec函数族主要有两种情况:(当进程认为自己不能再为系统和用户做出任何贡献时就可以调用任何exec函数族让自己重生(如果一个进程想执行另一个程序那么它就可以调用fork函数新建一个进程然后调用任何一个exec这样看起来就好像通过执行应用程序而产生了一个新进程。(这种情况非常普遍)()exec函数族语法实际上在Linux中并没有exec()函数而是有个以exec开头的函数族它们之间语法有细微差别本书在下面会详细讲解。下表列举了exec函数族的个成员函数的语法。表exec函数族成员函数语法所需头文件#include<unistdh>函数原型intexecl(constchar*path,constchar*arg,)intexecv(constchar*path,char*constargv)intexecle(constchar*path,constchar*arg,,char*constenvp)intexecve(constchar*path,char*constargv,char*constenvp)intexeclp(constchar*file,constchar*arg,)intexecvp(constchar*file,char*constargv)函数返回值(:出错这个函数在函数名和使用语法的规则上都有细微的区别下面就可执行文件查找方式、参数表传递方式及环境变量这几个方面进行比较。(查找方式读者可以注意到表中的前个函数的查找方式都是完整的文件目录路径而最后个函数(也就是以p结尾的两个函数)可以只给出文件名系统就会自动从环境变量“$PATH”所指出的路径中进行查找。(参数传递方式exec函数族的参数传递有两种方式:一种是逐个列举的方式而另一种则是将所有参数整体构造指针数组传递。在这里是以函数名的第位字母来区分的字母为“l”(list)的表示逐个列举的方式其语法为char*arg字母为“v”(vertor)的表示将所有参数整体构造指针数组传递其语法为*constargv。读者可以观察execl、execle、execlp的语法与execv、execve、execvp的区别。它们具体的用法在后面的实例讲解中会举例说明。这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字符串(包括该可执行程序命令本身)。要注意的是这些参数必须以表示结束如果使用逐个列举方式那么要把它强制转化成一个字符指针否则exec将会把它解释为一个整型参数如果一个整型数的长度char*的长度不同那么exec函数就会报错。(环境变量exec函数族可以默认系统的环境变量也可以传入指定的环境变量。这里以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp中指定当前进程所使用的环境变量。下表再对这个函数中函数名和对应语法做一总结主要指出了函数名中每一位所表明的含义希望读者结合此表加以记忆。表exec函数名对应含义前位统一为:exec第位l:参数传递为逐个列举方式execl、execle、execlpv:参数传递为构造指针数组方式execv、execve、execvp第位e:可传递新进程环境变量execle、execvep:可执行文件查找方式为文件名execlp、execvp()exec使用实例下面的第一个示例说明了如何使用文件名的方式来查找可执行文件同时使用参数列表的方式。这里用的函数是execlp。*execlpc*#include<unistdh>#include<stdioh>#include<stdlibh>intmain(){if(fork()==){*调用execlp函数这里相当于调用了“psef”命令*if(execlp("ps","ps","ef",)<)perror("execlperror!")}}在该程序中首先使用fork函数新建一个子进程然后在子进程里使用execlp函数。读者可以看到这里的参数列表就是在shell中使用的命令名和选项。并且当使用文件名的方式进行查找时系统会在默认的环境变量PATH中寻找该可执行文件。读者可将编译后的结果下载到目标板上运行结果如下所示:root(none)#execlpPIDTTYUidSizeStateCommandrootSinitrootSkeventdrootSksoftirqdCPUrootSkswapdrootSbdflushrootSkupdatedrootSmtdblockdrootSkhubdrootSbinbashusretcrclocalrootSbinbashrootSsbininetdrootSQtopiaqtopiafreebinqpeqwsrootSquicklauncherrootSusbstoragerootSscsiehrootRpsefroot(none)#envPATH=Qtopiaqtopiafreebin:usrbin:bin:usrsbin:sbin…此程序的运行结果与在Shell中直接键入命令“psef”是一样的当然在不同的系统不同时刻都可能会有不同的结果。接下来的示例使用完整的文件目录来查找对应的可执行文件。注意目录必须以“”开头否则将其视为文件名。*execlc*#include<unistdh>#include<stdioh>#include<stdlibh>intmain(){if(fork()==){*调用execl函数注意这里要给出ps程序所在的完整路径*if(execl("binps","ps","ef",)<)perror("execlerror!")}}同样下载到目标板上运行运行结果同上例如下所示:root(none)#execlPIDTTYUidSizeStateCommandrootSinitrootSkeventdrootSksoftirqdCPUrootSkswapdrootSbdflushrootSkupdated…示例利用函数execle将环境变量添加到新建的子进程中去这里的“env”是查看当前进程环境变量的命令如下所示:*execle*#include<unistdh>#include<stdioh>#include<stdlibh>intmain(){*命令参数列表必须以结尾*char*envp={"PATH=tmp","USER=sunq",}if(fork()==){*调用execle函数注意这里也要指出env的完整路径*if(execle("binenv","env",,envp)<)perror("execleerror!")}}下载到目标板后的运行结果如下所示:root(none)#execlePATH=tmpUSER=sunq最后一个示例使用execve函数通过构造指针数组的方式来传递参数注意参数列表一定要以作为结尾标识符。其代码和运行结果如下所示:#include<unistdh>#include<stdioh>#include<stdlibh>intmain(){*命令参数列表必须以结尾*char*arg={"env",}char*envp={"PATH=tmp","USER=sunq",}if(fork()==){if(execve("binenv",arg,,envp)<)perror("execveerror!")}}下载到目标板后的运行结果如下所示:root(none)#execvePATH=tmpUSER=sunq()exec函数族使用注意点在使用exec函数族时一定要加上错误判断语句。因为exec很容易执行失败其中最常见的原因有:(找不到文件或路径此时errno被设置为ENOENT(数组argv和envp忘记用结束此时errno被设置为EFAULT(没有对应可执行文件的运行权限此时errno被设置为EACCES。小知识事实上这个函数中真正的系统调用只有execve其他个都是库函数它们最终都会调用execve这个系统调用。.exit和exit()exit和exit函数说明exit和exit函数都是用来终止进程的。当程序执行到exit或exit时进程会无条件地停止剩下的所有操作清除包括PCB在内的各种数据结构并终止本进程的运行。但是这两个函数还是有区别的这两个函数的调用过程如图所示。SHAPE*MERGEFORMAT图exit和exit函数流程图从图中可以看出exit()函数的作用是:直接使进程停止运行清除其使用的内存空间并清除其在内核中的各种数据结构exit()函数则在这些基础上作了一些包装在执行退出之前加了若干道工序。exit()函数与exit()函数最大的区别就在于exit()函数在调用exit系统之前要检查文件的打开情况把文件缓冲区中的内容写回文件就是图中的“清理IO缓冲”一项。由于在Linux的标准函数库中有一种被称作“缓冲IO(bufferedIO)”操作其特征就是对应每一个打开的文件在内存中都有一片缓冲区。每次读文件时会连续读出若干条记录这样在下次读文件时就可以直接从内存的缓冲区中读取同样每次写文件的时候也仅仅是写入内存中的缓冲区等满足了一定的条件(如达到一定数量或遇到特定字符等)再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度但也为编程带来了一点麻烦。比如有一些数据认为已经写入了文件实际上因为没有满足特定的条件它们还只是保存在缓冲区内这时用exit()函数直接将进程关闭缓冲区中的数据就会丢失。因此若想保证数据的完整性就一定要使用exit()函数。()exit和exit函数语法下表列出了exit和exit函数的语法规范。表exit和exit函数族语法所需头文件exit:#include<stdlibh>exit:#include<unistdh>续表函数原型exit:voidexit(intstatus)exit:voidexit(intstatus)函数传入值status是一个整型的参数可以利用这个参数传递进程结束时的状态。一般来说表示正常结束其他的数值表示出现了错误进程非正常结束。在实际编程时可以用wait系统调用接收子进程的返回值从而针对不同的情况进行不同的处理()exit和exit使用实例:这两个示例比较了exit和exit两个函数的区别。由于printf函数使用的是缓冲IO方式该函数在遇到“n”换行符时自动从缓冲区中将记录读出。示例中就是利用这个性质来进行比较的。以下是示例的代码:*exitc*#include<stdioh>#include<stdlibh>intmain(){printf("Usingexitn")printf("Thisisthecontentinbuffer")exit()}root(none)#exitUsingexitThisisthecontentinbufferroot(none)#读者从输出的结果中可以看到调用exit函数时缓冲区中的记录也能正常输出。以下是示例的代码:*exitc*#include<stdioh>#include<unistdh>intmain(){printf("Usingexitn")printf("Thisisthecontentinbuffer")exit()}root(none)#exitUsingexitroot(none)#读者从最后的结果中可以看到调用exit函数无法输出缓冲区中的记录。小知识在一个进程调用了exit之后该进程并不马上就完全消失而是留下一个称为僵尸进程(Zombie)的数据结构。僵尸进程是一种非常特殊的进程它几乎已经放弃了所有内存空间没有任何可执行代码也不能被调度仅仅在进程列表中保留一个位置记载该进程的退出状态等信息供其他进程收集除此之外僵尸进程不再占有任何内存空间。.wait和waitpid()wait和waitpid函数说明wait函数是用于使父进程(也就是调用wait的进程)阻塞直到一个子进程结束或者该进程接到了一个指定的信号为止。如果该父进程没有子进程或者他的子进程已经结束则wait就会立即返回。waitpid的作用和wait一样但它并不一定要等待第一个终止的子进程它还有若干选项如可提供一个非阻塞版本的wait功能也能支持作业控制。实际上wait函数只是waitpid函数的一个特例在Linux内部实现wait函数时直接调用的就是waitpid函数。()wait和waitpid函数格式说明表列出了wait函数的语法规范。表wait函数族语法所需头文件#include<systypesh>#include<syswaith>函数原型pidtwait(int*status)函数传入值这里的status是一个整型指针是该子进程退出时的状态(status若为空则代表任意状态结束的子进程(status若不为空则代表指定状态结束的子进程另外子进程的结束状态可由Linux中一些特定的宏来测定函数返回值成功:子进程的进程号失败:(下表列出了waitpid函数的语法规范。表waitpid函数语法所需头文件#include<systypesh>#include<syswaith>函数原型pidtwaitpid(pidtpid,int*status,intoptions)函数传入值pidpid>:只等待进程ID等于pid的子进程不管已经有其他子进程运行结束退出了只要指定的子进程还没有结束waitpid就会一直等下去pid=(:等待任何一个子进程退出此时和wait作用一样pid=:等待其组ID等于调用进程的组ID的任一子进程pid<(:等待其组ID等于pid的绝对值的任一子进程续表函数传入值status同waitoptionsWNOHANG:若由pid指定的子进程不立即可用则waitpid不阻塞此时返回值为WUNTRACED:若实现某支持作业控制则由pid指定的任一子进程状态已暂停且其状态自暂停以来还未报告过则返回其状态:同wait阻塞父进程等待子进程退出函数返回值正常:子进程的进程号使用选项WNOHANG且没有子进程退出:调用出错:(()waitpid使用实例由于wait函数的使用较为简单在此仅以waitpid为例进行讲解。本例中首先使用fork新建一子进程然后让其子进程暂停s(使用了sleep函数)。接下来对原有的父进程使用waitpid函数并使用参数WNOHANG使该父进程不会阻塞。若有子进程退出则waitpid返回子进程号若没有子进程退出则waitpid返回并且父进程每隔一秒循环判断一次。该程的流程图如图所示。图waitpid示例程序流程图该程序源代码如下所示:*waitpidc*#include<systypesh>#include<syswaith>#include<unistdh>#include<stdioh>#include<stdlibh>intmain(){pidtpc,prpc=fork()if(pc<)printf("Errorforkn")*子进程*elseif(pc==){*子进程暂停s*sleep()*子进程正常退出*exit()}*父进程*else{*循环测试子进程是否退出*do{*调用waitpid且父进程不阻塞*pr=waitpid(pc,,WNOHANG)*若子进程还未退出则父进程暂停s*if(pr==){printf("Thechildprocesshasnotexitedn")sleep()}}while(pr==)*若发现子进程退出打印出相应情况*if(pr==pc)printf("Getchilddn",pr)elseprintf("someerroroccuredn")}}将该程序交叉编译下载到目标板后的运行情况如下所示:root(none)#waitpidThechildprocesshasnotexitedThechildprocesshasnotexitedThechildprocesshasnotexitedThechildprocesshasnotexitedThechildprocesshasnotexitedGetchild可见该程序在经过次循环之后捕获到了子进程的退出信号具体的子进程号在不同的系统上会有所区别。读者还可以尝试把“pr=waitpid(pc,,WNOHANG)”这句改为“pr=waitpid(pc,,)”和“pr=wait()”运行的结果为:root(none)#waitpidGetchild可见在上述两种情况下父进程在调用waitpid或wait之后就将自己阻塞直到有子进程退出为止。Linux守护进程守护进程概述守护进程也就是通常所说的Daemon进程是Linux中的后台服务进程。它是一个生存期较长的进程通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动在系统关闭时终止。Linux系统有很多守护进程大多数服务都是通过守护进程实现的如本书在第二章中讲到的系统服务都是守护进程。同时守护进程还能完成许多系统任务例如作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。由于在Linux中每一个系统与用户进行交流的界面称为终端每一个从此终端开始运行的进程都会依附于这个终端这个终端就称为这些进程的控制终端当控制终端被关闭时相应的进程都会自动关闭。但是守护进程却能够突破这种限制它从被执行开始运转直到整个系统关闭时才会退出。如果想让某个进程不因为用户或终端或其他的变化而受到影响那么就必须把这个进程变成一个守护进程。可见守护进程是非常重要的。编写守护进程编写守护进程看似复杂但实际上也是遵循一个特定的流程。只要将此流程掌握了就能很方便地编写出用户自己的守护进程。下面就分个步骤来讲解怎样创建一个简单的守护进程。在讲解的同时会配合介绍与创建守护进程相关的几个系统函数希望读者能很好地掌握。.创建子进程父进程退出这是编写守护进程的第一步。由于守护进程是脱离控制终端的因此完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成而用户在Shell终端里则可以执行其他的命令从而在形式上做到了与控制终端的脱离。到这里有心的读者可能会问父进程创建了子进程而父进程又退出之后此时该子进程不就没有父进程了吗?守护进程中确实会出现这么一个有趣的现象由于父进程已经先于子进程退出会造成子进程没有父进程从而变成一个孤儿进程。在Linux中每当系统发现一个孤儿进程就会自动由号进程(也就是init进程)收养它这样原先的子进程就会变成init进程的子进程了。其关键代码如下所示:*父进程退出*pid=fork()if(pid>){exit()}.在子进程中创建新会话这个步骤是创建守护进程中最重要的一步虽然它的实现非常简单但它的意义却非常重大。在这里使用的是系统函数setsid在具体介绍setsid之前读者首先要了解两个概念:进程组和会话期。(进程组进程组是一个或多个进程的集合。进程组由进程组ID来惟一标识。除了进程号(PID)之外进程组ID也一个进程的必备属性。每个进程组都有一个组长进程其组长进程的进程号等于进程组ID。且该进程ID不会因组长进程的退出而受到影响。(会话期会话组是一个或多个进程组的集合。通常一个会话开始于用户登录终止于用户退出在此期间该用户运行的所有进程都属于这个会话期它们之间的关系如下图所示。接下来就可以具体介绍setsid的相关内容:()setsid函数作用setsid函数用于创建一个新的会话并担任该会话组的组长。调用setsid有下面的个作用。(让进程摆脱原会话的控制。(让进程摆脱原进程组的控制。(让进程摆脱原控制终端的控制。SHAPE*MERGEFORMAT图进程组、会话期关系图那么在创建守护进程时为什么要调用setsid函数呢?读者可以回忆一下创建守护进程的第一步在那里调用了fork函数来创建子进程再将父进程退出。由于在调用fork函数时子进程全盘拷贝了父进程的进会话期、进程组、控制终端等虽然父进程退出了但原先的会话期、进程组、控制终端等并没有改变因此还不是真正意义上独立开来而setsid函数能够使进程完全独立出来从而脱离所有其他进程的控制。()setsid函数格式下表列出了setsid函数的语法规范表setsid函数语法所需头文件#include<systypesh>#include<unistdh>函数原型pidtsetsid(void)函数返回值成功:该进程组ID出错:(.改变当前目录为根目录这一步也是必要的步骤。使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中当前目录所在的文件系统(比如“mntusb”等)是不能卸载的这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此通常的做法是让“”作为守护进程的当前工作目录这样就可以避免上述的问题当然如有特殊需要也可以把当前工作目录换成其他的路径如tmp。改变工作目录的常见函数是chdir。.重设文件权限掩码文件权限掩码是指屏蔽掉文件权限中的对应位。比如有一个文件权限掩码是它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码这就给该子进程使用文件带来了诸多的麻烦。因此把文件权限掩码设置为可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里通常的使用方法为umask()。.关闭文件描述符同文件权限掩码一样用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写但它们一样消耗系统资源而且可能导致所在的文件系统无法卸下。在上面的第二步之后守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以文件描述符为、和的个文件(常说的输入、输出和报错这个文件)已经失去了存在的价值也应被关闭。通常按如下方式关闭文件描述符:for(i=i<MAXFILEi)close(i)这样一个简单的守护进程就建立起来了创建守护进程的流程图如图所示。图创建守护进程流程图下面是实现守护进程的一个完整实例:该实例首先建立了一个守护进程然后让该守护进程每隔s在tmpdameonlog中写入一句话。*dameonc创建守护进程实例*#include<stdioh>#include<stdlibh>#include<stringh>#include<fcntlh>#include<systypesh>#include<unistdh>#include<syswaith>#defineMAXFILEintmain(){pidtpcinti,fd,lenchar*buf="ThisisaDameonn"len=strlen(buf)pc=fork()第一步if(pc<){printf("errorforkn")exit()}elseif(pc>)exit()*第二步*setsid()*第三步*chdir("")*第四步*umask()for(i=i<MAXFILEi)*第五步*close(i)*这时创建完守护进程以下开始正式进入守护进程工作*while(){if((fd=open("tmpdameonlog",OCREAT|OWRONLY|OAPPEND,))<){perror("open")exit()}write(fd,buf,len)close(fd)sleep()}}将该程序下载到开发板中可以看到该程序每隔s就会在对应的文件中输入相关内容。并且使用ps可以看到该进程在后台运行。如下所示:root(none)#tailftmpdameonlogThisisaDameonThisisaDameonThisisaDameonThisisaDameon…root(none)#psef|grepdaemonrootSdaemonrootSgrepdaemon守护进程的出错处理读者在前面编写守护进程的具体调试过程中会发现由于守护进程完全脱离了控制终端因此不能像其他进程的程序一样通过输出错误信息到控制终端来通知程序员即使使用gdb也无法正常调试。那么守护进程的进程要如何调试呢?一种通用的办法是使用syslog服务将程序中的出错信息输入到“varlogmessages”系统日志文件中从而可以直观地看到程序的问题所在。注意“varlogmessage”系统日志文件只能由拥有root权限的超级用户查看。Syslog是Linux中的系统日志管理服务通过守护进程syslogd来维护。该守护进程在启动时会读一个配置文件“etcsyslogconf”。该文件决定了不同种类的消息会发送向何处。例如紧急消息可被送向系统管理员并在控制台上显示而警告消息则可记录到一个文件中。该机制提供了个syslog函数分别为openlog、syslog和closelog。下面就分别介绍这个函数。()syslog函数说明通常openlog函数用于打开系统日志服务的一个连接syslog函数是用于向日志文件中写入消息在这里可以规定消息的优先级、消息输出格式等closelog函数是用于关闭系统日志服务的连接。()syslog函数格式下表列出了openlog函数的语法规范表openlog函数语法所需头文件#include<syslogh>函数原型voidopenlog(char*ident,intoption,intfacility)函数传入值Ident要向每个消息加入的字符串通常为程序的名称OptionLOGCONS:如果消息无法送到系统日志服务则直接输出到系统控制终端LOGNDELAY:立即打开系统日志服务的连接。在正常情况下直到发送到第一条消息时才打开连接LOGPERROR:将消息也同时送到stderr上LOGPID:在每条消息中包含进程的PID续表函数传入值facility:指定程序发送的消息类型LOGAUTHPRIV:安全授权讯息LOGCRON:时间守护进程(cron及at)LOGDAEMON:其他系统守护进程LOGKERN:内核信息LOGLOCAL~:保留LOGLPR:行打印机子系统LOGMAIL:邮件子系统LOGNEWS:新闻子系统LOGSYSLOG:syslogd内部所产生的信息LOGUSER:一般使用者等级讯息LOGUUCP:UUCP子系统表列出了syslog函数的语法规范。表syslog函数语法所需头文件#include<syslogh>函数原型voidsyslog(intpriority,char*format,)函数传入值priority:指定消息的重要性LOGEMERG:系统无法使用LOGALERT:需要立即采取措施LOGCRIT:有重要情况发生LOGERR:有错误发生LOGWARNING:有警告发生LOGNOTICE:正常情况但也是重要情况LOGINFO:信息消息LOGDEBUG:调试信息format以字符串指针的形式表示输出的格式类似printf中的格式表列出了closelog函数的语法规范。表closelog函数语法所需头文件#include<syslogh>函数原型voidcloselog(void)()使用实例这里将上一节中的示例程序用syslog服务进行重写其中有区别的地方用加粗的字体表示源代码如下所示:*syslogdemac利用syslog服务的守护进程实例*#include<stdioh>#include<stdlibh>#include<stringh>#include<fcntlh>#include<systypesh>#include<unistdh>#include<syswaith>#include<syslogh>#defineMAXFILEintmain(){pidtpc,sidinti,fd,lenchar*buf="ThisisaDameonn"len=strlen(buf)pc=fork()if(pc<){printf("errorforkn")exit()}elseif(pc>)exit()*打开系统日志服务openlog*openlog("demoupdate",LOGPID,LOGDAEMON)if((sid=setsid())<){syslog(LOGERR,"sn","setsid")exit()}if((sid=chdir(""))<){syslog(LOGERR,"sn","chdir")exit()}umask()for(i=i<MAXFILEi)close(i)while(){*打开守护进程的日志文件并写入open的日志记录*if((fd=open("tmpdameonlog",OCREAT|OWRONLY|OAPPEND,))<){syslog(LOGERR,"open")exit()}write(fd,buf,len)close(fd)sleep()}closelog()exit()}读者可以尝试用普通用户的身份执行此程序由于这里的open函数必须具有root权限因此syslog就会将错误信息写入到“varlogmessages”中如下所示:Jan::localhostdemoupdate:open实验内容编写多进程程序.实验目的通过编写多进程程序使读者熟练掌握fork、exec、wait、waitpid等函数的使用进一步理解在Linux中多进程编程的步骤。.实验内容该实验有个进程其中一个为父进程其余两个是该父进程创建的子进程其中一个子进程运行“lsl”指令另一个子进程在暂停s之后异常退出父进程并不阻塞自己并等待子进程的退出信息待收集到该信息父进程就返回。.实验步骤()画出该实验流程图该实验流程图如图所示。()实验源代码具体代码设置如下:*excc实验一源码*#include<stdioh>#include<stdlibh>#include<systypesh>#include<unistdh>#include<syswaith>intmain(void){pidtchild,child,child*创建两个子进程*child=fork()child=fork()*子进程的出错处理*if(child==(){perror("childfork")exit()}*在子进程中调用execlp函数*elseif(child==){printf("Inchild:execute'lsl'n")if(execlp("ls","ls","l",)<)perror("childexeclp")}*子进程的出错处理*if(child==(){perror("childfork")exit()}*在子进程中使其暂停s*elseif(child==){printf("Inchild:sleepforsecondsandthenexitn")sleep()exit()}*在父进程中等待子进程的退出*else{printf("Infatherprocess:n")do{child=waitpid(child,,WNOHANG)if(child==){printf("Thechildprocesshasnotexited!n")sleep()}}while(child==)if(child==child)printf("Getchildn")elseprintf("Erroroccured!n")}}图实验流程图()首先在宿主机上编译调试该程序:rootlocalhostprocess#gccexcc–oexc()在确保没有编译错误后使用交叉编译该程序:rootlocalhostprocess#armlinuxgccexcc–oexc()将生成的可执行程序下载到目标板上运行。.实验结果在目标板上运行的结果如下所示(具体内容与各自的系统有关):root(none)#excInchild:execute'lsl'Inchild:execute'lsl'Inchild:sleepforsecondsandthenexittotalrwxrxrxrootrootJanexcrwxrxrxrootrootJanexitrwxrxrxrootrootJanforkrwxrxrxrootrootJanwaitpidtotalrwxrxrxrootrootJanexcrwxrxrxrootrootJanexitrwxrxrxrootrootJanforkrwxrxrxrootrootJanwaitpidInfatherprocess:Thechildprocesshasnotexited!Thechildprocesshasnotexited!Thechildprocesshasnotexited!Thechildprocesshasnotexited!Thechildprocesshasnotexited!Getchild因为几个子进程的执行有竞争关系因此结果中的顺序没有完全按照程来所编写。读者可以思考怎样可以保证子进程的执行顺序呢?编写守护进程.实验目的通过编写一个完整的守护进程使读者掌握守护进程编写和调试的方法并且进一步熟悉编写多进程程序。.实验内容在该实验中读者首先建立起一个守护进程然后在该守护进程中新建一个子进程该子进程暂停s然后自动退出并由守护进程收集子进程退出的消息。在这里子进程和守护进程的退出消息都在“varlogmessages”中输出。子进程退出后守护进程循环暂停其间隔时间为s。.实验步骤()画出该实验流程图该程序流程图如图所示。图实验流程图()实验源代码具体代码设置如下:*excc实验二源码*#include<stdioh>#include<stdlibh>#include<systypesh>#include<unistdh>#include<syswaith>#include<syslogh>#defineMAXFILEintmain(void){pidtchild,childintichild=fork()*创建子进程*if(child==(){perror("childfork")exit()}elseif(child>)exit()*打开日志服务*openlog("excinfo",LOGPID,LOGDAEMON)*以下几步是编写守护进程的常规步骤*setsid()chdir("")umask()for(i=i<MAXFILEi){close(i)}*创建子进程*child=fork()if(child==(){perror("childfork")exit()}elseif(child==){*在日志中写入字符串*syslog(LOGINFO,"childwillsleepfors")sleep()syslog(LOGINFO,"childisgoingtoexit!")exit()}else{waitpid(child,,)syslog(LOGINFO,"childnoticedthatchildhasexited")*关闭日志服务*closelog()while(){sleep()}}}()由于有些嵌入式开发板没有syslog服务读者可以在宿主机上编译运行。rootlocalhostprocess#gccexcc–oexc()运行该程序。()等待s后以root身份查看“varlogmessages”文件。()使用ps–ef|grepexc查看该守护进程是否在运行。.实验结果()在“varlogmessages”中有类似如下的信息显示:Jan::localhostexcinfo:childwillsleepforsJan::localhostexcinfo:childisgoingtoexit!Jan::localhostexcinfo:childnoticedthatchildhasexited读者可以从时间戳里清楚地看到child确实暂停了s。()使用命令ps–ef|grepexc可看到如下结果:root:::exc可见exc确实一直在运行。本章小结本章主要介绍进程的控制开发首先给出了进程的基本概念Linux下进程的基本结构、模式与类型以及Linux进程管理。进程是Linux中程序运行和资源管理的最小单位对进程的处理也是嵌入式Linux应用编程的基础因此读者一定要牢牢掌握。接下来本章具体讲解了进程控制编程主要讲解了fork函数和exec函数族并且举实例加以区别。Exec函数族较为庞大希望读者能够仔细比较它们之间的区别认真体会并理解。最后本章讲解了Linux守护进程的编写包括守护进程的概念、编写守护进程的步骤以及守护进程的出错处理。由于守护进程非常特殊因此在编写时有不少的区别需要特别注意。守护进程的编写实际上涉及进程控制编程的很多部分需要加以综合应用。本章的实验安排了多进程编程和编写完整的守护进程两个部分。这两个实验都是较为综合性的希望读者能够认真完成。思考与练习查阅资料明确Linux中进程处理和嵌入式Linux中对进程的处理有什么区别?�EMBEDWordPicture����SHAPE*MERGEFORMAT��图用户进程的两种运行模式�EMBEDWordPicture���图Linux中进程结构示意图doc进程组进程组进程进程会话期登陆shelldoc等待就绪执行时间片到调度因等待事件发生�而唤醒等待某个事件发生而睡眠docdocdoc内核态用户态用户进程中断或系统调用内核进程vsd��开始�fork创建两个子进程�child�child�父进程调用exec函数族调用sleep函数退出等待child�发现child�结束�doc进程中止运行调用exit系统调用清理IO缓冲调用退出处理函数进程运行exit()exit()doc堆栈段数据段代码段vsd��开始�fork()exit()�setsid()�chdir(“”)�umash()�close()�结

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/32

1492407

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利