冯诺依曼体系结构
现代计算机中的硬件体系结构:包含输入/输出设备、存储器、中央处理器【运算器,控制器】。所有的设备都是围绕存储器工作的,存储器是所有设备之间的一个缓冲带。
存储器:
- 内存(吞吐量更高,但断电后数据丢失)
- 硬盘(不会丢失)
操作系统
核心功能:统筹管理计算机上的软硬件资源,管理(仅管理信息)先描述再组织。
作用:操作系统向上系统调用接口向下通过硬件驱动程序管理多个硬件。
进程
概念:进程就是一个运行中的程序,由于操作系统将运行中的程序描述起来,所以进程通过描述信息控制程序的运行。
程序:高级语言代码–>编译后的机器二进制代码
描述信息就是操作系统调度一个程序运行的实体,所以操作系统中进程就是运行中程序的描述——pcb【进程控制块】
操作系统通过pcb控制管理程序的运行—linux下为结构体:struct task_struct{…}
结构体中包含:
- 内存指针
- 上下文数据
- 程序记录器
- 进程标识符(PID)【编号】
- 进程状态
- 进程优先级(交互式>===>程序优先分配)
- 进程文件状态信息
- 记账信息(cpu在各程序上运行时间,以动态调整)
cpu分时机制:让cpu在每一个程序上只运行很短的一段时间(时间片),切换下一个程序处理,给人多个程序同时运行的假象。
查看当前操作系统中所有进程信息:
ps -aux #更详细
ps -ef
创建进程:pid_t fork()
在内核中创建一个task_struct结构体,子进程结构体中会复制父进程结构体中所有信息,实现父子进程代码共享,数据独有。
注意: 使用pid_fork()创建,返回值<0:创建失败;,返回值==0:子进程返回;返回值>0:父进程中返回子进程pid,子进程返回0,父进程返回子进程。
int main()
{
pid_t pid = getpid();//获取当前进程pid
pit_t child_pid=fork();//创建子进程
int val=100;
if(child_pid==0)
{
val=10;
//输出10
}else if(child_pid>0)
{
//输出100
}
进程状态
进程状态:就绪、运行、堵塞。
linux下状态:
- 运行状态:R 正在运行+就绪(拿到时间片就运行)
- 可中断休眠状态 S
- 不可中断休眠状态: D 只能等待条件满足后醒来
- 停止状态:T 停止运行,什么都不做
- 死亡状态: X
- 僵死状态: Z
- kill:杀死进程,kill 9:强杀
僵尸进程:子进程先于父进程退出,若父进程没有关注子进程退出状态,则子进程进入僵尸状态。子进程退出之后并没有完全释放资源,保存退出返回值(退出原因),操作系统检测到后会通知父进程,但父进程没有关注这个通知,所以操作系统无法直接释放子进程所有资源,子进程会进入僵死状态成为僵尸进程。
僵尸进程的危害:资源泄漏
如何处理:退出父进程
如何避免僵尸进程的产生:进程等待
nums/zombie/orphan/g :将第num行内容中zombie字符串替换成orphan字符串
num1,num2/zombie/orphan/g : 从num1到num2进行内容替换
孤儿进程
父进程先于子进程退出,则子进程会成为孤儿进程,运行于后台,被1号init进程领养。
守护/精灵进程:
是一个特殊的孤儿进程,完全独立的运行在系统后台,脱离终端的影响。(…d)
#include <unistd.h>
int main()
{
pid_t pid = getpid();//获取当前进程pid
pit_t child_pid=fork();//创建子进程
if(child_pid==0)
{
//子进程
}else if(child_pid>0)
{
//父进程
}
while(1)
{
printf("-------%d---fpid---\n",getpid(),pid);
}
return 0;
}
关于创建进程:可以通过返回值对父子进程的运行进行分流,让其各自运行一段代码
**优先级:**决定cpu优先分配权的等级,让操作系统运行更良好。
nice/renice PRI=PRI+NI,nice值越高,PRI越高,优先级越低,设置nice值:
nice -n
renice -n -p pid
按性质划分程序:交互式和批处理
环境变量
保存系统运行环境参数的变量(仅当此生效)
作用:配置系统运行环境参数可以使系统配置起来更加灵活;可以通过环境变量给一个进程传递参数【环境变量具有全局性,可以由父进程传递给子进程】
操作命令:
- env:查看所有的环境变量
- set:查看所有变量
- echo:直接打印指定变量的值
- export:声明一个环境变量
- unset:删除一个变量
int main(int argc,char*argc[],char*env[])
# 程序运行参数 环境变量
{
int i;
for(i=0;i<argc;i++)
printf("argc[%d]==%s",i,argc[i]);
}
MYVAL=1000
echo $PATH
echo ${PATH}
export MYVAL
./env|greap MYVAL
典型的环境变量
PATH:程序运行的默认搜索路径
SHELL/USER/PWD/HOME…
获取环境变量:
int main(int argc,charargc[],charenv[])
extern char environ //全局变量
char getenv(const char name) //名称获取
创建一个进程,子进程可以获取到所有的环境变量。
设置环境变量:
putenv/setenv
程序地址空间
进程的虚拟地址空间(程序不占内存,运行产生进程后才有地址)。主要通过页表映射访问不连续的内存空间。因为直接访问物理内存,进程之间内存访问缺乏控制,内存的利用率低。
内存地址:内存区域的编号
在程序中看到所有地址都是虚拟的,程序地址空间是一个虚拟地址空间。
操作系统为用户虚拟了一个完整的、连续的地址空间mm_struct,避免用户直接操作物理内存。
如何将虚拟地址映射转换为物理地址
1.分段式内存管理
内存地址:段号+段内偏移
段表内获取 物理内存段起始地址—>物理地址
对程序编译时内存管理更加友好
2.分页式内存管理
内存地址:页号+页内偏移
物理块号
主要用于提高内存利用率
实现:
3.段页式内存管理
将程序地址空间进行分段式管理,但在每个段内进行分页式管理
内存地址:段号+段内页号+页内偏移
实现:
每个进程都有自己独立的虚拟地址空间,访问的都是虚拟地址、通过页表将虚拟地址映射到物理内存地址,进而访问物理内存区域实现了物理内存的离散式存储
通过页表映射转换实现了数据在物理内存中的离散式存储—提高了内存利用率。
通过页表中进行访问限制,实现内存访问的控制。
每个进程都有自己独立的虚拟地址空间,访问的是自己的虚拟地址,因此进程间具有独立性。
进程调度算法:大O(1)调度算法[多级反馈]
进程控制:
进程创建 - 进程终止 - 进程等待 - 程序替换
进程创建:
pid_t fork(void)
流程:1.创建pcb 2.复制信息 (clone())3.内存数据发生改变时为子进程重新开辟空间,拷贝数据(写时拷贝技术)
pid_t vfork(void)—创建子进程,父子进程公用同一个虚拟地址空间,使用同一个栈会造成调用栈混乱。
创建子进程的存储:
因此父进程调用vfork创建子进程,会被阻塞,直到子进程exit退出/进行了程序替换,重新创建了自己的虚拟地址空间后,父进程才会vfork返回继续运行。return退出会释放虚拟内存空间。
进程终止
进程退出的场景:
- 正常退出,结果符合预期
- 正常退出,结果不符合预期
- 异常退出,结果不能作为判断标准
#include<string.h>
//int error; 保存每次系统调用错误编号
//perror(char* info) 打印错误信息在info之后
//strerror(i); 根据给定的errno获取错误描述信息
int main()
{
for(int i=0;i<133;i++)
{
printf("%d"---"%s",i,strerror(i));
}
}
进程的返回值仅用了一个字节保存
1.main()函数中的return //数据写入文件
2._exit(int state)系统调用接口,二号手册 man 2 exit //直接释放缓冲区
3.exit(int state) 库函数, 三号手册 man 3 //数据从缓冲区写入文件
pid_t pid=vfork();
if(pid==0)
{
//子进程;
printf("child");
exit(99);//打印child
//_exit(99);//不打印直接退出
}else
{
//父进程;
}
进程等待:
等待子进程退出,获取子进程的返回值,允许操作系统释放子进程资源,避免产生僵尸进程。
如何等待:pid_t wait(int *status) 等待任意一个子进程退出,通过status获取返回值,释放子进程资源,退出返回子进程的pid.【阻塞函数】
pid_t waitpid(int pid,int *status,int options);
pid>0:等待指定子进程;-1:等待任意一个子进程
options:0:默认阻塞等待子进程退出;WNOHANG:非阻塞
返回值=0表示当前没有子进程退出,-1出错
int status;
while(waitpid(pid,&status,WNOHANG)==0)
{
#做其他事情并不断判断子进程是否释放
....
sleep(2);
}
printf("%d",status);
#do....
这两个wait并不是子进程退出的时候才能去回收,而是只要已经有退出的子进程就可以去回收。
【阻塞:为了完成一个功能发起调用,但是当前不具备完成功能的条件,则一直挂起,直到条件满足功能条件后返回】
【非阻塞:为了完成一个功能发起调用,但是当前不具备完成功能的条件,则立即报错返回】
返回值的获取:
只有进程正常退出时,获取返回值才有意义
判断是否正常:通过status数据的低7位是否为0,若为0,正常退出
返回值:存储在status中低16位中高八位
获取异常退出信号值:status&0x7f
获取程序退出返回值:(status>>8)&0xff
WIFEXITED(status)-判断是否正常退出
WEXITSTATUS(status)-返回值
if(!(status&0x7f))
{
(status>>8)&0xff;
}
if(WIFEXITED(status))
{
WEXITSTATUS(status);
}
程序替换
替换一个进程正在运行的程序
在一个进程中,可以先将另一端程序加载到内存中,将自己的页表映射到新的程序位置,初始化pcb中
的虚拟地址空间中的代码段和数据段信息
int exevce(char* filename,char* argv[],char* env[])
filename:新的程序代码文件名称(带路径的文件名)
argv:新程序运行参数信息
env:新程序环境变量信息 成功->运行新程序;失败->返回-1
替换过程:
int execl(char* path,char* arg,…)//需要新程序的路径
int execlp(char* file,char* arg,…)//只需文件名(去PATH指定路径找程序)
int execle(char* path,char* arg,…char*env[])//env:为新程序指定环境变量
int execv(charpath,charargv[])
int execvp(char* file,char* argv[])
自主实现minishell,完成shell基本功能
换行输入到标准输入缓冲区
fgets–标准输入缓冲区获取(包括换行)
#include<ctype.h>
#include<sys/wait.h>
int main()
{
while(1)
{
//输入
fflush(stdout);
char buf[1024]={0};
fgets(buf)
buf[strlen(buf)-1]='\0';//将换行符换为结尾标志
//字符串解析
char* prt=buf;
char* argv[30]={NULL};
int argc=0;
while(*ptr!='\0')
{
if(!isspace(*ptr))
{
argv[argc++]=ptr;
while(*ptr!='\0'&&!isspace(*ptr))
{
*ptr='\0';
}
ptr++;
}
argv[argc]=NULL;//最后一个参数以NULL结尾
pid_t pid=fork();
if(pid==0)
{
execvp(argv[0],argv);//子进程进行程序替换
exit();
}
waitpid(pid,NULL,0);//阻塞等待子进程
}
return 0;
}