进程
文章目录
- 进程
- PCB
- pid与ppid
- fork系统调用
- 进程状态
- 孤儿进程
- 状态优先级
- 环境变量
- 进程地址空间
- 虚拟地址
最直观的表示:启动一个软件,本质就是启动一个进程
PCB
PCB是Process Control Block的简称,是用来描述进程状态信息的数据结构。
进程运行时,是会在内存中存放该进程的代码+数据
因此:进程=代码和数据+PCB
这里的PCB结构是一种泛化的概念,在Linux下,PCB的实例化就是task_struct
pid与ppid
就像人有名字一样,进程也有自己的名字,进程的名字就是pid,ppid就是该进程的父进程的pid
fork系统调用
fork是创建进程的系统调用,给父进程返回子进程pid,给子进程返回0
#include<stdio.h>
#include<sys/types.h>
int main()
{
pid_t id =fork();
if(0!=id)
{
printf("我是父进程,pid:%d 我的子进程pid:%d\n",getpid(),id);
//getpid()返回pid
}
else if(0==id)
{
printf("我是子进程,pid:%d 我的父进程pid:%d\n",getpid(),getppid());
//getppid()返回父进程pid
}
return 0;
}
运行结果如下
注意
- fork之后会有两个不同的执行流,至于父进程对于执行流先运行还是子进程对于执行流先运行不一定,这由操作系统的调度器决定
- fork之后代码由父子进程共享
fork之后子进程会拷贝父进程的代码与数据给自己,操作系统会给子进程创建对应的PCB。
进程状态
- 运行态,这里的运行态不单单指在cpu上运行才叫做运行态,在运行队列中排队等待调度器调度时也叫做运行态
- 阻塞,阻塞态可以简单地理解为进程等待某种资源的释放或某种事件的发生,进程在这种状态下暂时停止执行,直到所需的资源可用或所等待的事件发生为止。
- 挂起,内存不足时,操作系统会适当置换进程的代码与数据到磁盘,这种状态就叫做挂起状态。
Linux下的进程状态
- R:就是上面运行态
验证R状态
#include <stdio.h>
int main()
{
while (1)
{
}
return 0;
}
运行起来之后写一段shell脚本来检测进程运行状态
while :;do ps axj | head -1 && ps axj | grep test | grep -v grep ; sleep 1 ;echo "------------------"; done
R后面的‘+’表示这是一个前台进程
- S:对于上面阻塞态,可被操作系统唤醒,可中断睡眠
验证S状态
#include <stdio.h>
int main()
{
int a;
scanf("%d", &a);
return 0;
}
运行起来之后,系统会等着你输入,这时候不要输入,去查看进程状态
- D:深度睡眠,不可被中断,不可被被动唤醒
不做验证
- T:暂停状态
在用一些调试工具调试时可以验证,这里不做验证
- Z:僵尸状态,进程已经退出,但是还不允许操作系统释放,处于被检测的状态。维持该状态是为了父进程/操作系统来回收
验证Z状态
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (0 == id)
{
int cnt = 3;
while (cnt--)
{
printf("我是子进程,pid:%d,ppid:%d\n", getpid(), getppid());
sleep(1);
}
exit(0);
}
else
{
while (1)
{
printf("我是父进程,pid:%d,ppid:%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
可以看到,前面三秒都是S装态,这里S状态是因为sleep了一秒钟,第四秒开始子进程变成僵尸状态,等待着父进程或者操作系统来回收,也就是对应的Z状态
子进程的进程名后面出现了‘defunct’,即为失效的,即僵尸状态
打印输出也只剩父进程
如何解决僵尸进程?在我的另一篇博客:中有详细解读,感兴趣的朋友可以自行阅读。
- X:终止状态
孤儿进程
父进程先退出,子进程就被称为孤儿进程,此时该进程就会被init进程(pid为1)领养,由init回收
验证孤儿进程
// 验证孤儿进程
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (0 == id)
{
while (1)
{
printf("我是子进程,pid:%d,ppid:%d\n", getpid(), getppid());
sleep(1);
}
}
else
{
int cnt = 3;
while (cnt--)
{
printf("我是父进程,pid:%d,ppid:%d\n", getpid(), getppid());
sleep(1);
}
exit(0);
}
return 0;
}
可以看到,前三秒父进程和子进程同时在跑,但是3秒后父进程退出,子进程被1号进程领养
状态优先级
进程优先级即cpu资源分配的先后顺序
查看进程优先级命令
ps -l
- PRI
代表进程优先级,数值越小,优先级越高
- NI
nice值,用来更改进程优先级,取值范围[-20,19]
PRI=PRI+NI
所以想要修改进程优先级,就要通过修改nice值来进而修改进程优先级
修改进程优先级命令
top
输入‘r’后输入进程pid,随后修改nice值即可
环境变量
先来看现象
我们写一个打印的程序
#include <stdio.h>
int main()
{
printf("hello linux\n");
return 0;
}
使用系统自带的命令时,不用带路径,直接可以运行,但是我们自己写的程序如果不带路径就会报错。
这就和环境变量有关
查看环境变量
echo $PATH
这里维护了大量的路径,路径之间用冒号’:‘隔开,我们使用的诸如’ls’,'pwd’的系统命令就在这些路径当中,我们可以查看一下ls所处的路径
which ls
所以我们运行自己的程序时,shell会在这些路径中去查找’myproc’,没有找到就会显示’command not found’
如果不想带路径运行自己的程序,可以将可执行程序所在的路径导入到系统环境变量中,即可像系统指令一样执行自己写的程序。
export PATH=$PATH:程序所在路径
导入之后再查看系统环境变量,这时程序即可直接运行,不用带路径
但是,这种方式只在本次对话当中有用,下次连接系统时不会存在这个路径,如果需要永久保存,需要修改环境变量配置文件。
但是呢,环境变量不止PATH,还有HOME
echo $HOME
SHELL环境变量
echo $SHELL
显示所有环境变量
env
- 子进程的环境变量继承自父进程,往上层去找最终就是bash的环境变量
- 环境变量具有全局属性,体现在可以被子进程继承
进程地址空间
在c/c++程序员眼中,程序地址空间如下
#include <stdio.h>
#include <stdlib.h>
int uninit_global;
int init_global = 10;
int main(int argc, char *argv[], char *env[])
{
const char *str = "read only";
int *heap1 = (int *)malloc(sizeof(int));
int *heap2 = (int *)malloc(sizeof(int));
printf("code address:%p\n", main);
printf("read only address:%p\n", str);
printf("init_global address:%p\n", &init_global);
printf("uninit_global address:%p\n", &uninit_global);
printf("head address:%p\n", heap1);
printf("head address:%p\n", heap2);
printf("stack address:%p\n", &heap1);
printf("stack address:%p\n", &heap2);
int i = 0;
for (i = 0; i < argc; i++)
{
printf("argv[%d] address:%p\n", i, &argv[i]);
}
for (i = 0; env[i]; i++)
{
printf("env[%d]:%p\n", i, env[i]);
}
return 0;
}
输入命令运行结果如下
虚拟地址
为什么叫虚拟地址空间?难道上面我们讲的进程地址空间都是虚拟的?假的? 没错!!就是假的!!
如果直接让用户对真实的 物理空间进行读写访问,其实是非常危险的动作,因此,操作系统不会让你直接访问真实的物理地址。
操作系统表面上告诉进程,计算机的地址空间全是你的,但是其实不然。
先来一段代码
#include <stdio.h>
#include <sys/types.h>
int global = 10;
int main()
{
pid_t id = fork();
if (0 == id)
{
printf("global_val:%d---%p\n", global, &global);
}
else
{
global = 5;
printf("global_val:%d---%p\n", global, &global);
}
return 0;
}
运行结果如下
出现了非常神奇的现象?同一个地址为什么对应的值不同???
我们知道,父进程在创建子进程之后会给子进程拷贝一份父进程的代码与数据,也会给子进程创建对应的程序地址空间,而这个程序地址空间就属于虚拟地址,需要页表哈希索引来找到对应的真实的物理地址,子进程拷贝的虚拟地址与父进程对应的虚拟地址一样,所以出现了地址一样,值却不一样的现象。解析图如下
上述程序因为拷贝了父进程的代码与数据,拷贝的地址属于虚拟地址,经过页表置换后才能找到真正的物理地址。