一:进程创建
fork()函数创建新进程
#include <unistd.h> pid_t fork(void); 返回值:自进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
1.为新进程分配一个唯一的进程标识符(PID)。复制父进程的相关信息,创建一个新的进程控制块(PCB)。指向同一块内容
2.内核不会立即复制父进程的整个地址空间,而是标记这些页面为只读,当父进程或子进程尝试写入时会触发系统错误,再由系统判断是野指针问题,还是写时拷贝,如果是写时拷贝,就新开辟空间来拷贝再修改。(为什么不直接修改?因为count++,需要原数据)
再把进行修改的进程的页表更改,并把父子进程权限恢复。(只读->可读写)
3.对父进程而言,fork返回新创建进程的PID;对子进程而言,返回0。
4.新进程加入调度队列.
二:进程终止
退出码作用:
通常,退出码为0表示进程成功执行,而非零值表示错误或异常情况。
不同的非零值可以代表不同类型的错误。(退出码也可以用自己定义的)
echo $?
查看最近一次进程退出码
char *strerror(int errnum);
用于根据错误码返回对应的错误信息字符串。
进程终止的方式
1.main函数return
2.exit()
3._exit()
void exit(int status);
函数用于终止进程,并可以返回一个状态码给操作系统。它定义在
<stdlib.h>
头文件中。#include <stdio.h> #include <stdlib.h>int main() {printf("Starting the program...\n");// 假设发生了错误if (/* some error condition */ 1) {printf("An error occurred. Exiting the program.\n");exit(1); // 以状态 1 退出}printf("Program completed successfully.\n");exit(0); // 以状态 0 正常退出 }
void _exit(int status);
是一个系统调用,用于立即终止进程,而不执行任何清理工作。它定义在
<unistd.h>
头文件中。
exit和_exit不同
1.exit,退出时会刷新缓冲区,而_exit不会刷新缓冲区.
2.exit
:适合在程序正常结束时使用,或者在需要进行资源清理时使用。_exit
:通常在子进程中使用,特别是在调用fork
后,如果子进程出现错误或者需要立即终止而不影响父进程的状态时使用。
exit
会在内部调用_exit
来完成终止进程的操作,但在此之前会执行清理工作。因此,可以认为exit
是对_exit
的封装。
三:进程等待
当我们fork创建子进程,等子进程结束后会进入僵尸状态,此时就需要父进程来回收子进程。父进程可以用wait waitpid来回收子进程获取退出信息。
pid_t wait(int *status)
父进程调用wait会阻塞,直到其任一子进程终止。参数status可以用来获取子进程的退出状态。
等待成功返回子进程pid,反之-1,并设置errno
解释:int*status是位图,看低16位,8~15位代表子进程的退出码。0~7退出信号的值。
进程退出情况:
1.代码跑完,结果正确return 0
代码跑完,结果错误return !0 返回退出码 退出信号为0
2.进程异常,(越界访问 栈溢出)系统用信号提前终止进程。 返回退出信号
进程因接收到信号而终止,它不会返回正常的退出码。
1.获取退出码
WIFEXITED(status)
来检查子进程是否正常退出,如果子进程正常退出,则可以调用
WEXITSTATUS(status)
来获得退出码。2.获取退出信息
WIFSIGNALED(status)
检查子进程是否因信号而终止,如果子进程是因为信号终止的,可以使用
WTERMSIG(status)
来获取导致终止的信号编号。
pid_t waitpid (pid_t pid, int *status, int options)
pid_t waitpid(pid_t pid, int *status, int options);
1.pid 指定等待子进程的pid,为-1等待任意子进程。为 0,则等待与调用进程相同组的任何子进程。2.status 输出型参数,带回子进程退出信息
3.options参数可以设定为 0,或者使用一些特定的选项来控制等待行为。
可以while循环重复调用直到子进程结束
WNOHANG
:
- 如果没有子进程结束,
waitpid
会立即返回,而不是阻塞父进程。- 返回值:
- 若有子进程结束,返回该子进程的 PID。
- 若没有子进程结束,返回 0。
返回值:
1.成功时,返回子进程的进程ID。
2.子进程没有结束,返回0。
3.出错时,返回 -1,且设置
errno
。
四:进程替换
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
execl
int execl(const char *path, const char *arg0, ..., (char *) NULL);
execl是系统调用,是用新程序替换当前进程的映像。
path要执行程序的路径arg0 arg1 ... 执行方法
最后以NULL结尾
eg. exel("bin/ls","ls","-l",nullptr);
1.execl是替换进程,并不是新建进程。
2.execl成功不返回 失败返回-1
execv
execv(const char *path, char *const argv[]);
和execl不同的是path后面不是可变参数列表,而是指针数组。
execle
execle(const char *path, const char *arg0, ..., NULL, char *const envp[]);
和execl一致,但可以传环境变量
execve
execve(const char *path, char *const argv[], char *const envp[]);
最底层的版本,允许传递参数和环境变量。
execlp
和execvp
execlp(const char *file, const char *arg0, ..., NULL);
execvp(const char *file, char *const argv[]);
不同的是它可以在环境变量(PATH)的默认路径下查找
eg. execlp("ls", "ls", "-l", NULL); // 在 PATH 中查找 ls
execlpe 和 execvpe
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execlpe(const char *file, const char *arg0, ..., NULL, char *const envp[]);
在execlp execvp基础上可以指定特定的环境变量envp[]环境变量数组,以 NULL 结尾。如果传递 NULL,则使用当前进程的环境变量。
putenv 新增环境变量
#include <stdlib.h>int putenv(char *string);
- 成功时返回 0。
- 失败时返回 -1,并设置 errno。