子进程先于父进程结束,而父进程又没有调用wait或者waitpid获取其退出信息。子进程还需要在其PCB中保存其退出的相关信息,所以,子进程的执行主体已经结束,但是操作系统并没有释放该进程PCB结构,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。在子进程结束运行之后,父进程读取其退出状态之前,我们称该子进程为僵尸进程。
由父进程调用wait或者waitpid获取其退出信息,但是调用wait或者waitpid的进程会阻塞运行,导致父进程在子进程结束之前不能执行其他事情。在此基础之上可用三种方式解决
重启操作系统(建议不推荐)。
利用信号SIGCHLD,子进程状态改变后会向其父进程发送SIGCHLD信号。父进程在接受到该信号后,在信号处理函数中调用wait或者waitpid。
#include
#include
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
#include
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
#include
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值。
环境变量(envirnment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
#include
int main()
{
std::cout<<"Hello world"<<std::endl;
return 0;
}
./hello运行
./目的是为了在当前路径下找到hello的可执行程序。
而如果直接运行hello那么将会发生错误:
用root和普通用户,分别执行 echo $HOME ,对比差异. 执行 cd ~; pwd ,对应 ~ 和 HOME 的关系。
root用户:
普通用户:
在C语言中我们学到了指针数组,就是存放指针的数组,那么存放的就是各种环境变量的首地址,通过找到环境变量的首地址就可以找到对于的环境变量。
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串。
代码如下:
#include
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
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
#include
#include
int main()
{
printf("%s\n",getenv("PATH"));
return 0;
}
导出环境变量 export MYENV=“hello world”。
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!
想象为什么?
代码如下:
#include
#include
int main()
{
char* env=getenv("MYENV");
if(env)
{
std::cout<<env<<std::endl;
}
return 0;
}
以上就是今天要讲的内容,本文仅仅进程概念后面的一些知识,而进程提供了大量能使我们快速便捷地处理数据的函数和方法。到这里,进程概念就结束了,后序将会有更重要的文章陆续更新,希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。