进程的创建

在进程的创建中,我们一个非常重要的函数 fork()函数,fork()函数会创建一个新的进程,为原有进程的子进程,原有就为父进程。
我们来看一下fork()函数的原型。

#include <unistd.h>
pid_t fork(void);

返回值:子进程返回0,父进程返回子进程的pid。fork()失败返回-1(linux下)

进程调用fork()函数后具体的操作

1)当进程调用fork()函数后,会将控制转移到内核中的fork()代码。
2)内核会分配新的内存和内核数据结构给子进程
3)将父进程部分数据结构内容拷贝到子进程中(写时拷贝)
4)添加子进程到系统的进程列表中
5)fork()返回,开始调度器调度
第一步:

调用fork()函数后到内核中去申请资源。当资源申请好就创建出子进程,子进程和父进程各自拥有独立的资源,然后把父进程的数据代码拷贝一份到子进程中。但是要注意的是:这里的拷贝是采用了写时拷贝。通常情况下,父子进程是共享代码的,数据是写时拷贝。
当有了子进程时,代码是怎么执行的。

当fork()完了子进程和父进程就从fork()函数开始向下执行,但是父进程先执行还是子进程先执行,这是取决去调度器。

虚拟地址空间简述

我们在写程序的时候每次取地址或者传指针等,这些地址都是操作系统为了物理内存更为合理的使用,虚拟出地址空间。通过虚拟地址空间。
1)能有效的分配和使用物理内存
2)能保护进程与进程之间的独立
3)同时一定程度上提高了运行速度
我们了解一下,虚拟地址与物理地址

fork()创建子进程通常在一个进程下需要做别的事情是时,可以创建一个子进程去做,自己来处理结果。例如客户端请求父进程接受,子进程去处理。
创建子进程势失败的原因
1)系统中子进程数太多达到上限
2)内存不足

vfork()函数

vfork()函数也是用来创建子进程,但是与fork()相比还是有一定的差别 。

  • vfork用于创建一个子进程,而子进程和父进程共享地址空间(也就是共享页表),而fork的子进程具有独立的地址空间
  • vfork是一定保证了子进程先执行,当子进程调用exec或者(_exit)之后父进程才能被执行。fork父子进程调度完全取决于调度器,在同一时刻。
  • 当子进程改变程序中数据时候,父进程也会改变。也是说明了共享地址空间
    在这里有一个我们要注意:
    在vfork函数创建的子进程中,为什么用return程序会崩溃。
    前面我们说的第一点,因为vfork是父子共用一块地址空间,也就是用一个页表,所以在子进程运行的过程中父进程是处于等待状态的,要是在子进程中return,那么也就是将main函数的栈帧结束了,那么当子进程结束后,父进程开始运行,但是这个时候main函数的栈帧都已经释放,所以父进程再运行就会崩溃。

    进程的终止

    进程的终止是一个进程的结束或者说是一个进程的退出,进程的退出可以分为两种。一种是是正常的退出,一种是异常的退出。

    正常退出

    进程的正常退出,大概有三种,严格的说只有两种。
    1)main函数的返回
    2)调用系统函数或者库函数(_exit() exit() )
    main()函数返回
    main()函数返回,这个很好理解,当main()函数执行到return 0 时候进程也正常退出。进程也就结束。就像我们写的第一个程序hello world ,当执行程序时,就会创建出一个进程,当return后,进程也就结束。
    _exit()函数
    先看函数声明:

    
    #include<unistd.h>// 系统调用
    void _exit(int status);
    // 参数status为退出码。(注意退出码为int 但是用两个字节,后面说)

    _exit()函数是系统函数,用于程序的退出,一般在进程中调用_exit()会直接结束程序,不会做进程结束前的清理工作。
    exit()函数
    函数说明:

#include<stdlib.h>
void exit(int status);

exit函数和_exit()函数功能都是让进程退出,而exit()函数在底层也是调用了_exit函数让进程退出,但是为什么要用exit函数呢?其实exit函数在调用的时候大概分为三个步骤
1)执行用户定义的清理函数
2)关闭所有打开的流,冲涮缓冲区。
3)调用_exit()函数
举个例子

int main()
{printf("123");exit(0);return 0;
}
int main()
{printf("123");_exit(0);return 0;
}

上面两个代码的运行结果是不一样的。
前一个是有输出为123,而后面的没有输出。
这就说明exit会在结束进程前做一些关闭文件,刷新缓冲区等事情。

异常退出

异常退出就是在进程在执行在某个一指令时收到了某一个信号,将程序终止,比如我们在执行过程中用的Ctrl+c这是表示在进程执行的过程中,进程收到了一个2号信号将程序终止掉。

还有在我们写程序的时候总会出现程序奔溃的情况,那就我们拿内存访问越界来说。当我们要访问某个地址空间的时候,因为我们现代的内存空间都用的是虚拟地址空间,所以操作系统会去查页表,然后找到对应的物理内存块,这时会有个mmu地址映射检查,如果映射在自己的内存空间中,则正常访问,否则,将发出11号信号,将程序终止掉。

进程的等待

什么是进程等待?为什么要进程等待?
进程的等待,因为一个进程在为了在它结束前需要等其它(子进程)进程完成一些事情,但是事情又没有完成,所以要进入等待状态,要等其它(子进程)完成,它才能继续执行或者结束。
为什么要进程等待,这个就比如,一个进程在创建出一个子进程后为了不让子进程变成僵尸进程,所以要进行进程的等待,等待接受子进程返回的结果。

那么进程中等待是怎么实现的?

wait方法

首先我们先看看wait方法

#include<sys/wait.h>
pid_t wait(int* status);

返回值:成功返回等待进程的pid,失败则返回-1
参数为输出型参数,如果不需要知道则可以传入NULL

wait 为阻塞式等待,意思就是如果一个子进程在没有结束前父进程都会在wait这里等着子进程的返回信息。

waitpid方法

先来看看函数的原型:

#include<sys/wait.h>
pid_t waitpid(pid_t pid, int* status, int options);

返回值

有三种情况:
1)当waitpid正常返回时,返回子进程的ID。
2)当参数中options的参数被设置为WNOHANG,而调用中waitpid发现没有已经退出的子进程可以收集,那么就返回0。
3)如果在调用中出错,那么返回-1,这是errorn会被置成相应的错误信息。

参数

pid:
pid值 = -1时,waitpid等待是任意一个子进程,与wait等待类似。
pid值 > 0 时,waitpid等待的子进程的ID(也就是进程pid)与参数pid相等的进程。

status:
参数为输出型参数。
注意:后面讲的输出型参数解析里面,参数虽然是int型,但是在用的时候只用两个字节的内容。所以为了方便使用,定义了两个宏。

WIFEXITED(status):若为正常终止的子进程返回的状态,则为真(非0),否则(0)。(主要用于判断子进程是否正常退出)解释:判断正常退出是判断低int的低15位是否全为0,而退出码是在高两个字节里面存储

WEXITSTATUS(status):·若WIFEXITED判断为非零(即子进程正常退出),那么它就可以提取正常子进程退出的退出码。

options
参数里面如果是0,那么就想wait一样是阻塞式等待,如果是WNOHANG,那么就是非阻塞式等待。
阻塞式等待:就是当子进程创建出来后,父进程在执行wait或者waitpid (-1,status,0),这时候如果子进程还在执行没有退出,那么父进程就会进入睡眠状态不会执行其他任务,等待子进程退出。
非阻塞式等待:当子进程创建出来后,父进程需要知道子进程的结果,调用waitpid(-1,status,WNOHANG)后,那么如果父进程发现子进程孩还在执行时,父进程不会进入睡眠状态,而是去执行其他事情,每过一段时间回来看看子进程是否执行完成,如果完成回收信息,没有则继续去做自己的事情。

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main()
{pid_t id = fork();if (id > 0){   // 父进程int s = 0;do  {   int ret = waitpid(-1, &s, WNOHANG);// 判断子进程是否正常退出// 并且返回的是要处理的子进程if (WIFEXITED(s) && ret == id) {   printf("eixt coid :%d", WEXITSTATUS(s));break;}else if (ret == 0){// 程序还在执行sleep(1);printf("running\n");}else{// 等待失败perror("waitpid error\n");break;}}while(1);}else if (id == 0){// 子进程sleep(10);printf("child over");exit(123);}else{perror("fork\n");}                                                                                                                     return 0;
}

获取进程的status

关于status的参数解释:

  • 在wait(int status)和waitpid(int status) 中的status是一个int的输出型参数,由操作系统来进程填充。
  • 如果传递的是NULL,表示不关心子进程的退出状态。
  • 改参数为输出型参数,但是参数具体由操作系统填充,所以操作系统会根据改参数,将子进程的退出信息反馈给父进程。
  • 最重要的一点: status虽然是int型的参数,但是具体用,却只用低两个字节,就相当于位图。

首先我们说明为什么要低两个字节?
是因为,我们要表示正常状态,并且要表示出它的退出状态码,或者被信号所截杀的终止信号码。所以采用int只用低两位字节,一个字节表是进程是否正常退出,一个表示退出码。
我们用图来更清楚的认识一下:

我们再来理解一段代码:

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{pid_t id = fork();if (id > 0){// 父int w = 0;// 如果取成功就返回子进程的pidint ret = wait(&w);if (ret > 0 && (w & 0x7f == 0)){// 正常退出printf("子进程正常退出的退出码 = %d\n",  w>>8);}else if (ret > 0){// 异常退出printf("异常终止的信号 = %d", w & 0x7f) // 只用后15个比特位}else{perror("wait");}}else if (id == 0){// 子sleep(10); // 先睡10秒exit(0);}else{perror("fork()");}return 0;
}

通过上面的代码我们可以得出,如果正常退出,我们就可以得到退出码,如果异常退出,我们就可以得到异常退出的信号。

进程的程序替换

首先要说明的是进程的程序替换是在程序运行过程中,当执行到exec函数时候,该进程的跑去执行其它的程序,就比如说:我们在用linux时候,我们会输入一个ls来查看当前目录底下有那些内容,那么对于我们来看是一瞬间的事情,但是在输入ls时,系统创建一个进程,用来调用ls的可执行程序,那么这个就是程序的替换。

替换的原理

从原理上来看我们程序替换,而不是进程的替换,那么我们就需要用当前进程去创建出子进程来进行程序的替换,往往子进程用来调用一个程序替换的函数,从而程序的子进程跑去执行,而父进程就处于等待状态,所以进程的程序替换不会改变进程pid。
我们用图来解释一下:

进程替换中需要注意
1)进程的替换只是替换了进程的代码数据,被替换的进程的id不会改变
2)进程替换在调用exec成功后,后面的代码将不会被执行到
3)在进程替换后,堆栈会清空原来进程的数据。

了解进程替换的函数

进程的替换函数以exec开头的家族:
它们的都在头文件为:

#incldue <unistd.h>

大致可以划分为两种类型,一种是参数列表,一种是数组型。
先来看看参数列表exec函数

// path为要执行的程序的路径,arg为参数列表(注意参数列表以NULL结尾)
int execl(const char* path, const char* arg,···);// 这是自动的去环境变量中找path,file为可执行程序,arg参数列表
int execlp(const char* file, const char* arg, ···);// path为执行程序路径,arg参数列表,envp为自己需要配置环境变量
int execle(const char* path,const char* arg,···,char* const envp[]);

用数组来装参数的exec函数

// path为执行程序的路径,argv数组为命令行参数
int execv(const char* path, char* const argv[]);// file为执行程序名字,路径自动查找环境变量中的路径
int execvp(const char* file, char* const argv[]);// path为可执行程序路径,argv装有命令行参数的数组,envp为需要自己来配置的环境变量。
int execve(const char* path, char* const argv[], char* const envp[]);

实现一个简单的shell

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>// 这是从命令行获取的字符串进行切分,装入argv数组中
void Do_Split(char buf[], char* argv[])
{char* token = strtok(buf, " ");argv[0] = token;int i = 1;while (token != NULL){token = strtok(NULL, " ");argv[i++] = token;}argv[i] = NULL;
}// 这是创建出一个子进程,进行程序替换
void Do_Execute(char* argv[])
{pid_t id = fork();if (id > 0){// fatherwait(NULL);}else if (id == 0){// childexecvp(argv[0], argv);perror(argv[0]);printf("替换错误 \n");exit(1);}else{perror("fork\n");}
}int main(int argc, char* argv[], char* env[])
{(void)argc;(void)env;char buf[1024] = {};while (1){char arr[50] = {};gethostname(arr, 9);printf("[myshell@%s]$ ",arr);/* gets(buf); */scanf("%[^\n]%*c",buf);Do_Split(buf,argv);Do_Execute(argv);}return 0;
}

Linux中进程的创建、进程的终止、进程的等待、进程的程序替换相关推荐

  1. linux自解压执行程序,如何在Linux中使用shar创建自解压文件

    原标题:如何在Linux中使用shar创建自解压文件 正文 使用shar,您可以将许多文件"打包"成一个文件.如果将其发送给您的联系人,他们只需确保其可执行并运行以将其提取即可.没 ...

  2. linux 创建子进程,linux中fork同时创建多个子进程的方法(一)

    Fork同时创建多个子进程方法 第一种方法:验证通过 特点:同时创建多个子进程,每个子进程可以执行不同的任务,程序 可读性较好,便于分析,易扩展为多个子进程 int main(void) { prin ...

  3. Linux中fork()系统调用创建两个子进程

    使用系统调用fork()创建两个子进程: #include <stdio.h> #include <unistd.h>int main(){int fpid = fork(); ...

  4. linux中要怎么创建文件夹

    我是一个linux初学者,由于工作上面需要,我需要在linux中创建一个文件夹,然后自学了一点点,其实创建文件夹很简单,下面分享给大家,越努力越幸运,共勉! 创建文件夹 mkdir 后面加文件夹名字 ...

  5. linux编程两个子进程,Linux中fork同时创建多个子进程的方法

    怎么创建多个进程呢?我说那还不容易,看下边代码: //省略必要头文件 int main() { pid_t pid[2]; int i; printf("This is %d\n" ...

  6. linux 创建子进程,Linux中使用fork创建子进程详解及示例程序

    1. 进程 1.1. 什么是进程 当可执行文件开始运行之后,就变为了系统中的一个进程,一个程序(可执行文件)运行起来之后可以创建多个进程执行,称之为多进程程序. 每个进程包含有进程运行环境.内存地址空 ...

  7. Linux中的线程创建函数pthread_create函数

    Linux系统中的多线程遵循POSIX线程接口,成为pthread.pthread_create函数用来创建一个用户线程,函数原型如下. #include <pthread.h>int p ...

  8. linux怎么创建用户教程,在Linux中如何手动创建一个用户

    1.首先要明白用useradd创建用户的时候会更改添加5个地方的内容 (1)/etc/passwd             //比如创建useradd  111 // [root@localhost ...

  9. linux添加压缩文件tar,在linux中使用tar创建与解压文件

    tar命令用于将一组文件创建tar存档.它也可以提取tar档案,显示档案中包含的文件列表,将其他文件添加到现有档案,以及各种其他类型的操作 tar支持种类繁多的压缩程序如gzip,bzip2,lzip ...

  10. Linux中的虚拟机创建

    kvm虚拟化相关信息 KVM是Kernel-based Virtual Machine的简称 服务名称: libvirtd 虚拟化核心 qemu/kvm 虚拟化存储目录(虚拟机硬盘) /var/lib ...

最新文章

  1. 将ImageVIew中的图片保存到本地相册中
  2. 十二、使用索引规则【完】
  3. javascript小实例,多种方法实现数组去重问题
  4. 深度学习,究竟该如何学?
  5. javascript中的try finally
  6. mysql 表大小_MySQL查看数据库表容量大小的方法示例
  7. Robo 3T SQL
  8. vs2013和mysql连接_安装VS2013后与数据库的连接问题
  9. opera android 7,Opera迷你浏览器 Opera Mini 7
  10. 如何通过破解hash来获取管理员密码(转)
  11. 08方法重载,覆写,多态
  12. python内置json模块_python的常用内置模块之序列化模块json
  13. python 环境问题
  14. 麻省理工线性代数第一讲
  15. tomcat Failed creating java C:\Program Files\Java\jre6\bin\client\jvm.dll %1 不是有效的 Win32 应用程序。...
  16. 主要空间数据挖掘方法
  17. java中实现正态分布
  18. IDEA添加gitlab仓库并上传代码(无需使用任何git指令),报错Ask a project Owner or Maintainer to create a default branch解决方案
  19. 制作VMware 6.X安装源安装流程
  20. UTF-8编码下\u7528\u6237转换为中文汉字

热门文章

  1. layui当前表格第一行_layui使用表格渲染获取行数据的例子
  2. 全志V3S裸机 SDRAM内存初始化(并使用SDRAM启动仿真)
  3. B站黑马测试第二篇P204:navicat连接本地tpshop2.0数据库
  4. css导航栏悬浮在轮播图上面,如何设置半透明悬浮效果
  5. 电动机故障诊断——数据预处理
  6. vmware 显示器一拖二
  7. VC 打开 Excel 文件后,excel.exe进程无法退出
  8. XJTU_ 西安交通大学2020大学计算机作业-第十二周
  9. 升级版图片转换成pdf转换器
  10. Tableau 重新认识购物篮分析(SQL版)