文章目录
冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如大型服务器,大部分都遵守冯诺依曼体系。冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。
数学家冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成(运算器、控制器、存储器、输入设备、输出设备),这套理论被称为冯·诺依曼体系结构。
其中:
输入单元:包括键盘, 鼠标,扫描仪, 写板等
中央处理器(CPU):含有运算器
和控制器
等
运算器:由算术逻辑单元(ALU)、累加器、状态寄存器、通用寄存器组等组成。算术逻辑运算单元(ALU)的基本功能为加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、求补等操作。计算机运行时,运算器的操作和操作种类由控制器决定。运算器处理的数据来自存储器;处理后的结果数据通常送回存储器,或暂时寄存在运算器中
存储器:是用来存储程序和各种数据信息的记忆部件。存储器可分为主存储器(简称主存或内存)和辅助存储器(简称辅存或外存)两大类。和CPU直接交换信息的是主存。
输出单元:显示器,打印机等
计算机发展所遵循的基本结构形式始终是冯·诺依曼机结构。这种结构特点是“程序存储,共享数据,顺序执行”,需要 CPU 从存储器取出指令和数据进行相应的计算。
主要特点有:
(1)单处理机结构,机器以运算器为中心;
(2)采用程序存储思想,存储程序,也就是通过计算机内部存储器保存运算程序。这样,程序员仅仅通过存储器写入相关运算指令,计算机便能立即执行运算操作,大大加快运算效率;
(3)指令和数据一样可以参与运算;
(4)数据以二进制表示,大大加快了计算机速度;
(5)将软件和硬件完全分离;
(6) 指令由操作码和操作数组成;
(7)指令顺序执行。
操作系统概念与定位
操作系统:是管理
计算机硬件与软件资源的计算机程序(或者说是软件)
,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务.
如何管理:管理=描述+组织
- 描述起来,用struct结构体
- 组织起来,用链表或其他高效的数据结构
总结操作系统的理解与定位
1.硬件交互,管理所有的软硬件资源
2.为用户程序(应用程序)提供一个良好的执行环境
3.在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
操作系统主要包括以下几个方面的功能 :
1.进程管理,其工作主要是进程调度,在单用户单任务的情况下,处理器仅为一个用户的一个任务所独占, 进程管理的工作十分简单。但在多道程序或多用户的情况 下,组织多个作业或任务时,就要解决处理器的调度、 分配和回收等问题 。
2.存储管理分为几种功能:存储分配、存储共享、存储保护 、存储扩张。
3.设备管理分有以下功能:设备分配、设备传输控制 、设备独立性。
4.文件管理:文件存储空间的管理、目录管理 、文件操作管理、文件保护。
5.作业管理是负责处理用户提交的任何要求。
系统调用和库函数概念
由上图可以看出从用户空间到操作系统内核空间,只能从系统调用接口进入
,而部分系统调用接口被封装在库函数中,方便使用者调用。
系统调用:在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
库函数:系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
进程概念特征与PCB
基本概念:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
宏观的理解:
从理论角度看
,是对正在运行的程序过程的抽象;
从实现角度看
,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
进程特征
1.动态性
:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
2.并发性
:任何进程都可以同其他进程一起并发执行
3.独立性
:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
4.异步性
:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
5.结构特征
:进程由程序、数据和进程控制块三部分组成。
6.多个不同的进程可以包含相同的程序
:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
程序与进程的区别
1.程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
2.程序可以作为一种软件资料长期存在,而进程是有一定生命期的。程序是永久的,进程是暂时的。
3.进程更能真实地描述并发,而程序不能;
4.进程是由进程控制块、程序段、数据段三部分组成;
5.进程具有创建其他进程的功能,而程序没有。
6.同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一程序可以对应多个进程。
7.在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。
进程的描述-PCB(process control block)
进程是由进程控制块、程序段、数据段三部分组成
。
进程信息被放在一个叫做进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。Linux操作系统下描述进程的结构体叫做task_struct。
task_struct:是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
所有运行在系统里的进程都以task_struct链表的形式存在内核里。
task_ struct内容分类
标示符:
描述本进程的唯一标示符,用来区别其他进程。
状态
: 任务状态,退出代码,退出信号等。
优先级
: 相对于其他进程的优先级。
程序计数器
: 程序中即将被执行的下一条指令的地址。
内存指针
: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据
: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息
: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息
: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
查看进程信息
格式: /proc/[pid]
如:要获取PID为1的进程信息,你需要/proc/1
大多数进程信息同样可以使用top和ps这些用户级工具来获取
通过系统调用获取进程标示符
进程id(PID) 当前进程的进程标识符getpid()
父进程id(PPID) 父进程的进程标识符 getppid()
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid()); //当前进程的进程标识符
printf("ppid: %d\n", getppid()); //父进程的进程标识符
return 0;
}
通过系统调用创建进程-fork初识
运行man fork 认识fork
fork返回值
如果成功,则在父进程中返回子进程的PID,在子进程中返回0。
如果失败,则返回-1在父进程中,不创建任何子进程,并且适当地设置了errno
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
操作系统是使用双向链表结构来组织进程pcb的,
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid = fork(); //使用系统调用创建子进程
if(pid < 0){ //由于内存不够创建失败 子进程pcb是需要内存的
perror("fork");
return 1;
}
else if(pid == 0){ //child 子进程
printf("I am child : %d!, fatherid: %d\n", getpid(), getppid() ); //子进程的pid 与其父进程的pid
}else{ //father 父进程
printf("I am father : %d!, fatherid: %d\n", getpid(), getppid() );e
}
sleep(1);
return 0;
//其中父子进程是抢占式执行的
//在fork后,进程是从fork的下一条指令开始执行的,这是因为进程执行时是按照程序计数器
取指令的,其保留了下一条指令,所以子进程被创建后是从下一条指令开始的,不然就一直进入入口的fork函数,进入死循环
}
注意多个进程的执行顺序是由系统具体的调度算法实现的:
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
进程状态
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
僵死状态(Zombies是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
可以通过系统调用 kill 来结束进程
kill -9 [pid] -9参数代表强制删除
僵尸状态如何处理呢?
可以将其父进程杀掉,不让父进程来等待这个僵死的子进程退出,而是让其变为孤儿进程,由1号进程来专门领养,在内核中回收释放该孤儿进程的pcb资源。
孤儿进程(不是状态,而是进程种类了)
父进程先退出,子进程就称之为“孤儿进程”,孤儿进程被1号init进程领养,就由init进程回收。达到释放孤儿进程资源的工作。
环境变量
基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境
的一些参数.如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。环境变量通常系统当中通常具有全局特性.
通俗来说,就是 在当前目录环境下(配置好了的)来执行编译好了的二进制可执行文件使用命令 ./fork(带了路径的)和单纯的 fork (没带路径的)结果如下图。
但是 像 ls ,cd,等命令为啥也没带路径但是也可以执行呢,就是因为他们配置了环境变量的原因。
常见环境变量
PATH : 指定命令的搜索路径 [重点]
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)[重点]
SHELL : 当前Shell,它的值通常是/bin/bash。
查看环境变量方法
echo $NAME //NAME:你的环境变量名称[重点]
环境变量认识
环境变量的组织方式
每个程序都会收到一张环境表,环境表是一个字符指针数组
,每个指针指向一个以’\0’结尾的环境字符串.
通过代码如何获取环境变量
//命令行第三个参数
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++){
printf("%s\n", env[i]);
return 0;
}
//通过第三方变量environ获取
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;//libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
return 0;
}
//通过系统调用getenv获取或设置环境变量
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
程序地址空间
以前我们认知的空间布局图
进程控制块对应的虚拟进程地址空间
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 10;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 0){ //child,子进程先修改,完成之后,父进程再读取这个值
g_val=100;
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);//值和地址
}
else{ //father
sleep(3); //先让父进程睡三秒,等待子进程先执行
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);//值和地址
}
sleep(1);
return 0;
}
结果显示如上图,两者地址相同,但是值却不同,以我们之前的思维想,这肯定是不可能的呀!
分析如下:
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
但地址值是一样的,说明,该地址绝对不是物理地址(说明那就是虚拟地址咯)!
在Linux地址下,这种地址叫做虚拟地址,
我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理OS必须负责将虚拟地址转化成物理地址。
一张图理解虚拟地址与物理地址的映射及写时拷贝
页表:保存虚拟地址与物理地址的映射信息
程序中子进修改了变量的值,因为时写时拷贝,故系统有为其在虚拟地址空间对应的逻辑地址上重新映射一个(另开辟的)新的物理地址,并把原来页表中映射关系更新
再由此图和前面的理解来分析:同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!