子进程先于父进程结束,而父进程又没有调用wait或者waitpid获取其退出信息。子进程还需要在其PCB中保存其退出的相关信息,所以,子进程的执行主体已经结束,但是操作系统并没有释放该进程PCB结构,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。在子进程结束运行之后,父进程读取其退出状态之前,我们称该子进程为僵尸进程。
由父进程调用wait或者waitpid获取其退出信息,但是调用wait或者waitpid的进程会阻塞运行,导致父进程在子进程结束之前不能执行其他事情。在此基础之上可用三种方式解决
重启操作系统(建议不推荐)。
利用信号SIGCHLD,子进程状态改变后会向其父进程发送SIGCHLD信号。父进程在接受到该信号后,在信号处理函数中调用wait或者waitpid。
将僵尸进程的父进程杀掉,将最终使用的子进程变为孤儿进程,从而交由init进程处理其退出信息。
代码如下:
#include<stdio.h> #include<unistd.h> int main() { pid_t pid=fork(); if(pid<0) { printf("fork error!\n"); } else if(pid==0) { printf("i am child:pid=%d,ppid=%d\n",getpid(),getppid()); //子进程 } else { while(1) { printf("i am father:pid=%d,ppid=%d\n",getpid(),getppid()); sleep(1); } //父进程 } return 0; }
子进程的状态:
至此,值得关注的进程状态全部讲解完成,下面来认识另一种进程。
父进程结束或者异常终止,但是子进程继续运行。此时子进程的PPID被设置为1,即init进程。init进程接管了该子进程,并等待它结束,在父进程退出之后,子进程退出之前,该子进程属于孤儿进程(在Linux高性能服务器编程中也被称为僵尸进程)。
代码如下:
#include<stdio.h> #include<unistd.h> int main() { pid_t pid=fork(); if(pid<0) { printf("fork error!\n"); } else if(pid==0) { //子进程 while(1) { printf("i am child:pid=%d,ppid=%d\n",getpid(),getppid()); sleep(1); } } else { printf("i am father:pid=%d,ppid=%d\n",getpid(),getppid()); } return 0; }
由于进程号为17398的进程变成后台进程,所以不能ctrl+c退出。
要使用kill -9 强杀命令杀掉17398进程。**
题目:
无论哪种情况,如果父进程没有正确的处理子进程的返回信息,子进程都将停留在僵尸态或者孤儿态,并占据着内核资源。这是不允许的,毕竟内核资源有限。
在linux或者unix系统中,用ps –al命令则会类似输出以下几个内容:
代码如下:
#include<stdio.h> #include<unistd.h> int main() { while(1) { printf("Hello Linux!\n"); sleep(1); } return 0; }
我们很容易注意到其中的几个重要信息,有下:
top命令用于显示系统运行的进程信息,作用类似于windows中的任务管理器,只不过top不是图形化的,而是显示实时文本信息。
统计信息区域的下方显示了各个进程的详细信息。首先来认识一下各列的含义。PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
序号列名含义
PID 进程id
PPID 父进程id
RUSER Realusername
UID 进程所有者的用户id
USER 进程所有者的用户名
GROUP 进程所有者的组名
TTY 启动进程的终端名。不是从终端启动的进程则显示为?
PR 优先级
NInice 值。负值表示高优先级,正值表示低优先级
P 最后使用的CPU,仅在多CPU环境下有意义
%CPU 上次更新到现在的CPU时间占用百分比
TIME 进程使用的CPU时间总计,单位秒
TIME+ 进程使用的CPU时间总计,单位1/100秒
%MEM 进程使用的物理内存百分比
VIRT 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
SWAP 进程使用的虚拟内存中,被换出的大小,单位kb。
RES 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
CODE 可执行代码占用的物理内存大小,单位kb
DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
SHR 共享内存大小,单位kb
nFLT 页面错误次数
nDRT 最后一次写入到现在,被修改过的页面数。
S 进程状态。
D= 不可中断的睡眠状态
R= 运行
S= 睡眠
T= 跟踪/停止
Z= 僵尸进程
COMMAND 命令名/命令行
WCHAN 若该进程在睡眠,则显示睡眠中的系统函数名
Flags 任务标志,参考sched.h
进入top后按“r”–>输入进程PID–>输入nice值。
这里就显示了修改以后的PRI和NI。
重要:
环境变量(envirnment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
echo $NAME //NAME:你的环境变量名称
代码如下:
#include<iostream> int main() { std::cout<<"Hello world"<<std::endl; return 0; }
./hello运行
./目的是为了在当前路径下找到hello的可执行程序。
而如果直接运行hello那么将会发生错误:
为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?
将我们的程序所在路径加入环境变量PATH当中, export PATH=$PATH:hello程序所在路。
这种方法只是在当前终端有效,离开当前终端就失效了。
对比测试使环境变量永久生效。
原来的.bash_profile是这样的。
经过修改后变成了:
还有什么方法可以不用带路径,直接就可以运行呢?
扩展知识:
用root和普通用户,分别执行 echo $HOME ,对比差异. 执行 cd ~; pwd ,对应 ~ 和 HOME 的关系。
root用户:
普通用户:
在C语言中我们学到了指针数组,就是存放指针的数组,那么存放的就是各种环境变量的首地址,通过找到环境变量的首地址就可以找到对于的环境变量。
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串。
代码如下:
#include<stdio.h> int main(int argc,char* argv[],char* env[]) { //argc : 命令行参数的个数, 本质上就是argv数组的元素个数 //argv :具体的命令行参数 // envp : 环境变量的值 for(int i=0;i<argc;i++) { printf("%s\n",argv[i]); } for(int i=0;env[i]!=NULL;i++) { printf("%s\n",env[i]); } return 0; }
代码如下:
#include<stdio.h> int main(int argc,char* argv[]) { extern char** environ; for(int i=0;environ[i]!=NULL;i++) { printf("%s\n",environ[i]); } return 0; }
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
常用getenv和putenv函数来访问特定的环境变量。
代码如下:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { printf("%s\n",getenv("PATH")); return 0; }
直接查看,发现没有结果,说明该环境变量根本不存在。
导出环境变量 export MYENV=“hello world”。
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!
想象为什么?
代码如下:
#include<iostream> #include<stdlib.h> int main() { char* env=getenv("MYENV"); if(env) { std::cout<<env<<std::endl; } return 0; }
以上就是今天要讲的内容,本文仅仅进程概念后面的一些知识,而进程提供了大量能使我们快速便捷地处理数据的函数和方法。到这里,进程概念就结束了,后序将会有更重要的文章陆续更新,希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。