学习目标:
1、计算机的基本组成
2、进程概述
3、简单分页的内存管理
4、C程序两个问题
学习内容:
1、计算机的基本组成
冯·诺依曼体系 计算机有五大部件组成:
- 计算器
- 控制器
- 存储器 – 内存、主存
- 输出设备 – 磁盘、显示器 -output
- 输入设备 – 磁盘、键盘、鼠标 -input
IO 输入、输出。读取或写入的效率相对于内存操作,是比较低的 (CPU - 一级缓存 - 二级缓存 - 内存 - IO)
系统总线类型
- 控制总线: 由于各个连接数据总线和控制总线的部件都是共享这两个总线的,所以他是用来指定某一时刻由谁来控制总线的
- 数据总线 : 用于在各个功能部件间传输数据的,是双向传输总线
- 地址总线: 用于支出数据总线上的源数据或目的数据在主存单元的地址或者IO设备的地址 --》 地址总线决定了CPU的寻址能力
扩充:32位机器 CPU的计算能力,一次能够计算的数据的数据宽度,ALU的宽度,一次能处理的数据的最大位数(32位 – 最大的处理数字)
- x86(32位) 指针都是4字节; x64(64位)指针都是8字节 - 指令:地址码+操作码
2、进程概述
进程的概念:一个正在运行的程序。(程序:二进制可执行文件)
- 进程是一组有序指令+数据+资源(内存资源、CPU资源)的一个集合。
执行一个程序,使之成为一个进程:操作系统先得分配一个PCB变量,再需要将二进制文件加载到内存上,操作系统使用调度策略来分配CPU去执行加载到内存上的指令和数据。
操作系统如何管理进程:
- 将进程相关的一些属性信息(一组)保存到一个地方(内存) — 通过C语言的结构体将这些属性信息组织在一块 — PCB(进程控制块)task_struct
- PCB:即是进程控制块,是进程存在的唯一标志,用来描述进程的属性信息。
- 操作系统维护管理所有的PCB是通过:双向循环链表(任务队列 task list)(有优先级的队列)
- 进程结束时,操作系统先释放进程主体,再释放PCB。
- 孤儿进程:父进程拥有子进程,子进程还在运行,但是父进程提前结束了,此时子进程就成了孤儿进程。
孤儿进程会被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 - 僵尸进程:PCB 还在,进程实体已经释放了。
僵尸进程的处理:
当子进程终止时,内核就会向它的父进程发送一个SIGCHLD信号,父进程可以选择忽略该信号,也可以提供一个接收到信号以后的处理函数。系统默认动作是忽略该信号。所以可以在父进程接收到SIGCHLD信号后就应该调用wait 或 waitpid函数对子进程进行处理,就可以释放子进程占用的资源
不过有些时候,当有很多个子进程都结束了的时候,内核都会向父进程发送SIGCHLD信号,而父进程此时有可能仍然处于接受该信号并进行处理的函数中,那么在处理完之前,中间接收到的SIGCHLD信号就会丢失,内核并没有使用队列等方式来存储同一种信号,所以为了解决这一问题,我们需要调用waitpid函数来清理子进程
- 进程的状态
就绪:所有的条件,资源都准备完成,只等CPU空间来调度执行。
执行:正在被CPU执行的其中的命令
阻塞:所需要某些事件(条件)还没好
- 并发与并行
更多是并发编程。
3、简单分页的内存管理
简单分页:将内存空间划分为大小相等的(相对于内存大小而言,这个大小很小,比较经典的就是4k)区块,将其称为页帧。对所有的页帧可以编号。4G内存有2^20个页帧。
操作系统为每一个进程维护一个页表。页表中记录了该页加载到内存上的那个页帧,还有一些控制信息。
编译链接:连接过程合并所有的段,调整段的大小和起始位置,合并符号表,进行符号解析,给符号分配虚拟地址,符号重定位。
程序中的地址都是虚拟地址,在程序运行时,需要通过虚拟地址找到内存上的一个确定位置。除了与这个虚拟地址有关,还与进程的页表有关。
交换分区
在磁盘上开辟一块空间作为对内存的补充。
一个进程并不需要所有的页都在内存上驻留。
可以执行比内存空间还大的一些进程。也可以执行更多的进程。
操作系统发展史
操作系统
管理计算机上的软硬件资源,为用户提供一个交互的窗口。
串行处理 — 批处理 — 多道程序设计 — 分时系统 — 实时系统
4、C程序两个问题
主函数的参数
主函数默认至少接受一个参数,就是执行程序命令本身。
传递参数时,按照空格来区分各个参数。
传递的参数的类型都是字符串,与用户给定的参数的类型无关。
#include<stdio.h>#include<string.h>#include<stdlib.h>/* envp:是一个字符指针数组,环境变量 */intmain(intargc,char*argv[],char*envp[]){inti=0;while(envp[i]!=NULL){printf("%s\n",envp[i]);i++;}return0;}#if0/* argc:整形 传递的参数个数 argv:字符指针数组,每一个数组都是一个字符指针(字符串) 传递的参数列表 */intmain(intargc,char*argv[]){printf("%d\n",argc);intnum=0;sscanf(argv[1],"%d",&num);printf("num=%d , num*num =%d\n",num,num*num);for(inti=0;i<argc;i++){printf("%d:%s\n",i,argv[i]);}return0;}#endif输出缓冲区
在Linux系统上,一个进程默认打开第三个文件:
标准输入stdin 标准输出 stdout 标准错误输出stderr
printf函数仅仅是将内容写到标准输出缓冲区中,等满足一定条件,才会将内容输出到界面上:
- 遇到 “\n”
- 进程结束时
- 主动刷新 fflush(stdout);
- 缓冲区满
#include<stdio.h>#include<unistd.h>#include<stdlib.h>intmain(){printf("hello");//printf仅仅是将内容写到输出缓冲区中sleep(3);printf("world");//结果是一块输出exit(0);}exit和_exit的区别
exit会刷新缓冲区的内容;
_exit不会刷新缓冲区,直接结束进程。
atexit函数
atexit注册函数:向操作系统注册一个函数,这个函数当前不会被调用,程序结束时,调用exit之后,exit函数退出之前,调用之前注册的函数。
#include<stdio.h>#include<unistd.h>#include<stdlib.h>voidfunc1(){printf("func1...\n");}intmain(){atexit(func1);atexit(func2);printf("main start\n");sleep(2);printf("main end\n");exit(0);}若遇到_exit,则不会调用:
也可以使用atexit调用多个函数(最多注册32个函数),进程结束时,按照出栈的顺序来调用: