进程创建:
fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:
用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
参数:
无
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为整型。
失败:返回-1。
失败的两个主要原因是:
1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
2)系统内存不足,这时 errno 的值被设置为 ENOMEM
一个进程调用 fork() 函数后,系统先给新的进程分配资源。fork() 函数被调用一次,但返回两次。两次返回的区别是:子进程的返回值是 0,而父进程的返回值则是新子进程的进程 ID。通过返回值来区分父子进程。
写实拷贝
fork之后,通常父子代码共享,当父子各个进程不对数据写入时,数据也是共享的,当任意一方试图写入,便会以写实拷贝的方式各自一份副本。
进程退出:
进程退出时,可以通过在终端执行echo $?来获取最近一个进程退出的退出码。
_exit函数
#include<unistd.h>
void _exit(int status);
返回值:
无
参数:
status定义了进程的终止状态,父进程通过wait来获取该值
虽然status是int,但是仅有低八位可以被父进程所用,所以_exit(-1)时,在终端执行$?发现返回值
是255。
exit函数
#include<unistd.h>
void exit(int status);
_exit()属于标准库函数,exit()属于系统调用函数。
return函数
eturn是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
进程等待:
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
子进程退出,父进程不回收资源, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
wait
#incldue<sys/types.h>
#include<sys/wait.h>
pit_t wait(int*status);
功能:
等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数:
status : 进程退出时的状态信息。
返回值:
成功:已经结束子进程的进程号
失败: -1
waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则 返回该子进程的ID。
我们可以通过参数options设置非阻塞,这样就不会占用父进程的所有精力,可以再轮训期间执行其他代码。
样例:设置非阻塞。
int main()
{
pid_t id=fork();
if(0==id) //子进程 {
int cnt=5;
while(cnt) {
printf("我是子进程:%d, 父进程:%d, cnt:%d\n",getpid(),getppid(),cnt--);
sleep(3);
}
exit(10);
}
int status=0; //不被整体使用,有自己的位图结构
londTask();
while(1) //循环检测 {
pid_t ret=waitpid(id ,&status,WNOHANG); //WBOHANG: 非阻塞 ->子进程没有退出,父进程检查的时候,立即返回
if(ret==0){
//waitpid调用成功 && 子进程没有退出
//子进程没有退出,我的waitpid没有等待失败,仅仅是检测到了子进程没有退出
printf("子进程在跑...\n");
...//设置完非阻塞后,在此执行其他代码
}
else if(ret>0) {
//1.waitpid 调用成功 && 子进程退出了
//通过status获取退出信息
printf("wait success, exit code: %d, sig:%d\n",(status>>8)&0xff,status & 0x7f);
break;
}
else {
//waitpid调用失败
printf("waitpid call failed\n");
break;
}
sleep(1);
}
return 0;
}
获取子进程status:
子进程statuswait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)
不回收子进程的危害:
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。这样就会导致一个问题,如果进程不调用wait() 或 waitpid() 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
进程程序替换:
fork后子进程默认执行父进程代码的一部分想要子进程不行执行父进程的代码,想要执行全新地程序就可以使用exec函数将磁盘上的代码和数据加载到内存,替换掉当前进程的代码数据。这样子进程就可以通过虚拟内存+页表映射来执行新的程序。
exec函数族
Linux 中,并不存在 exec() 函数,exec 指的是一组函数,一共有 6 个:
#include <unistd.h>
extern char **environ; //
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execve(const char *filename, char *const argv[], char *const envp[]);
(1)其中只有 execve() 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
(2)exec 函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
(3)调用exec并不创建新进程,所以调用exec前后该进程的id并不会改变
exec函数族命名理解:
l(list) | 参数地址列表,以空指针结尾 |
---|---|
v(vector) | 存有各参数地址的指针数组的地址 |
p(path) | 按 PATH 环境变量指定的目录搜索可执行文件 |
e(environment) | 存有环境变量字符串地址的指针数组的地址 |
函数名 | 参数格式 | 使用路径/文件名 | 是否使用当前环境变量 |
execl | 列表 | 路径名 | 是 |
execlp | 列表 | 文件名 | 是 |
execle | 列表 | 路径名 | 不是,自己组装环境变量 |
execv | 数组 | 路径名 | 是 |
execvp | 数组 | 文件名 | 是 |
execve | 数组 | 路径名 | 不是,自己组装环境变量 |
execvpe | 数组 | 文件名 | 不是,自己组装环境变量 |
举例:
#include <unistd.h>
int main()
{
char *const argv[] = {"ls", "-l", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL}; //自己定义的环境变量
extern char **environ;//系统默认环境变量
execl("/bin/ls", "=ls", "-l", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ls", "ls", "-l", NULL);
// 带e的,需要带环境变量
execle("ls", "ls", "-l", NULL, envp);
execv("/bin/ls", argv);//也可以使用environ环境变量
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ls", argv);
// 带e的,//也可以使用environ环境变量
execve("ls", argv, envp);
exit(0);
}