嵌入式Linux学习笔记第四天 ——进程控制

2019-07-12 22:26发布

嵌入式Linux学习笔记第四天 ——进程控制 一:进程相关的基本概念 进程:进程是一个具有一定独立功能的程序的一次运行活动。 特点:动态性、并发性、独立性、异步性。 操作系统三态图: 进程ID(PID):标识进程的唯一数字,一般为非负型数字。好比如我们的身份证一样,每个人的身份证号是唯一的。因为进程ID标示符总是唯一的,常将其用来做其他标示符的一部分以保证其唯一性,进程ID(PID)是无法在用户层修改的。 父进程ID(PPID):任何一个进程(除init进程)都是由另一个进程创建,该进程称为被创建进程的父进程(PPID)。父进程ID无法在用户层修改,父进程的进程ID即为子进程的父进程ID(PPID)。 进程组ID(PGID):在Linux系统中,每个用户都有用户ID(UID)和用户组ID(GUID).同样,进程也拥有自己的进程ID(PID)和进程组ID(PGID)。进程组是一个或多个进程的集合;他们与同一作业相关联.每个进程组都有唯一的进程组ID(PGID),进程组ID(PGID)可以在用户层修改。比如,将某个进程添加到另一个进程组,就是使用setpgid()函数修改其进程组ID。 每个进程组都可以有一个组长进程,组长进程的进程组ID等于其进程ID.但组长进程可以先退出,即只要在某个进程组中有一个进程存在,则该进程组就存在,与其组长进程是否存在无关.进程组的最后进程可以退出或转移到其他组. 会话期:会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。 进程互斥:两个或两个以上的进程,不能同时进入关于同一组共享变量的临界区域,否则可能发生与时间有关的错误,这种现象被称作进程互斥· 也就是说,一个进程正在访问临界资源,另一个要访问该资源的进程必须等待。 临界资源:临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。 临界区:每个进程中访问临界资源的那段代码称为临界区(Critical Section),每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。 进程同步:进程是并发执行的,不同进程之间存在着不同的相互制约关系。 进程调度:按一定的算法,从一组待运行的进程中选出一个来占用CPU运行。 调度算法:         1:先来先服务调度算法   2:短进程优先调度算法   3:高优先级优先调度算法(在linux操作系统数值越大,优先级越低)   4:时间片轮转法 死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。       二:进程编程 1:获取本进程ID:       getpid(取得进程识别码) 相关函数:fork,kill,getpid 表头文件:#include               #include 定义函数:pid_t getpid(void); 函数说明:getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。 返回值:       成功:目前进程的进程识别码                      失败:-1,出错原因存于error   2:获取父进程ID:        getppid(取得父进程的进程识别码) 相关函数:fork,kill,getpid 表头文件:#include               #include 定义函数:pid_t getppid(void); 函数说明:getppid()用来取得目前进程的父进程识别码。 返回值:       成功:目前进程的父进程识别码                      失败:-1,出错原因存于error 例:getpid.c #include #include #include #include   int main(void) {        printf("Pid =%d ",getpid());        printf("Ppid =%d ",getppid());        return 0; }   运行结果
  3:创建子进程 fork 相关函数:fork,kill,getpid 表头文件:#include               #include 定义函数:pid_t fork(void); 函数说明:fork()创建子进程。调用一次,返回两次 返回值:       在父进程:fork返回新创建的子进程PID                      在子进程:fork返回0                      错误: 返回负值   子进程的数据空间,堆栈空间都是从父进程拷贝的,而不是共享。     例程:fork.c #include #include #include #include #include #include #include   int main(void) {        pid_t child;          child = fork();        /*创建子进程*/        if(child < 0)        {               printf("ForkError :%s ",strerror(errno));               exit(1);        }        else if(child == 0)  //子进程               {                      printf("iam the child: %d ",getpid());                      exit(0);               }               else //父进程               {                      printf("iam the futher: %d ",getpid());                      return(0);               } }       4:建立一个新的进程vfork() 相关函数:wait,execve 表头文件:#include 定义函数:pid_t vfork(void); 函数说明:vfork()会产生一个新的子进程,其子进程会复制父进程的数据与堆栈空间,并继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限制等。Linux 使用copy-on-write(COW)技术,只有当其中一进程试图修改欲复制的空间时才会做真正的复制动作,由于这些继承的信息是复制而来,并非指相同的内存空间,因此子进程对这些变量的修改和父进程并不会同步。此外,子进程不会继承父进程的文件锁定和未处理的信号。注意,Linux不保证子进程会比父进程先执行或晚执行,因此编写程序时要留意 死锁或竞争条件的发生。   返回值:如果vfork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回0。如果vfork 失败则直接返回-1,失败原因存于errno中。 错误代码 EAGAIN 内存不足。ENOMEM 内存不足,无法配置核心所需的数据结构空间。   Fork 与vfork的区别 1:fork:子进程拷贝父进程的数据空间    Vfork:子进程与父进程的数据空间是共享的   2:fork:子进程与父进程的执行次序不确定 Vfork:子进程先运行,父进程后运行   例程: #include #include   int main(void) {   pid_t pid;   int count = 0;     pid = fork();     count++;   printf("count = %d ",count);     return(0); } 结果   将vfork改成 fork 结果
最后出错了,并且结果也不是预期的1,2 vfork创建子进程,父子进程是共享数据段的,且子进程先运行,当子进程运行到return0后,就将公共数据区里的count变量收回。再运行父进程,此时父进程里没有count变量,有些及其可能会是个随机数,得到的打印的count值会很大,而大部分的机器在没有初始化变量的情况下会默认count值为0,故此时可以打印出1。然而 最后经过上述理解,我觉得问题主要出在return 0上,  exit()(或return 0)会调用终止处理程序和用户空间的标准I/O清理程序(如fclose),这样会清楚本进程内或者函数内的数据,而 _exit和_Exit不调用而直接由内核接管进行清理,故不会立即进程变量的回收和清理,因为内核的清理一般是整个程序结束时才会进行空间回收。所以大家以后在使用vfork时返回函数一定使用_exit(),而尽量不要使用exit()或者return。 改为如下 #include #include   int main(void) {   pid_t pid;   int count = 0;     pid = vfork();     count++;   printf("count = %d ",count);          //return 0;   _exit(0); } 结果:   5:exec函数族 Fork是创建新的进程,产生新的PID。 Exec启动新的程序,替换原有的进程,不会产生新的PID。 execl(执行文件) 相关函数 fork,execle,execlp,execv,execve,execvp 表头文件 #include 定义函数 int execl(const char * path,const char * arg,....); 函数说明 execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。   execlp(从PATH 环境变量中查找文件并执行) 相关函数:fork,execl,execle,execv,execve,execvp 表头文件:#include 定义函数:int execlp(const char *file,const char * arg,……); 函数说明:execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。   execv(执行文件) 相关函数:fork,execl,execle,execlp,execve,execvp 表头文件:#include 定义函数:int execv (const char * path,char * const argv[ ]); 函数说明:execv()用来执行参数path字符串所代表的文件路径,与execl()不同的地方在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件。 返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。   execve(执行文件) 相关函数 fork,execl,execle,execlp,execv,execvp 表头文件 #include 定义函数 int execve(const char * filename,char * const argv[ ],char * const envp[ ]); 函数说明 execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。 返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。  
execvp(执行文件) 相关函数 fork,execl,execle,execlp,execv,execve 表头文件 #include 定义函数 int execvp(const char *file ,char * const argv []); 函数说明 execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。 返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中   例程: #include #include #include   int main(void) {        if(fork() == 0)               if(execl("/bin/ps","ps","-ef",NULL)< 0 )                      perror("execlerror"); }     进程等待 wait(等待子进程中断或结束) 相关函数 waitpid,fork 表头文件 #include
#include 定义函数 pid_t wait (int * status); 函数说明 wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则 参数 status可以设成NULL。子进程的结束状态值请参考waitpid()。 返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。   waitpid(等待子进程中断或结束) 相关函数:wait,fork 表头文件:#include #include 定义函数:pid_t waitpid(pid_t pid,int *status,int options); 函数说明:waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码,其他数值意义如下: pid<-1 等待进程组识别码为pid绝对值的任何子进程。 pid=-1 等待任何子进程,相当于wait()。 pid=0 等待进程组识别码与目前进程相同的任何子进程。 pid>0 等待任何子进程识别码为pid的子进程。 参数option可以为0 或下面的OR 组合 WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以等待。 WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。 子进程的结束状态返回后存于status,底下有几个宏可判别结束情况 WIFEXITED(status)如果子进程正常结束则为非0值。 WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED来判断是否正常结束才能使用此宏。 WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真 WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。 WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。 WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。 返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。   例程: #include #include #include #include #include #include   int main(void) {        pid_t child;               /*创建子进程*/        child = fork();          if(child == -1)        {               printf("ForkError:%s ",strerror(errno));               exit(1);        }        else if(child == 0) //子进程        {               printf("thechild process is runing ");               sleep(5);               printf("i amthe child: %d ",getpid());               exit(0);        }        else        {               wait(NULL);  //待子进程退出时,父进程才运行               printf("thefuther process is runing ");               printf("i amthe father:%d ",getpid());               return 0;        }   }   结果   进程结束 exit(正常结束进程) 相关函数:_exit,atexit,on_exit 表头文件:#include 定义函数:void exit(int status); 函数说明:exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。 返回值   _ exit(结束进程执行) 相关函数 exit,wait,abort 表头文件 #include 定义函数 void _exit(int status); 函数说明 _exit()用来立刻结束目前进程的执行,并把参数status返回给父进程,并关闭未关闭的文件。此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。 返回值 这两个具体的区别参考http://www.linuxidc.com/Linux/2011-02/32125p4.htm