fork函数:创建一个新进程
#include
pid_t fork(void);
返回值:
注意:不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子各需要返回一个。fork函数是一个系统调用函数。
#include
#include
//获得当前进程的id
pid_t getpid(void);
//获得父进程的id
pid_t getppid(void);
举个栗子:
#include
#include
int main()
{
int x=1;
pid_t pid=fork();
if(pid<0)
{
perror("fork fail");
return 0;
}
else if(pid==0)
{
//Child
x++;
printf("I am child process,id is %d,my father id is %d,x=%d\n",getpid(),getppid(),x);
}
else if(pid>0)
{
x--;
printf("I am father process,id is %d,my father id is %d,x=%d\n",getpid(),getppid(),x);
}
//睡眠一下
while(1)
{
sleep(1);
}
return 0;
}
fork()函数创建子进程,子进程得到与父进程虚拟地址空间相同的一份副本,包括代码段、数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本。当fork函数在第六行返回时,本地变量x在父进程和子进程中都为1,然而,父进程和子进程都是独立的进程,都有自己的私有地址空间,后面父进程和子进程对x做的操作都是独立的,因此运行结果在父进程中是0,子进程中是1。
#include
#include
pid_t vfork(void);
vfork()创建出来的子进程拷贝部分父进程的PCB,子进程和父进程共同用一个虚拟地址空间,这样就会出现调用栈混乱的问题,vfork的解决方案:先让子进程执行,父进程等待子进程执行完毕之后再执行。
一个进程在终止时会关闭所有的文件描述符,释放用户空间分配的内存,但它的PCB还保留着,内核在其中保留了一些信息:
这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
在shell中用 echo $? 获取进程的退出码,因为shell是它的父进程,当它终止时 shell调用wait或waitpid得到它的退出状态。
从main函数中,return退出
执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数。
调用exit:库函数
#include
void exit(int status);
_exit:系统调用
#include
void _exit(int status);
exit最后也会调用_exit,但是在调用之前还做其他工作:
执行用户通过atexit或者on_exit定义的清理函数
int atexit(void(*funcation)(void));
//参数一个函数指针,可以接受没有返回值,没有参数的函数地址
//作用:调用atexit,将参数传入的函数地址告诉内核。当程序需要退出的时候,才调用传入的地址,这样的函数叫回调函数。
关闭所有打开的流,所有的缓存数据均被写入
调用_exit
刷新缓冲区:
缓冲区是C库维护的,并不是内核维护的,如果直接调用_exit函数,直接执行了内核代码,不会刷新缓冲区。
ctrl+c,信号终止
进程等待就是为了防止僵尸进程的产生。
作用:
#include
#include
pid_t wait(int* status);
status是一个传出参数,供调用者获取子进程的退出状态
返回值
阻塞:当调用函数需要等待一定条件成熟的时候,才可以返回;条件一直不成熟,则一直等待
调用wait会导致父进程陷入阻塞状态,直到有一个子进程退出,则执行wait的逻辑之后退出
作用同wait,但可以指定pid进程清理,可以不阻塞
#include
#include
pid_ t waitpid(pid_t pid, int *status, int options);
当进程调用一种exec函数时,该进程的用户空间的代码段和数据段完全被新程序替换,从新程序的启动历程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
通过进程PCB当中的内存指针,找到进程虚拟地址空间当中的数据段和代码段,通过页表映射将数据段和代码段映射到新的程序的物理内存上,直白一点,使用新的程序将之前进程的数据段和代码段进行更新。
头文件:#include
返回值:只有失败的时候才有返回值,返回-1
举个栗子:
#include
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin",
"TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
exec函数簇都是库函数,最终都是调用函数int execve(const char* filename, const char* argv[], const char* envp[])