进程概念
(冯诺依曼体系结构,操作系统,进程概念,进程状态,环境变量,程序地址空间)
冯诺依曼体系结构:(计算机硬件体系结构)
输入设备,输出设备,存储器,运算器&控制器(CPU)
CPU想要处理数据,要从内存中取出进行处理–CPU执行的一个程序,首先要把程序从硬盘加载到内存
操作系统:
本质:一个软件程序
功能:对计算机进行软硬件管理
目的:让计算机更好用
完整的操作系统:内核+外部应用(linux内核)
各个操作系统厂商,根据linux内核进行定制化开发,然后加上自己封装的应用,组成自己的操作系统(Redhat,Debian…)
操作系统如何管理硬件
用户
库函数
系统调用接口
操作系统
硬件驱动
硬件
自上而下,由操作系统进行串联,
进程:
运行中的程序,是程序运行过程的动态描述,这写描述在pcb-进程控制块中,在linux下是一个task_struct结构体
操作系统管理和调度程序的运行是通过pcb来实现
pcb描述的信息:标识符(进程ID),内存指针(程序数据在内存中的地址),程序计数器,上下文数据(切换cpu寄存器中的数据会保存下来,这是其一),进程状态,io信息,…
进程状态:
状态标记了当前进程该如何被操作系统进行调度管理(标识一个进程什么状态该做什莫事)
课本上:
就绪–准备好了,拿到时间片就能运行
运行–CPU在处理,获得CPU
阻塞–因为某些特殊原因,暂时不能运行,满足某种条件后,被置为就绪状态,拿到时间片可以运行
linux下进程状态:
查看进程:ps -aux | grep name 查看进程状态 ps -ef | grep name 查看进程的父进程
进程的六种状态
运行态–R:正在被执行,以及拿到时间片就能执行的进程,正在运行或在运行队列中等待
可中断休眠–S:(因为某种运行条件不足,暂时不能被调度运行的进程,sleep(3)),可以被kill -9 pid杀死
不可中断休眠态–D:无法被中断阻塞,只能等待阻塞的唤醒条件才能被调度执行,不可被kill
停止态–T:什么都不做,但还活着,可以被调度(与休眠不同,休眠是阻塞)
僵尸态–Z:进程退出运行,但是资源没有被完全释放,等待处理,pcb中还保存其信息
创建子进程:pid_t fork(void);–通过赋值调用进程(父进程)来创建一个新的进程(子进程)
返回值:在父进程中返回值是子进程的pid(大于0);在子进程中返回0;出错返回-1;
在在系统的角度进程就是pcb,咋linux下是task_struct 结构体
创建一个进程就是创建了一个task_struct 结构体,也就是创建了pcb
fork创建就是复制了父进程中大部分数据
它们都是进程,平等关系,不是子集关系
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello\n");//如果没有换行就不会清空缓冲区,那么下面fork子进程,子进程会把缓冲区内容一块复制,就会出现和父进程一样的输出hellobye,有'\n'则只有bye会输出两次
pid_t child=fork();
printf("bye\n");
return 0;
}
僵尸进程:子进程先于父进程退出,为了保存退出原因,退出后没有完全释放资源成为僵尸进程
危害:资源泄露
避免:进程等待(等待子进程退出,获取退出子进程的返回值,释放子进程资源,避免产生僵尸进程)
僵尸进程存在的原因就是为了保存子进程退出原因给父进程看,只要父进程获取了退出原因,资源就会被释放
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int ret=fork();
if(ret==0)
{
printf("child--%d\n",getpid());
sleep(5);//子进程休眠五秒后退出,但是父进程没有接受其返回值,所以成为僵尸进程
}
else
{
printf("parent--%d\n",getpid());
sleep(30);//让父进程休眠更长时间
}
return 0;
}
用ps查看进程
程序运行结果:
孤儿进程:父进程先于子进程退出,子进程成为孤儿进程
特性:运行在后台,父进程成为1号进程(以前叫init进程,现在是systemd)
fork创建子进程,父子进程的运行顺序是不一定的,在操作系统中,进程按照异步方式运行,按各自独立的、不可预知的速度向前推进
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int ret=fork();
if(ret==0)
{
printf("我是子进程:pid=%d\n",getpid());
sleep(10);
}
else
{
printf("父进程:pid=%d,3秒后退出\n",getpid());
sleep(3);
exit(0);
}
while(1)
{
sleep(1);
}
return 0;
}
子进程的ppid为1,状态为
1号进程systemd
孤儿进程会成为僵尸进程吗?—不会变成僵尸进程,会被1号进程收养
环境变量:保存当前程序运行环境参数的变量
优点:即时生效;让运行环境配置更加灵活;通过环境变量可以给运行的程序传递数据
终端shell有两种变量:环境变量(有继承性)、普通变量(无)
接口:char* getenv(char* name);
指令:env 查看所有环境变量 ; set 查看所有变量 ; echo 打印指定内容到终端
export 声明一个变量为环境变量 ; unset 删除变量
在程序中访问环境变量:
1.getenv接口;
2.main函数第三个参数 main(int agrc,char* argv[],char* env[])
3.全局变量:extern char**environ;
并发:独木桥,多个人,一次走一个,轮换着走
并行:更宽的桥,可以多个人同时一起走
程序地址空间:操作系统为每个进程描述的一个虚拟地址空间
内存地址:对存储区域的编号(以字节为单位)
程序地址空间更应称作:进程地址空间(运行起来的程序才会占据内存)
在一个进程中,所访问到的所有变量地址都是一个虚拟地址,(并不是真正的物理地址存储),虚拟地址经页表映射得到物理地址,从而访问物理内存
虚拟地址空间是一个结构体:linux下是mm_struct
系统通过mm_struct结构体向每个描述了一个虚拟的、连续的、完整的、线性的地址空间。
好处:1.每个进程都要一个完整独立的虚拟地址空间,贼地址可以随便使用,不用担心冲突(地址管理更加方便)
2.通过页表映射可以随机存储在物理地址的任意位置,实现数据离散式存储,提高内存利用率
3.在进行页表映射后可以进行访问权限的控制
内存管理方式:
分段式内存管理:将一整个的地址空间划分为多个段(代码段,数据段,栈区,堆区…)
作用:更加利于编译器对于地址的管理
段表:是一种数据结构,其中描述的信息,段号,物理内存的一个起始地址
虚拟内存地址:
段号 | 偏移量 |
---|
通过段号找到段表项,得到一块物理内存的起始地址,起始地址+偏移量=实际物理地址
分页式内存管理:将一整的地址空间划分为大量的小分页page(当前一般默认4k字节一页)
作用:实现数据的离散存储,提高内存利用率
页表:页号,物理内存地址起始,缺页中断位,访问权限位…
虚拟地址组成:页号+页内偏移
通过虚拟地址中的页号直到对应的页表项,得到了一个物理内存块的起始地址,加上页内偏移就是实际的访问位置,不同的地方在于应用方向不同,分段式利于地址管理,分页式更倾向于碎片化管理,提高内存利用率和内存访问控制
访问权限位:标记当前地址能够进行什么样的操作
比如:0号地址(NULL)不可读不可写,对其进行解引用或修改,会造成内存访问错误,程序崩溃
比如:const修饰的常变量,只读;代码段的所有地址都是只读
缺页中断:当进程要访问一块内存时经过页表映射法先缺页中断为被置位(意思是这个地址原先的数据现在不在内存中),则会触发缺页中断。
内存交换:一个程序运行就要占据大量内存,内存有限,耗尽时就无法进行任何操作,内存交换就是将内存中的某些数据从内存中移出,放到硬盘,腾出内存空间
当访问数据刚好是刚被移出的数据,就会发生缺页中断,而当发生缺页中断时,内存就又要重新进行内存置换。频繁的置换和缺页中断,会造成性能大大降低,硬盘吞吐量太小
内存置换算法:
LRU–最久未使用 ;LFU–最少使用
交换分区的大小一般是内存的两倍,无具体大小。