建的网站打开很慢,高邮市建设网站,手机wap网站建设,网站做3儿童车开场动画进程控制
进程创建
对于进程的创建#xff0c;你肯定知道#xff0c;在 C/C 当中使用 fork#xff08;#xff09;函数#xff0c;以当前可执行程序生成的进程为 父进程#xff0c;创建这个父进程的 一个子进程#xff0c;这个 子进程就是一个新的进程。 如上图所示你肯定知道在 C/C 当中使用 fork函数以当前可执行程序生成的进程为 父进程创建这个父进程的 一个子进程这个 子进程就是一个新的进程。 如上图所示刚开始创建出子进程之时甚至父子进程的 页表 数据 和 代码都可以是共用的当 子进程 有修改操作之时才会发生写时拷贝拷贝子进程修改之后需要有用到的数据。
当父进程 执行到 fork函数之时就会在内核当中去找到 fork函数定义调用 fork函数创建子进程此后就从fork函数之后父子进程就会各自执行开始各自的“旅程”。
所以fork之前父进程独立执行fork之后父子两个执行流分别执行。注意fork之后谁先执行完全由调度器决定。
当然fork函数创建进程是有创建失败的情况的比如系统当中进程数太多内存爆满或者是 实际用户的进程使用数超过了限制都是可能会导致 fork函数创建进程失败的。 除了可是使用 if-else 语句来判断当前是什么进程把父进程和子进程用来执行不同的代码块之外你还可以用 循环来 批量化 创建多个进程
#includestdio.h
#includestdlib.h
#includeunistd.h#define N 5int main()
{int i 0;for (int i 0; i N; i){int fork_ret fork();if (fork_ret 0){// 某一个子进程printf(child : PID: %d , PPID: %d\n, getpid(), getppid());exit(0);// 子进程执行完上述的代码就终止}}return 0;
} 这个写时拷贝不仅仅只是子进程会进行写时拷贝父进程同样会进行写时拷贝父子进程谁先写谁就先进行写时拷贝。
如上所示不管原本的父进程对应的页表项是 什么访问权限就算是 可读可写在最后都会修改成只读会把全部可写的区域全部改成只读所以子进程继承下来也是只读的。
所以在此之后不管是哪一个进程想在某一个 区域当中的写的话都与触发权限方面的问题此时操作系统识别到了是不会进行异常处理的反而是做写时拷贝的 处理是谁想修改的就新开辟空间拷贝一份数据给这个 进程使用。然后再把两个进程对应的页表项的 访问权限标签标成可读的。
而写时拷贝本质上就是用一种延迟申请按需申请不要盲目的给子进程直接拷贝一份开辟一个 和 父进程一样大的空间给子进程使用如果父进行当中有100个数据但是 子进程只用修改一个数据父进程也不修改数据的话那么很大部分的子进程的空间就浪费了因为这些不用修改数据完全可以和父进程共同使用。所以才要写时拷贝需要的时候再去拷贝数据开辟新的空间。
进程终止
在上述用循环来创建 多个进程 这个例子当中我们想提前终止掉 子进程 的运行所以我们在函数体当中 就 使用 exit这个函数来终止掉这个 子进程 要讨论上述的 exit 函数之前我们先来讨论一下为什么我们使用 main总是喜欢 return 0 呢那么 return 其他的数可以吗比如 return 1
进程终止无外乎就三种终止的情况
代码运行完毕结果正确代码运行完毕结果不正确代码异常终止
如果是前两种情况结果正确的情况我们是不关心的我们最关心的是结果为什么是不正确的。如果结果是不正确的那么我们对程序的返回结果做分析。
所以其实 return 0 是 进程的退出码这个退出码表示进程的运行结果是否正确我们一般用 0-sucess 。0 是我们默认的 程序程序运行的退出码其他程序员也是这样认为的如果这个程序返回的是0 不仅仅是告诉写这个程序的人这个程序执行成功了也告诉 其他程序员这个程序是正常退出的。 同时main函数的返回值返回给谁了为什么要返回这个值
上述所说的 return 0 这个退出码返回给谁了呢其实这个 退出码会被这个进程的父进程所拿到对应到 我们日常在 使用 命令行来运行程序的话就是被 bash 所拿到。
我们可以在命令行使用 echo $? 命令来查看到最近一个 bash 的子进程的退出码是多少 之所以父进程要接收子进程的程序退出码是因为一般而言某一个进程的父进程是要关心子进程的运行情况的。 如果子进程返回的是0那么说明这个程序运行是正确的父进程知道了子进程返回的是 0 的退出码他也就放心了因为 子进程运行结果是正确的。
父进程最关心的是 子进程 的退出码不是 0 的情况也就对应 程序运行结果不正确的情况所以我们可以用 return 不同的数字来表示不同错误原因。 用这种方式来告知使用这个程序的用户这个程序出没出错或者出错了出的是什么错误因为一个程序是不一定 一定要在屏幕上打印什么内容的肯定是有程序是没有打印内存的这个需求的那么在这种没有任何打印来提示错误的程序当中要想知道这个程序是否出错的话使用 程序退出码来表示是最好的。
所以我们之前在写代码直接“无脑写” return 0 其实是不太正确的因为如果整个程序的运行环境是在多个进程同时运行的环境下而且这些进程都互相之间有一定关联或者整个程序就是在各自运行没有任何的反馈我们是不知道这个程序到底出没有出错的。 而且退出码是一个一个的数字他是比较适合让计算机去看的不然的话这个程序退出码是1 表示 内存出错2 表示数组越界····· 这些报错的退出码人是不可能每一个都记住的我们需要去查文档等等的文献来知道当前程序的退出码表示的是什么意思。
这是一种机械化的操作不就是查文档吗那么交给计算机去做不就行了所以返回这些不同的退出码本质上也就是返回一些代表着 不同报错信息的数字这些数字是给计算机去看的。
当计算机查到当前程序的退出码对应的错误信息计算机就可以把这个退出码转换成对应的错误信息的字符串让我们知道这个程序当中是出现了什么错误。 在 Linux 当中string.h 这个头文件当中有一个 strerror这个函数接口这个函数就可以传入一个 退出码把这个退出码对应的 报错信息转换成 字符串的形式作为这个函数的返回值 所以我们可以简单的使用这个函数来打印一些 退出码代表的 错误信息来查看 输出 所以我们在日常当中使用各个指令使用错误的时候就会报一些错误 这些错误信息就可以对应得上了。 所以错误码和错误码对应的描述是有对应关系的。 而父进程之所以要关心这个 子进程的 这个程序的退出码其实就是把报错信息打印出来做一个收集工作。
其实父进程就是一个工具人一个跑腿的真正关心这个子进程的 结果对不对的是使用这个程序的用户用户才是最关心这个程序的执行结果是不是正确的。
用户根据 父进程接收到的打印在外设当中的 错误信息根据这个错误信息 用户才能更好的使用这个软件。
就像刚刚我们在使用 ls 查看某一个目录的时候报错了因为我们输入的文件在当前目录是不存在的所以它报错“找不到这个文件”。然后我就去查看当前目录发现确实没有这个文件所以用户就去看区查找它想要访问的文件在哪里从而正确的使用 ls 这个命令。
由此可见使用这种方式用户可以知道是自己使用错了还是程序的实现出错了。这一切都是为用户服务。
所以一个进程运行得怎么样用户如何知道靠的就是父进程收集这个子进程的 退出码退出信息转交给用户用户才知道 这个 子进程是否运行成功。
所以一个代码成功运行之后返回的结果正不正确统一采用进程的退出码来判定而这个退出码就是 main函数的返回值。 自己设置错误码信息
系统当中给的错误码是参考的你想用就可以用不想用自己写一套错误码体系也是可以的。
而且实现也是非常的简单只需要定义一个保存 错误码信息的 全局字符串数组即可。这个字符串数组每一个 元素是一个字符串那么这个数组的下标代表的就是 每一个字符串代表的错误信息描述所对应的错误码 C语言当中的 errno 全局变量
与上述的 退出码 类似的在 C 语言当中会有一个 errno 的全局变量 这个全局变量保存的是最近一次执行的错误码。是什么执行的错误码呢
在 C 当中有很多的库函数但是我们调用库函数不是每一次都会调用成功的比如 malloc realloc fopen 这些函数都是可能会调用失败的当我们调用失败这函数可能会返回 -1 或者是 NULL 的返回值。
像上述如果我们只看这个函数的返回值的话就只能知道这个函数究竟有没有调用成功但是对于调用失败的情况我们通过函数返回值只能知道 当前函数调用失败了但是具体为什么失败是什么原因导致的这个函数的返回值是不能体现的。 所以一旦我们使用 C 当中的库函数调用失败了那么就会把 这个 errno 全局变量设置为对应的数字这里的每一个数字和上述说过的 程序的退出码一样是要各自的含义的。就是我们想要知道的为什么会报错的。 我们把这个数字称之为 错误码。 但是不排除当前情况下有多个函数都报错的情况比如第一个函数报错了errno 就被更新为这个函数的报错信息了但是当 第二个报错函数出现的时候 errno 是会继续更新的。会被最新一个报错的函数的错误码覆盖。
所以如果你想知道某一个C/C 程序当中如果出错了出错原因是什么那么可以直接返回这个 errno 全局变量 像上述的情况就是不仅仅 可以通过 p 变量知道当前 malloc 函数是否调用成功吗还可以通过 ret 变量接受的 errno 的值知道错误码是多少然后通过main函数返回这个错误码 给 父进程父进程在 转交给用户那么用户就可以知道 具体是什么原因发生的错误了。 代码异常终止的情况 代码如果出现异常终止了那么这个代码可能就是 没有跑完没有执行完毕就被操作系统直接干掉了。
如果代码没有跑完程序直接退出了那么根本就没有执行main函数最后一条语句return 返回退出码最后一步是没有执行的。
所以退出码在此处是不是没有意义呢
是的退出码在此处是没用了
就算你执意认为就算发生异常但是没有及时捕获还是执行到了最后的return语句。但是这个退出码你敢用码程序发生异常可能在任何位置任何时候都可能会发生异常那么最后返回的这个 错误码一定是正确的吗不一定吧。 就好比的是你考试作弊考了90 分回去你更你爸爸说你考试考90 分但是要被请家长你爸爸问你为什么要被请家长你说在考试过程当中“发生了异常”考试作弊比发现了像这个例子虽然最后你跟你爸爸说了你考试考了90 分相当于return 90但是 是作弊的程序是异常退出的你可以在考试期间作弊也可以在 考试最后作弊但是不管在那个时候作弊就是作弊了那么你爸爸会相信你考的90分吗 所以当一个进程以 异常退出的方式结束的话程序的退出码就没有意义了我们也就不关心这个退出码了。 如何知道程序是异常退出的程序是为什么异常的
进程出现异常本质其实是进程收到了对应的信号
使用 kill -l 可以查看所以的信号代表的是什么意义 比如野指针访问的错误其实访问指针指向地址空间这个地址其实是一个虚拟地址这个虚拟地址由页表映射到内存当中的实际存储数据的物理地址。
如果访问一个野指针的话也就是访问某一个虚拟地址但是这个虚拟地址在页表当中不存在找不到这个虚拟地址的映射关系。
还是就是除数为0的情况其实是在cpu当中的 状态寄存器就会出现溢出的情况。
像出现上述的两个情况运行程序就会发生下面异常报错 分别对应 进程信号当中的 8 和 11。 使用 kill 信号可以给某一个进程发送某一个信号那么这个进程就会因为这个信号而发生一些改变比如使用 8 号信号的话就会直接抛出 野指针的异常 在程序当中主动退出程序使用 exit函数 和 _exit
所以父进程想要关系某一个子进程运行状态的话首先要关心子进程退出之时有没有接收到对应的信号如果没有说明程序是正常退出的代码是跑完的那么再去看子进程的退出码。 所以我们在使用 exit 函数我们在其中可能会写 0 等等数字这些个数字代表的就是我们想要退出这个程序所使用 退出码。 exit函数可以在程序代码的任意地址被调用都可以表示在exit调用处进程直接按照所给退出码直接退出。
而 return 和 exit函数的区别在于return只表示当前函数返回程序继续向后运行。 调用这个函数输出的是 发现最后四个的 end show 没有打印。而且返回的是 exit当中的13而不是 main函数当中的 12。 上述现在使用的是 return返回发现返回的退出码不是 exit 函数当中的 13而且是在main函数当中的 12。 还有一个 _exit函数可以提前终止掉进程 使用这个 函数都可以终止进程进程的退出码及时其中参数 status。
那么exit() 和 _exit()有什么区别呢
_exit函数是纯正的 系统调用函数。当我们要终止进程的时候是直接在操作系统内部直接终止掉这个进程而缓冲区当中的数据不做刷新。他会直接调用操作系统层面上的接口直接关闭掉进程。 而 exit函数在终止程序之前会先执行用户定义的清理函数再冲刷缓冲区关闭流等等然后 exit 调用 _exit。 例子printf函数一定是先把数据写到缓冲区当中在合适的时候在进程刷新到屏幕上这个合适的时候比如是 遇到 \n 或者是 进程退出 这个缓冲区绝对不在 内核区当中。因为是在内核区当中也就是上图当中的 kernel 当中那么 _exit函数在结束进程之时就可以在内核区当中对缓冲区当中的数据进行刷新但是实际上是没有刷新的。所以操作系统只要维护缓冲区那么就一定会刷新。