目录
一、进程创建
二、进程状态
1. 运行状态R
2. 睡眠状态S
3. 僵尸状态Z
4. 孤儿进程
三、进程优先级 PRI
四、地址空间的层次结构
五、虚拟地址和物理地址
一、进程创建
- fork()函数创建子进程,若创建成功,则给父进程返回子进程的pid,给子进程返回0
- 父子进程关系:子进程ppid == 父进程pid
- 父子进程可同时运行,互不干扰
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("这是一个子进程,pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
else
{
while (1)
{
printf("这是一个父进程,pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
进程特性:
- 竞争性:系统进程数目众多,但cpu只有少数,所有进程之间根据优先级有了竞争性
- 独立性:多进程运行,需要独享各种资源,各个进程之间互不干扰
- 并行:多个进程在多个cpu上,分别同时进行运行
- 并发:多个进程在一个cpu上,采用进程切换的方式,在一段时间内让多个进程得以推进
进程在切换的时候,进行上下文保护,上下文是寄存器内的数据
进程在恢复的时候,进行上下文修复
二、进程状态
查看进程状态: ps ajx | head -1 && ps ajx | grep "myproc"
- 杀死进程: kill -9 pid
- 暂停进程: kill -19 pid
- 继续进程: kill -18 pid
- 进程状态带+号,代表是前台进程,否则是后台进程
- 前台进程在终端运行,后台进程不受终端控制
进程状态STAT:
- R:running,运行状态
- X:dead
- Z:zombie,僵尸状态,进程退出了但是还没被(父进程/os)回收
- T:stopped,暂停状态
- D:disk sleep,深度睡眠状态,改状态下的进程无法被os杀死
- S:sleeping,睡眠状态
1. 运行状态R
int main()
{
while (1);
return 0;
}
2. 睡眠状态S
程序运行的时候,99%以上的时间在等待加载,1%的时间在执行
int main()
{
while (1)
{
printf("pid = %d\n", getpid());
}
}
3. 僵尸状态Z
子进程结束了,但是父进程和os并没有回收子进程,子进程进入僵尸状态
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("子进程:pid=%d, ppid=%d\n", getpid(), getppid());
sleep(5);
exit(1);
}
}
while (1)
{
printf("父进程:pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
4. 孤儿进程
父进程结束了,但是子进程还在运行
孤儿进程会被os领养,变成后台进程
int main()
{
pid_t id = fork();
if (id == 0)
{
while (1)
{
printf("子进程:pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
else
{
while (1)
{
printf("父进程:pid=%d, ppid=%d\n", getpid(), getppid());
sleep(5);
exit(1);
}
}
}
三、进程优先级 PRI
- linux支持进程运行中,进行优先级调整(修改nice值)
- 最终优先级 = 原优先级 + nice值
- 每一次设置,原优先级都默认变为80
- 优先级的数值越小,优先级别越高
- nice的取值范围[-20, 19]
四、地址空间的层次结构
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct AddRoom
{
int code_start;
int code_end;
int init_start;
int init_end;
int heap_start;
int heap_end;
int stack_start;
int stack_end;
//...
}; //虚拟地址空间数据结构
int g_unval;
int g_val = 100;
int main(int argc, char* argv[], char* env[])
{
printf("code addr: %p\n", main);
const char* str = "hello";
printf("the const str addr: %p\n", str);
printf("init globle addr: %p\n", &g_val);
printf("uninit globle addr: %p\n", g_unval);
char* heap_mem0 = (char*)malloc(10);
char* heap_mem1 = (char*)malloc(10);
char* heap_mem2 = (char*)malloc(10);
char* heap_mem3 = (char*)malloc(10);
printf("heap addr: %p\n", heap_mem0);
printf("heap addr: %p\n", heap_mem1);
printf("heap addr: %p\n", heap_mem2);
printf("heap addr: %p\n", heap_mem3);
printf("stack addr: %p\n", &heap_mem0);
printf("stack addr: %p\n", &heap_mem1);
printf("stack addr: %p\n", &heap_mem2);
printf("stack addr: %p\n", &heap_mem3);
for (int i = 0; i < argc; ++i)
{
printf("argv[%d]: %p\n", i, argv[i]);
}
for (int i = 0; env[i]; ++i)
{
printf("env[%d]: %p\n", i, env[i]);
}
return 0;
}
运行结果:
五、虚拟地址和物理地址
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int g_val = 100;
int main()
{
pid_t id = fork();
if (id < 0)
{
printf("子进程创建失败\n");
return 1;
}
else if (id == 0)
{
int cnt = 0;
while (1)
{
printf("子进程: pid=%d, ppid=%d, g_val=%d, &g_val=%p\n",
getpid(), getppid(), g_val, &g_val);
sleep(1);
++cnt;
if (cnt == 5)
{
g_val = 300;
printf("子进程已将全局变量修改为 300 \n");
}
}
}
else
{
while (1)
{
printf("父进程: pid=%d, ppid=%d, g_val=%d, &g_val=%p\n",
getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
return 0;
}
执行结果:
原理:
虚拟地址存在的意义:
- 阻止非法访问,保护物理内存数据
- 内存管理模块与进程管理模块进行了解耦合
- 防止物理空间内存浪费
- 地址空间+页表,将内存分部有序化
- 实现对程序的分批加载,分批换出
虚拟地址转换为物理地址:
- 地址空间是进程能看到的资源窗口
- 页表决定进程真正拥有资源的情况
- 合理的对地址空间空间+页表进行资源划分,我们可以对一个进程的所有资源进行分类管理
- 先用虚拟地址的前10位查页目录
- 再用虚拟地址的中间10位查页表
- 最后用虚拟地址的后12位查页内偏移
- 磁盘内每一个页帧的大小为4KB,对应最大页内偏移量为2^12
- 页表只有在需要使用的时候才被创建