嵌入式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