进程创建

通过调用进程创建一个新的进程
调用进程称为父进程, 新的进程称为子进程

两个接口

pid_t fork(void);
子进程通过复制父进程的pcb创建, 代码共享, 数据独有
子进程并非从头开始运行, 而是从创建成功后的下一刻开始运行
父子进程谁先运行不一定, 取决于CPU算法
返回值:
	父进程: 返回值大于0, 返回的是子进程的pid
	子进程: 返回值等于0
	返回值小于0出错
	
pid_t vfork(void);
创建子进程, 阻塞父进程
子进程先运行, 父进程等到子进程退出或者程序替换后才会运行
父子进程公用虚拟地址空间, 同时运行会造成调用栈混乱, 因此阻塞父进程
子进程退出后, 如果释放资源会导致父进程陷入混乱或错误
vfork用处: 为解决早期fork创建子进程拷贝数据的缺点, 被fork的写时拷贝技术淘汰

两个接口都是通过在底层调用clone()函数实现的

写时拷贝技术
子进程对数据进行修改, 就会重新为其申请一块空间并更新页表, 节省资源, 提高子进程创建效率, 保证数据独有, 保证进程间的独立性

代码示例
fork

#include <stdio.h>
#include <unistd.h>

int main(){
    pid_t pid = fork();
    if(pid < 0){
        perror("fork error");
        return -1;
    }
    else if(pid == 0){
        printf("child process! pid: %d\n", getpid());
    }
    else{
        sleep(1);
        printf("parent process! pid: %d\n", getpid());
    }
    return 0;
}

运行结果

child process! pid: 21740
parent process! pid: 21739   

vfork

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    pid_t pid = vfork();
    if(pid < 0){
        perror("vfork error");
        return -1;
    }
    else if(pid == 0){
        sleep(5);
        printf("child process! pid: %d\n", getpid());
        exit(0);
    }
    else{
        printf("parent process! pid: %d\n", getpid());
    }
    return 0;
}

运行结果

child process! pid: 21242
parent process! pid: 21241

进程终止

进程退出场景
① 代码运行完毕, 结果正确
② 代码运行完毕, 结果不正确
③ 代码异常终止

进程常见退出方法
正常退出
① 从main中return退出
return退出是一种更常见的退出进程方法, 执行return n等同于执行exit(n), 因为调用main的运行时函数会将main的返回值当做exit的参数
② 调用exit()
exit()是库函数, 相当于是对_exit()系统调用接口的封装, 在调用_exit()之前还做了其他工作: 执行用户通过atexit或on_exit定义的清理函数, 关闭所有打开的流, 所有的缓存数据均被写入, 调用_exit()
③ _exit()
系统调用接口
异常退出
Ctrl + c, 信号终止

三者的区别
main函数中return和exit()会刷新缓冲区, 然后释放资源
_exit()会直接释放资源

exit()和_exit()图示
Linux_进程控制

进程等待

为什么要进行进程等待?
我们知道, 子进程退出, 父进程如果不管不顾, 就可能造成僵尸进程的问题, 进而造成内存泄漏
另外, 子进程一旦成为僵尸进程, 就会很难处理, kill -9强杀也没用
最后, 父进程派给子进程的任务完成的如何, 我们需要知道, 如: 子进程运行完成, 结果对还是不对, 或者是否正常退出
父进程通过进程等待的方式, 回收子进程资源, 获取子进程退出信息

如何进行进程等待 – 两个接口

pid_t wait(int *status);
wait()接口是一个阻塞函数
功能是等待任意一个子进程的退出, 如果子进程没有退出, 则一直等待, 直到子进程退出
status用于获取子进程的退出返回值
返回值: 返回子进程的pid, 出错返回-1

pid_t waitpid(pid_t pid, int *status, int options);
等待指定pid的子进程, pid = -1等待任意子进程, pid > 0等待指定子进程
status: 用于获取返回值
options: 选项, WNOHANG将waitpid()设置为非阻塞, 需要轮询判断
阻塞和非阻塞: 完成功能的时候不具备完成条件, 区别就是是否立即返回, 立即返回为
非阻塞, 不返回一直等待为非阻塞
返回值: 小于0, 出错, ==0目前没有子进程退出, >0退出的子进程的pid

注意status: wait()和waitpid()都有一个status参数, 该参数是一个输出型参数, 
由操作系统填充. 如果传递NULL, 表示不关心子进程的退出状态信息, 否则
操作系统会根据该参数, 将子进程的退出信息反馈给父进程. status不能简单
的当做整型来看待, 可以当做位图来看. status是一个32位int型变量, 使用其中低16位
进程的返回值为8位, 放在高8位上, 低8位最高位是core dump标志(是否要进行核心转储 -- 
保存异常退出是程序的异常信息), 剩下的7位是异常退出原因(异常信号的信号值)
如果程序异常退出, 低7位保存异常信号值 > 0, 否则低7位为0; 判断低7位是否为0即
判断程序是否是异常退出, 有封装好的两个宏: WIFEXITED(status)为真, 表示子进程
正常终止; WEXITSTATUS(status)提取子进程的退出码

Linux_进程控制

代码演示
wait()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    pid_t pid = fork();
    if(pid < 0){
        perror("fork error");
        return -1;
    }
    else if(pid == 0){
        sleep(5);
        int i = 0;
        for(i = 0; i < 5; ++i){
            printf("child process!\n");
            sleep(1);
        }
        exit(0);
    }
    wait();
    while(1){
        printf("parent process!\n");
        sleep(1);
    }
    return 0;
}

输出结果

child process!
child process!
child process!
child process!
child process!
parent process!
parent process!
parent process!
parent process!

waitpid()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    pid_t pid = fork();
    if(pid < 0){
        perror("fork error");
        return -1;
    }
    else if(pid == 0){
        sleep(5);
        exit(0);
    }
    int status;
    while(waitpid(pid, &status, WNOHANG) == 0){
        printf("no exit! waiting!\n");
        sleep(1);
    }
    while(1){
        printf("parent process\n");
        sleep(1);
    }
    return 0;
}

运行结果

no exit! waiting!
no exit! waiting!
no exit! waiting!
no exit! waiting!
no exit! waiting!
parent process
parent process
parent process
parent process

程序替换

为什么要进行程序替换?
为了让子进程完成其他功能

替换原理?
替换进程所运行的程序, 重新初始化虚拟地址空间, 更新页表信息

如何进行程序替换 – 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[]);
函数名 参数格式 是否带路径 是否使用当前环境变量
execl 列表 不是
execlp 列表
execle 列表 不是 不是, 须自己组装环境变量
execv 数组 不是
execvp 数组
execve 数组 不是 不是, 须自己组装变量

总结如下:
execl和execv的区别: 参数的赋予是以指针数组海域还是以不定参形式赋予
有p和没有p的区别: 第一个参数是否需要给路径
有e和没有e的区别: 环境变量是否由用户自己设置
上面几个库函数都是通过调用execve()来实现的

代码演示

#include <stdio.h>
#include <unistd.h>

int main(){
    pid_t pid = fork();
    if(pid < 0){
        perror("fork error");
        return -1;
    }
    else if(pid == 0){
        printf("child process!\n");
        execlp("ls", "ls", "-l", NULL);
    }
    else{
        sleep(1);
        printf("parent process!\n");
    }
    return 0;
}

结果如下:

child process!
总用量 80
-rwxrwxr-x 1 sss sss 8648 4月   8 20:28 a.out
-rw-rw-r-- 1 sss sss 1942 3月  17 19:35 binary_search.c
-rw-rw-r-- 1 sss sss  184 4月   8 12:57 env.c
-rw-rw-r-- 1 sss sss  340 4月   8 20:28 execvl.c
-rw-rw-r-- 1 sss sss  336 4月   8 20:03 fork.c
-rw-rw-r-- 1 sss sss  109 3月  19 20:24 hehe.c
-rw-rw-r-- 1 sss sss 1254 3月   9 15:22 insert_sort.c
-rw-rw-r-- 1 sss sss   58 3月  17 19:44 makefile
-rw-rw-r-- 1 sss sss 1265 3月  21 13:27 merge_two_list.c
-rw-rw-r-- 1 sss sss 1384 3月  17 10:57 quick_sort.c
-rw-rw-r-- 1 sss sss  663 3月  21 13:17 remove_element.c
-rw-rw-r-- 1 sss sss  430 3月  21 13:06 reverse_list.c
-rw-rw-r-- 1 sss sss 1339 3月   9 15:15 select_sort.c
-rw-rw-r-- 1 sss sss 1511 3月   9 15:29 shell_sort.c
-rw-rw-r-- 1 sss sss 1920 3月  28 15:55 sort.c
-rw-rw-r-- 1 sss sss  375 4月   8 16:09 vfork.c
-rw-rw-r-- 1 sss sss  458 4月   8 19:22 wait.c
-rw-rw-r-- 1 sss sss  449 4月   8 19:33 waitpid.c
parent process!

相关文章:

猜你喜欢
  • 2021-10-09
  • 2021-08-25
  • 2021-08-31
  • 2021-08-12
  • 2021-10-30
相关资源
相似解决方案