【linux】进程控制
因为进程具有独立性,虽然父子进程代码是共享的,但是父进程无法直接拿到子进程的状态数据,子进程的退出信息在内核数据结构对象PCB中存放,父进程只能通过系统调用来拿取子进程的状态数据。return是一种更常见的退出进程的方法,执行return n等同于执行exit(n),因为调用main的运行时启动代码会将main的返回值作为参数传递给exit。当一个进程调用fork之后,就有两个二进制代码相同的进程
一、进程创建
1、fork函数初识
fork从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。
#include<unistd.h>
pid_t fork(void);
进程调用fork,当控制转移到内核中的fork代码后,内核做:
1)分配新的内存块和内核数据结构给子进程
2)将父进程部分数据结构内容拷贝至子进程
3)添加子进程到系统进程列表当中
4)fork返回,开始调度器调度

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
printf("begin: 我是一个进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(5);
pid_t id = fork();
if(id == 0)
{
// 子进程
while(1)
{
printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
else if(id > 0)
{
//父进程
while(1)
{
printf("我是父进程,pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
//error
}
return 0;
}
运行结果:

fork之前,父进程独立执行,fork之后,父子两个执行流分别执行。
注意:fork之后,谁先执行完全由调度器决定。
2、fork函数返回值
1)子进程返回0
2)父进程返回的是子进程的pid
3)出错返回-1
3、写时拷贝
二、进程终止
1、进程退出场景
1)代码运行完毕,结果正确
2)代码运行完毕,结果不正确
3)代码异常终止
2、进程常见退出方法
正常终止:
可以通过echo $?查看进程退出码,注意,$?保存的是最近一次进程退出时的退出码。
退出码为0代表进程运行结果正确。
1)从main返回
main函数的返回值本质表示:进程运行完成时是否是正确的结果,如果不是,可以用不同的数字表示不同的出错原因。
2)调用exit
3) 系统调用_exit
exit和_exit在任意地方被调用,都表示调用进程直接退出,而return 只表示当前函数返回。
exit和_exit的联系:

exit最后也会调用_exit,在调用_exit之前,还进行了刷新缓冲区等工作。
return是一种更常见的退出进程的方法,执行return n等同于执行exit(n),因为调用main的运行时启动代码会将main的返回值作为参数传递给exit。
异常退出:
ctrl + c,信号终止
三、进程等待
1、进程等待是什么?
通过系统调用wait/waitpid,对子进程进行状态检测与回收的功能。
2.为什么要进行进程等待(进程等待的必要性)?
我们知道,子进程一旦退出,父进程如果不管不顾,就可能造成僵尸进程,进而造成内存泄漏。
僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄露的问题(必须要解决)。
我们要通过进程等待,获得子进程的退出情况。
3、进程等待的方法
1)wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:成功,返回被等待进程的pid,失败,返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。
2)waitpid方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int* status, int options);
返回值:
当正常返回的时候,waitpid返回收集到的子进程的pid;
如果设置了选项WNOHANG(非阻塞),而调用中waitpid发现没有已退出的子进程可收集(即等待的条件还没有就绪),则返回0;
如果调用中出错,则返回-1,这时errno(错误码)会被设置成相应的值以指示错误所在;
参数:
pid:
pid = -1,等待任一个子进程,与wait等效。
pid > 0,等待其进程id与pid相等的子进程。
status(输出型参数):
四、进程程序替换
1、替换原理

2、替换函数
以exec开头的函数统称exec函数。
库函数:
int execl(const char *path, const char *arg, ...);
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 execvpe(const char *file, char *const argv[], char *const envp[]);
系统调用:
int execve(const char *filename, char *const argv[], char *const envp[]);
上面六个库函数最终都会调用execve。
注意:
1)程序替换成功之后,exec函数后续的代码不会被执行,只有替换失败才可能执行后续代码。
2)exec函数,只有失败返回值,没有成功返回值。
命名理解:
l(list) : 表示参数采用列表。
v(vector) : 参数用数组。
p(path) : 有p自动搜索环境变量PATH,只写程序名就行;没有p需要写绝对路径或相对路径。
e(env) : 表示自己维护环境变量。

代码示例:
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
execlp("ls", "ls", "-a", "-l", NULL);
如果我们想给子进程传递环境变量,该如何传递?
1)新增环境变量
父进程的地址空间中直接putenv。
putenv("KEY=VALUE");
使用第三方全局变量environ。
execle("./otherExe", "otherExe", "-a", "-w", "-v", NULL, environ);
2)彻底替换(覆盖而不是追加)
char *const myenv[] = {
"MYVAL=1111",
"MYPATH=/usr/bin/XXX",
NULL//必须以NULL结尾
};
execle("./otherExe", "otherExe", "-a", "-w", "-v", NULL, myenv);
五、实现一个简单的shell
实现代码:

运行示例:

更多推荐



所有评论(0)