Linux系统编程---进程相关接口介绍
简述
在日常的开发中,接触的较多可能是多线程的实现。当要实现并行处理时,Linux提供开辟线程和子进程的方式去实现,但从开销上来看,线程是要优于子进程的开辟,但在一些情况下,开辟子进程也是我们去实现功能的首选,
相关接口
1、fork()函数
#include<unistd.h>
pid_t fork(void)
//返回值:子进程返回0,父进程返回子进程ID;出错:返回-1
fork()后,会开辟一个子进程,通过下面的例子,我们可以发现通过对进程描述符的判断,我们可以让子进程实现对应的功能代码。那么子进程在资源分配上与线程有什么不同?
-
资源分配
子进程会与父进程之间共享数据段和代码段,所以父进程中创建的一些文件句柄等系统资源,在子进程中都会被“复制”----共享。共享让资源的使用看起来方便,但在实际开发中,子进程继承父进程的资源时,需要小心去处理(对继承的资源可以选择将其关闭或重新开辟),因为父子进程的相关资源共享使用可能会出现资源竞争的情况。 -
资源调度
Linux为父子进程之间的资源使用----写时复制的策略,那么什么是写时复制呢?写时复制是操作系统中对系统资源的一种管理方式。为尽可能的节约资源,当进程之间有共享资源,内核不会将这些资源直接拷贝到这些进程的地址空间中。如果只是读操作,则采取映射的方式去实现,当进程需要对内核进行一个写操作时,内核才会将相关的资源的副本拷贝至对应进程地址空间。
-
进程管理
父进程使用fork()后创建的进程是其的子进程,父子进程的生命周期并不是独立的,这篇博文对父子进程之间的生命周期关系就有很好的描述----linux父子进程问题,所以利用fork()去协助父进程处理相关任务时,就一定要注意资源问题和生命周期的处理。
#include<unistd.h>
#include<iostream>
#include<errno.h>
int main(){
pid_t pid;
pid = fork();
int i=0;
if(pid<0) {
std::cerr<<"fork erro"<<std::endl;
exit(1);
}
//子进程
if(pid == 0){ /*业务逻辑*/}
else{ /*业务逻辑*/}
return 0;
}
2、exit()函数
Linux中提供了一个exit函数,其用处为执行一个进程的退出,在这之前,我们就可以先来了解进程退出的几种状态
正常终止情况:
- 正常return
- 调用exit
- 调用_exit
- 进程中的最后一个线程中执行return退出
- 进程中最后一个线程调用pthread_exit
异常状态:
1.调用abort
2.当进程接受到某些信号,终止 - 最后一个线程收到"取消"信号—取消已延迟发送,线程到时终止。
了解进程退出状态的原因在于,当父进程开辟一个子进程后,他需要知道子进程的一个状态,即时退出,也应即时的告知父进程,这样可以避免当子进程提早结束,父进程不清楚子进程执行情况,从而无法对相关资源进程进行处理,使得子进程成为"僵死进程"(生命周期结束,但资源未释放)。
exit提供了一个参数,在子进程调用exit(num)退出,内核会将这个num传递给父进程。
3、wait()和waitpid
#include<sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
//成功:返回进程id,出错:返回0
- wait会阻塞,直到子进程结束,如果子进程已经结束,那么就会直接进行返回。
- waitpid相对与wait则提供了更多的选择
- pid:制定的pid对象
- option:为等待提供选项
- statloc:存放子进程的退出信息
#include<iostream>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
int main(){
pid_t pid;
int status;
if((pid = fork())<0)
std::cerr<<"fork1 erro:"<<std::endl;
else if(pid==0){
std::cout<<"I am Child1"<<std::endl;
exit(1);
}
else{
wait(&status);
std::cout<<"child exit:"<<status<<std::endl;
}
return 0;
}
exit(1)时
wlm@wlm:~/code/example/Linux$ ./exit
I am Child1
child exit:512
exit(2)时
wlm@wlm:~/code/example/Linux$ ./exit
I am Child1
child exit:512
从上面的例子我们可以看出,exit的退出码,是可以通过wait来捕捉的,(关于退出码的解读,我这里也没搞弄很明白,若有了解的网友可评论留言-在书中,说了wait及waitpid会获得的终止状态的宏,我的疑惑在于:exit的返回的终止码可以自定义指定,那么内核如何去将这些宏与自定义的终止码去对应?还是说,exit的退出码是有规定的?)
对于waitpid的pid
pid参数 | |
---|---|
pid==-1 | 等待任一子进程,此种情况下,waitpid和wait等效 |
pid>0 | 等待进程ID与pid相等的子进程 |
pid==0 | 等待调用进程组内的任一进程退出 |
pid<-1 | 等待组id等于pid绝对值的任一子进程 |
4、waitid()函数
#include<sys/wait.h>
int waitid(idtype_t idtype,id_t id,siginfo_t *info,int option);
//成功返回0,失败返回-1
相比于之前的waitid函数,使用waitid的灵活性更高,关于idtype和option的设定可以参见上文的链接,另外waitid还提供了一个siginfo_t类型变量,这个变量可以获取子线程的信号相关信息。
5、exec()函数
链接中对exec进行了详细的描述----exec函数详解
前面使用fork可以开辟一块空间执行子进程代码,但实并不是所有情况fork都能满足,当需要使用其他应用程序,我们可以通过fork先向系统申请资源空间,然后在新开辟的空间中,使用exec来运行一个新的程序,从而实现多进程。