【Linux】创建进程,进程终止,进程替换

创建进程 -- fork()的原理

首先要问几个问题:

1、为什么给子进程返回0,给父进程返回子进程的 pid ?

2、如何理解 fork() 有两个返回值?

接下来通过了解 fork() 原理解答:

回答第一个问题,因为父进程需要区分子进程,而子进程只有一个父亲且不管理父进程,所以不需要返回。

而第二个问题需要从 fork() 内部分析,首先 fork() 是一个函数,返回值是 pid_t 类型,当进入 fork() 时,会先进行拷贝父进程,拷贝父进程的 PCB ,拷贝 task_struct、mm_struct、页表、文件等等。

在子进程拷贝和 return 返回值的区间,会进行分流,执行两个进程,就相当于一个 fork() 分成了两个 return,所以才会有两个返回值。所以说两个返回值是两个分流再执行 pid 导致的,而不是一个函数返回两个值的意思。

内核:分配新的内存块和内核数据结构给子进程,拷贝父进程的内容,添加子进程到系统的进程列表中,fork 返回。

为何要写时拷贝?

        1、进程具有独立性。2、子进程不一定会使用父进程所有的数据,做到按需分配(你都不一定该我给你分什么),延时分配(你立刻调用吗?轮到再给你)

综上可以看出,物理内存空间的分配由 OS 管理(内存管理),不允许进程调度 —— 解耦。

代码会写时拷贝吗?

不是完全不会,进程替换就是。

进程终止

查看退出码:

echo $?  // 查看的是最近一次,包括指令

        退出码有 0 和 非0:0 代表成功只有一种情况,非0 代表失败,有很多种情况。

查看错误码信息:

strerror(n)  // n 代表退出码

        每种退出码都有对应的字符串含义,任务失败的原因。

exit(n)  // n 是进程退出码

        return 是返回 main 函数,不是终止,而有了 exit(),就不仅限于 main 函数了,在任何地方都可以终止。

        exit()  和 _exit() 的功能一样:

exit() 在进程退出时,会刷新缓冲区;
_exit() 比较暴力,不会刷新缓冲区;
exit() 会释放进程曾经占用的资源,而 _exit() 直接终止。

问:进程异常退出了,退出码还有意义吗?

答:没有意义!,因为异常行为没走到 return 就退出,所以 return 的值没有意义。

进程等待

        进程等待:父进程回收子进程资源,获取子进程退出信息

进程等待的方法:

1、wait 方法:

#include <sys/wait.h>
#include <sys/types.h>

pid_t wait(int *status)

        正常返回 id,错误返回 -1。不关心 status 可以加 NULL。

阻塞等待:

        在子进程运行期间,父进程 wait 时,在等待子进程退出。

2、waitpid 方法

        waitpid 有阻塞等待和非阻塞等待,用 options 代表,0代表阻塞等待。

pid_t waitpid(pid_t pid, int *status, int options)

        waitpid 可以等待指定的一个进程,wait 等待任意一个进程。

问:进程等待成功是否意味着子进程运行成功?

答:不是的,所以用 status 获取信息。

         该图是 status 所代表的含义,status 用 4 个字节,也就是 32 个比特位中的低 16 位存储 status。

        次 8 位代表进程退出码,低 7 位,代表进程退出时的退出信号,高 16 位不关心。正常组织用次 8,异常终止用低 7。如何接收呢?

int status = 0;
//pid_t i = wait(&status);
pid_t ret = waitpid(id, &status, 0);
if (ret >= 0){
    printf("parent running! pid : %d\n",getpid());
    printf("child exit code : %d\n", (status >> 8)&0xFF);
    printf("child get signal : %d\n", status & 0x7F);
}

        如果一个进程被异常中止,那么它的退出码是没有意义的,怎么看呢?如果有 signal 值不是 0,那么 code 就一定没意义。

        为了方便,可以用另外一种方法:

if (WIFEXITED(status)){
    printf("%d\n", WEXITSTATUS(status));
}

WIFEXITED(status):检测进程是否异常中止,本质是检查信号低 7 位比特位是否为 0。

WEXITSTATUS(status):判断后若非0,获取进程退出码。

非阻塞等待:WNOHANG

pid_t ret = waitpid(id, &status, WNOHANG);

        若 pid 指定的子进程没有结束,则 waitpid() 返回 0,不等待,若正常结束,返回子进程的 pid。

        可以用 while 进行非阻塞接口的轮循方案:if 返回值等于 0:说明子进程还在运行状态,等待成功;else if 大于 0:返回 pid 等待完成; else 小于 0:等待失败。

进程替换

进程替换:让子进程执行新的进程

exec 的 6 类函数:

#include <unistd.h>

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 execve(const char *path, char *const argv[], char *const envp[]);

const char *path 是命令行参数地址,const char *arg 是执行对象。

例如: execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL)

        进程替换没有进行进程创建,但是调用后不再返回,也就说,exec 后的代码以及被替换。如果失败了,就不会被替换,程序后序不会受到影响。路径、传参会导致代码替换失败。

        当 fork() 创建子进程后,子进程想要替换成新的程序,用 exec 函数,其实是完全替换掉原来的数据和代码,在替换之前会发生写时拷贝!!!

        exec 系列的函数,根本不需要判断返回值,返回就是失败了。

exec开头:

“ l ” :list,表示参数采用列表,一个一个传;

execl("/usr/bin/ls", "ls", "-a", "-i", "-o", NULL);

“ v ”:vector,表示给个数组,不要一个一个给;

char* myarg[] = {"ls", "-a", "-i", "-o", NULL};
execv("/usr/bin/ls", myarg);

“ p ” :path,告诉执行标准就行,不要带路径,会字节到环境变量列表中找;

execlp("ls", "ls", "-a", "-i", "-o", NULL);

“ e ” :自己组装环境变量,自己的环境变量是覆盖式,有系统无你,有你无系统;

char* myenvp[] = {"MYVAL=YOU CAN SEE ME"};
execle("/usr/bin/ls", "ls", myenvp);

前五个函数的底层都是调用 execve() 函数。 

学会了这几个函数,就可以自己写一个小的 shell 外壳了。

在系统互动及用户登录时,某些软件会自动调用 bash 程序变成进程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值