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

本文详细解析了嵌入式Linux中进程的相关概念与特性,包括PID、PPID、进程组ID、会话期、进程互斥、临界资源、进程同步与调度算法等内容。同时,通过实例代码演示了如何获取进程ID、创建子进程以及使用vfork与exec函数族进行进程管理,最后介绍了进程等待与正常结束的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

嵌入式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<unistd.h>

              #include<sys/types.h>

定义函数:pid_t getpid(void);

函数说明:getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。

返回值:       成功:目前进程的进程识别码

                     失败:-1,出错原因存于error

 

2:获取父进程ID:

       getppid(取得父进程的进程识别码)

相关函数:fork,kill,getpid

表头文件:#include<unistd.h>

              #include<sys/types.h>

定义函数:pid_t getppid(void);

函数说明:getppid()用来取得目前进程的父进程识别码。

返回值:       成功:目前进程的父进程识别码

                     失败:-1,出错原因存于error

例:getpid.c

#include <unistd.h>

#include <sys/types.h>

#include <stdio.h>

#include <stdlib.h>

 

int main(void)

{

       printf("Pid =%d\n",getpid());

       printf("Ppid =%d\n",getppid());

       return 0;

}

 

运行结果


 

3:创建子进程 fork

相关函数:fork,kill,getpid

表头文件:#include<unistd.h>

              #include<sys/types.h>

定义函数:pid_t fork(void);

函数说明:fork()创建子进程。调用一次,返回两次

返回值:       在父进程:fork返回新创建的子进程PID

                     在子进程:fork返回0

                     错误: 返回负值

 

子进程的数据空间,堆栈空间都是从父进程拷贝的,而不是共享。

 

 

例程:fork.c

#include <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <math.h>

 

int main(void)

{

       pid_t child;

 

       child = fork();

       /*创建子进程*/

       if(child < 0)

       {

              printf("ForkError :%s\n",strerror(errno));

              exit(1);

       }

       else if(child == 0)  //子进程

              {

                     printf("iam the child: %d\n",getpid());

                     exit(0);

              }

              else //父进程

              {

                     printf("iam the futher: %d\n",getpid());

                     return(0);

              }

}

 

 

 

4:建立一个新的进程vfork()

相关函数:wait,execve

表头文件:#include<unistd.h>

定义函数: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<unistd.h>

#include<stdio.h>

 

int main(void)

{

  pid_t pid;

  int count = 0;

 

  pid = fork();

 

  count++;

  printf("count = %d\n",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<unistd.h>

#include<stdio.h>

 

int main(void)

{

  pid_t pid;

  int count = 0;

 

  pid = vfork();

 

  count++;

  printf("count = %d\n",count);

 

       //return 0;

  _exit(0);

}

结果:

 

5:exec函数族

Fork是创建新的进程,产生新的PID。

Exec启动新的程序,替换原有的进程,不会产生新的PID。

execl(执行文件)

相关函数

fork,execle,execlp,execv,execve,execvp

表头文件

#include<unistd.h>

定义函数

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<unistd.h>

定义函数: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<unistd.h>

定义函数:int execv (const char * path,char * const argv[ ]);

函数说明:execv()用来执行参数path字符串所代表的文件路径,与execl()不同的地方在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件。

返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。

 

execve(执行文件)

相关函数

fork,execl,execle,execlp,execv,execvp

表头文件

#include<unistd.h>

定义函数

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

函数说明

execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。

返回值

如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。

 


execvp(执行文件)

相关函数

fork,execl,execle,execlp,execv,execve

表头文件

#include<unistd.h>

定义函数

int execvp(const char *file ,char * const argv []);

函数说明

execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。

返回值

如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中

 

例程:

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

 

int main(void)

{

       if(fork() == 0)

              if(execl("/bin/ps","ps","-ef",NULL)< 0 )

                     perror("execlerror");

}

 

 

进程等待

wait(等待子进程中断或结束)

相关函数

waitpid,fork

表头文件

#include<sys/types.h>
#include<sys/wait.h>

定义函数

pid_t wait (int * status);

函数说明

wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则

参数

status可以设成NULL。子进程的结束状态值请参考waitpid()。

返回值

如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

 

waitpid(等待子进程中断或结束)

相关函数:wait,fork

表头文件:#include<sys/types.h>

#include<sys/wait.h>

定义函数: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 <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <stdio.h>

#include <errno.h>

#include <math.h>

 

int main(void)

{

       pid_t child;

      

       /*创建子进程*/

       child = fork();

 

       if(child == -1)

       {

              printf("ForkError:%s\n",strerror(errno));

              exit(1);

       }

       else if(child == 0) //子进程

       {

              printf("thechild process is runing\n");

              sleep(5);

              printf("i amthe child: %d\n",getpid());

              exit(0);

       }

       else

       {

              wait(NULL);  //待子进程退出时,父进程才运行

              printf("thefuther process is runing\n");

              printf("i amthe father:%d\n",getpid());

              return 0;

       }

 

}

 

结果

 

进程结束

exit(正常结束进程)

相关函数:_exit,atexit,on_exit

表头文件:#include<stdlib.h>

定义函数:void exit(int status);

函数说明:exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。

返回值

 

_

exit(结束进程执行)

相关函数

exit,wait,abort

表头文件

#include<unistd.h>

定义函数

void _exit(int status);

函数说明

_exit()用来立刻结束目前进程的执行,并把参数status返回给父进程,并关闭未关闭的文件。此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。

返回值

这两个具体的区别参考http://www.linuxidc.com/Linux/2011-02/32125p4.htm

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值