冯诺依曼体系
所有的硬件都是围绕存储器工作的
管理者并不需要直接与被管理者交互,而是通过对被管理者描述,并且将描述信息得当的组织起来进行管理
库函数与系统调用接口的关系:
库函数是系统调用及接口的一层封装,是上下级的调用关系
进程概念
用户层面:
进程就是运行起来的程序
从操作系统层面理解:
1.程序运行需要将代码数据加载到内存中
2.将内存中的所有程序描述,再处理
在操作系统层面:
进程就相当于操作系统对一个运行的程序的描述信息,这个描述信息叫做–PCB(进程控制块);在linux中是通过struct task_struct的结构体作为PCB
linux是怎么实现多个进程同时运行的
采用的是CPU分时机制
**CPU分时机制:**对程序运行处理进行切换调度处理,让所有的程序能同时推进
时间片是CPU在其中一个一个程序上所运行的这段时间
CPU通过PCB来了解上一次运行该程序运行到的位置
CPU–>OS–>PCB–>程序
PCB中的描述信息:
内存指针
程序计数器
上下文数据
标识符(process ID)
状态
优先级(CPU资源使用的的优先级)
记账信息
IO信息
查看进程
ps -aux或者ps -ef 查看所有进程
ps -aux | grep name 查看具体某个进程的信息
进程创建
子进程的创建,通过fork完成
操作系统通过复制父进程来创建子进程,因此父子进程数据独有,代码共享
创建子进程的意义:
分摊任务处理压力,让子进程处理其他任务----提高稳定性
可以通过分别创建父子进程来让子进程处理其他任务
返回值:
对于父进程来说,fork返回值是子进程的pid(>0);创建子进程失败时返回-1
对于子进程来说,fork返回值是0
用户可以通过父子进程fork的返回值不同来对父子进程进行分流
vfork():与父进程共用一份虚拟地址空间,同时运行父进程和子进程时会造成调用栈混乱,具体使用方法是阻塞父进程直到子进程exit退出或者程序替换重新开辟自己的内存空间以及虚拟地址空间-------淘汰了,不常用
fork和vfork都是通过内核中的clone函数实现pcb创建拷贝
//进程创建
// pid_t fork(void);
#include <unistd.h>
#include<stdio.h>
int main(){
printf("hello world\n");
//pid_t fork(void);
//创建一个子进程,父进程返回子进程的pid,父进程返回0
pid_t pid=fork();
if (pid<0){
printf("fork error\n");
return -1;
}
else if(pid==0){
//子进程==0
printf("i------am------child---:%d\n",getpid());
}
else{
//父进程>0
printf("i------am------parent---:%d\n",getpid());
}
return 0;
}
进程的状态
1.运行®:并不意味着进程一定在运行,它表明进程正在运行或者在运行队列里
2.可中断休眠(S)
3.不可中断休眠(D)
4.停止(T)
5.僵死(Z)
僵尸进程
处于僵死状态的进程-------导致资源泄露
产生僵尸进程的原因
子进程先于父进程退出,因为要保留退出原因,因此操作系统不能直接释放所有资源
而是通知父进程获取退出原因,这是才可以释放资源,但是父进程没有关注这个通知,导致子进程的资源一直无法释放,处于僵死状态成为僵尸进程
僵尸进程的危害-----资源泄露
避免僵尸进程的方法:
进程等待,退出父进程
孤儿进程
父进程先于子进程退出,子进程变为孤儿进程,在后台运行,父进程变为1号进程
孤儿进程退出后不会变为僵尸进程,而是直接释放资源
守护进程/精灵进程:特殊的孤儿进程(常用d来表示)
进程的终止:进程的退出
进程的退出场景
正常退出, 结果符合预期/正常退出, 结果不符合预期/异常退出
如何退出进程:main函数中的return; 库函数exit(int retval); 接口_exit(int retval)
进程等待:等待子进程的退出
获取退出子进程的返回值; 避免子进程成为僵尸进程
如何等待进程退出
两个接口:wait(int *status)/waitpid(int pid,int *status,int opt)
waitpid接口
非阻塞时常用while循环保证持续关注子进程是否退出
wait接口功能是一直等待子进程退出,子进程退出后,获取到返回值,放入传入的参数status中,
如果一直没有子进程退出,则wait一直处于阻塞状态
status高16位没有使用
statu中低16位中的高8位存储子进程退出返回值
statu中低16位中的低8位中的高1位存储的是core dump标志,核心存储:程序异常退出时保存程序的运行信息,便于之后调试,
statu中低16位中的低8位中的低7位中异常退出信号值为0,表示没有异常退出信号,表示程序正常退出;否则异常退出,返回值无意义
wait等待任意一个子进程退出,waitpid可以等待制定的一个子进程退出,并且可以设置为非阻塞
阻塞:为了完成一个功能发起调用,若当前不具备完成条件,等待到条件具备完成功能后返回
非阻塞:为了完成一个功能发起调用,若当前不具备完成条件,则立即报错返回
获取子进程退出返回值
程序替换
替换一个进程正在运行的程序
重新加载一个新的程序到物理内存中,对一个进程的代码段通过页表在物理内存中的地址进行修改映射关系,让程序的代码段经过页表转换后,指向了新的程序位置,但是不创建新的进程,前后的pid不变
将当前成虚替换为ls-la
int main(int argc, char *argv[], char *env[])
{
printf("leihoua~~~\n");
//int execl(const char *path, const char *arg, ...);
//使用path这个路径的程序,替换当前进程要运行的程序
//让当前进程运行ls这个程序的功能,
//后边arg以及...都是这个程序的运行参数
//execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
//execlp("pwd", "pwd", NULL);//程序在当前路径运行
//int execv(const char *path, char *const argv[]);
//
/*
char *argv[32] = {NULL};//,每个参数都具体给出
argv[0] = "ls";
argv[1] = "-l";
argv[2] = "-a";
argv[3] = NULL;
execv("/usr/bin/ls", argv);
*/
//int execle(const char *path, char *arg, ..., char * const envp[]);
execle("./test", "test", "-l", NULL, env);
printf("nihaoa~~~\n");//其中第一条语句会打印,但是第二条语句不会打印,原因是已经发生了程序替换
return 0;
}
其中第一条语句会打印,但是第二条语句不会打印,原因是已经发生了程序替换
环境变量
环境变量:存储系统运行环境参数的变量—让操作系统的某些操作更加简单
查看环境变量:env set(也可以查看普通变量) echo
常见环境变量:PWD HOME SHELL PATH(重点)
PATH:指定程序默认所在路径
环境变量的特性:全局特性(继承)
shell终端下所有运行的进程能够获取到所有的环境变量,但是获取不到普通变量
通过环境变量名称获取环境变量内容
int main(){
//char *getenv(const char *name);
//通过环境变量名称获取内容
char *ptr=getenv("PWD");
printf("ptr:%s\n",ptr);
return 0;
查看当前所有的环境变量
int main(int argc,char *argv[],char *env[]){
int i=0;
for(i=0;env[i]!=NULL;i++){
printf("env[%d]=[%s]\n",i,env[i]);
}
return 0;
}
extern:声明
extern char **environ; 声明全局变量
export: 声明或设置一个新的环境变量
unset:删除环境变量
使用库中的环境变量时需要声明
int i=0;
extern char **environ;//用extern声明
for(i=0;environ[i]!=NULL;i++){
printf("env[%d]=[%s]\n",i,environ[i]);
}
环境变量的使用场景:
通常是父进程通过给子进程设置环境变量来达到向子进程传递数据的功能
程序地址空间
子进程和父进程代码共享,因此他们处于同一物理地址空间,但是为什么给其中一个赋值时,另外一个并不随之发生变化呢?
进程之间数据独有
我们知道每个进程都需要连续的内存地址,但是连续的物理内存直接利用的话,利用率太低,不相连的内存块大小和足够,但是因为不连续,无法进行程序的运行,所以为了解决这个问题,产生了虚拟地址空间
虚拟地址—虚拟地址空间(也就是内存指针)
我们看到的程序地址空间实际上是一个虚拟地址空间,实际上是操作系统通过mm_struct这个结构体为进程描述的一个空间,因此有时候被称为内存描述符
mm_struct{
ulong mem_size;//内存空间大小
ulong code_start; //开始位置
ulong code_end;//结束位置
}
使用虚拟地址空间的目的:
进程通过访问虚拟地址进而获取变量数据,最终还是要去访问物理内存,因为数据还是存储在物理内存中的.
在虚拟地址和物理地址之间通过页表进行映射,得到物理地址,进而访问物理内存区域
通过映射之后,物理地址不一定连续,通过映射这种转换的方式,提高内存的利用率
页表不但记录了虚拟地址和物理地址的映射关系,还记录了这块地址的属性
虚拟地址空间有什么用
提高内存利用率,增加内存访问控制,保持进程的独立性
进程的独立性
进程应该具有独立性,保证自身不收其他进程影响,更加稳定
创建子进程是都发生了什么?
写时拷贝技术:操作系统通过复制父进程创建子进程,子进程初始时与父进程只想同一块物理内存区域,子进程对当前内存区域只读,当子进程的数据需要发生改变时,会为子进程重新开辟内存空间,并更新页表
页表是如何将虚拟地址转化为物理地址的
虚拟地址的管理:
分段式内存管理:
内存地址的构成:段号+段内偏移
物理地址的管理
1.分页式内存管理:
内页地址的构成:页号+页内偏移
2.段页式内存管理:
内存地址:段号+段内页号+页内偏移
段表项中包含段内页表起始地址
段内页表项中又包含物理页号
具体转化过程
分段式:通过地址中的段号去段表中找到段表项,通过段表项中的物理段其实地址加上地址中的段内偏移获取到物理地址
分页式:通过地址值的页号去页表中找到页表项,通过页表项中的物理页号加上页内偏移获取到物理地址
段页式:通过段号在段表中找到段表项,通过段表项中的段内页表地址找到段内页表,页表中的每一个表目指出本段的逻辑页号与内存物理块号之间的对应关系
题目练习
页号=虚拟地址/内存页大小
即页号从0开始排,
第一个内存页的范围是0~2048
第二个内存页的范围是2049~4096
由上得出5088/2048=2…余993
其中2为页号,992为页内偏移
由图可知,页号2对应的块号为5
一块的大小为2048,所以第五块的起始地址为52048
所以物理地址为:52048+992=11232
内存置换算法
内存大小不够处理数据
swap分区:也叫交换内存,内存不足时,将内存中的数据置换到交换分区中,腾出内存
1.先进先出算法
2.最近最少使用算法
3.最近最久未使用算法