一.进程相关操作
1.进程创建
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
2.进程等待
进程等待的必要性:
1.子进程退出, 如果父进程不管, 会成为僵尸进程, 造成资源泄漏
2.进程一旦变成僵尸进程, 不能被 kill
3.父进程需要知道子进程运行完成的返回
4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法: wait() waitpid()
wait() 和 waitpid() 中传出参数 status保存了进程的退出状态, 不能简单的当作整形来看待,
可以借助系统提供的宏判断终止的具体原因
1. WIFEXITED(status) != 0 进程正常退出
WEXITSTATUS(status) 获取进程退出状态
2. WIFSIGNALED(status) != 0 进程异常终止
WTERMSIG(status) 取得使进程终止的信号编号
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
功能:
1.阻塞等待子进程退出 2.回收子进程资源 3.获取子进程结束状态
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
// wait() 使用
int main(){
pid_t ret = fork(); // 创建进程
if (ret == -1){
perror("fork() error");
return 1;
} else if (ret == 0){
printf("child[%d] sleeping\n", getpid());
sleep(20);
return 3;
} else {
sleep(2);
int status;
pid_t wait_ret = wait(&status); // 父进程阻塞等待子进程
if (wait_ret == -1){
perror("wait() error");
return 1;
}
if (WIFEXITED(status)){ //子进程正常退出
printf("child[%d] eixt code: %d\n", wait_ret, WEXITSTATUS(status));
}
if (WIFSIGNALED(status)){ //子进程收到信号退出
printf("child[%d] signal code: %d\n", wait_ret, WTERMSIG(status));
}
}
return 0;
}
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:
传出参数
options:
0 : 阻塞
WNOHANG: 非阻塞
// 父进程 回收三个子进程: 1. 调用 ls -a 2.调用 add 3.执行段错误程序
int main(){
pid_t pid;
int i = 0;
for ( ; i < 3; i++){
pid = fork();
if (pid == 0){
break;
}
}
if (i == 0){
execlp("ls", "ls", "-a", NULL);
} else if (i == 1){
execl("add", "add", NULL);
} else if (i == 2){
execl("err", "err", NULL);
} else {
int status;
int ret;
int n = 3;
do{
ret = waitpid(-1, &status, WNOHANG); // 父进程调用 waitpid() 非阻塞等待三个子进程
if (ret == -1){
perror("waitpid() error\n");
return -1;
} else if (ret == 0){
sleep(1);
continue;
} else {
n--;
printf("回收子进程: %d\n", ret);
if (WIFEXITED(status)){
printf("子进程正常返回值: %d\n", WEXITSTATUS(status));
}
if (WIFSIGNALED(status)){
printf("子进程收到信号退出: %d\n", WTERMSIG(status));
}
}
}while(n > 0);
}
printf("finish\n");
return 0;
}
3.进程终止
正常退出:(可通过echo $? 查看退出码)
1._exit() 直接退出
2.exit() 执行清理函数, 刷新缓冲,关闭流, 调用_exit()
3.return n 等同于调用 exit(n)
异常退出:
cltr + c, 信号终止
所以进程执行完后最好使用exit()或return终止进程
二.程序替换(exec函数)
1.替换原理
用 fork() 创建子进程后执行和父进程相同的代码段(有可能执行不同的代码分支), 子进程可以调用一种exec函数以执行另一个程序 当进程调用一种exec函数时, 该进程的用户空间代码段和数据被新程序完全替换, 从新程序启动例程开始执行
因为调用exec不创建新进程, 只是替换了代码段, 所以进程的id并不变
2. exec函数
返回值: 这些函数调用成功, 替换代码从新程序启动代码开始执行, 不返回
调用失败返回 -1
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
// file: 要加载的程序名 函数去PATH中搜索file, 没有找到报错 常用于调用系统程序: ls, cat, cp
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
//exec函数规律:
l: list 参数使用列表
v: vector 参数使用数组
p: PATH 搜索环境变量PATH
e: env 表示自己维护的环境变量
execve是真正的系统调用,其它五个函数最终都调用 execve
使用方法:
// 执行 ls -la 命令
execlp("ls", "ls", "-la", NULL); // NULL表示参数结束 p表示PATH, 无需写全路径, 自动去PATH找
execl("/bin/ls", "ls", "-la", NULL); // 不带 p 要写替换程序的路径
char *const args[] = {"ls", "-la", NULL}; // 使用数组做参数, 要构造一个数组
execv("/bin/ls", args);
execvp("ls", args);