1. 冯诺依曼
冯诺依曼体系结构示意图
冯诺依曼的两个重要思想 (1)所有的数据采用二进制存储(契合电路的特性,高低电平) (2)数据都保存在存储器当中(内存当中)
常见的输入输出设备 输入:键盘、鼠标、扫描仪、写板 中央处理器:包括运算器和控制器,作用:算术运算 & 逻辑运算 输出:显示器、打印机 网卡:既是输入也是输出
2. 操作系统
操作系统 = 内核 + 一堆应用 内核的本质就是某种操作系统的代码的统称
操作系统就是管理计算机当中软硬件资源的软件 进程也是用结构体描述的 系统调用接口:操作系统为程序员提供的函数 库函数:将系统调用函数再次封装了一遍,提供出来的函数
3. 进程概念
3.1 进程和程序的区别
程序:本质上就是一个文件,是静态的,存储在磁盘当中 进程:程序运行起来之后,就称之为进程,是动态的,是由操作系统来管理的
3.2 从操作系统内核角度看进程
描述进程:struct task_struct{…}; 也叫进程控制块(PCB) 组织进程:使用双向链表
3.3 查看进程的命令
进程PID:进程号,在当前操作系统中唯一标识一个进程 ./mytest命令:执行mytest这个可执行程序 要想查看进程的信息,不要让进程结束掉,也就是不要让程序执行完 退出死循环:ctrl+z ps aux:可以查看当前操作系统中的所有进程信息 可以使用grep进行过滤找到所需要的进程信息 ps aux | grep [可执行程序名称] 有两个mytest,第一个是grep运行的mytest,第二个才是要找的mytest(./mytest)
3.4 进程状态
运行态:正在拿着cpu资源进行运算的进程,所持有的状态 就绪态:一切的准备资源都准备就绪了,等待操作系统分配cpu资源 阻塞态:等待某种资源到来之后,才能进行运算
细分状态: R:运行状态 S:可中断睡眠状态 D:不可中断睡眠状态 T:暂停状态 t:跟踪状态,当进程被gdb调试的时候,会产生t X:死亡状态 Z:僵尸状态 +代表前台进程,没有+代表后台进程
3.5 进程切换
假设只有一个cpu,要么是A进程执行,要么是B进程执行。 操作系统为了让每一个进程都得到拥有cpu的时间(雨露均沾),那就有一个时间片的概念,操作系统为每一个进程都会分配在cpu中运行的时间。假如给A进程分配100ms,这100ms里A进程就可以在cpu中运行,100ms过了之后,A进程就要被切换出来。(在运行过程中,它要执行汇编指令,在执行的过程中,它执行到某一条指令,100ms时间到了,那程序计数器就要记录它运行到哪一条指令以及上下文信息保存寄存器的值,否则在它下次拥有cpu时,它不知道运行到哪里了,也不知道寄存器里面的值是多少。)进程切换之后,程序计数器就保存了进程即将要执行的下一条指令(汇编指令)以及上下文信息保存寄存器的值
程序计数器:保存了进程即将要执行的下一条指令(汇编指令) 上下文信息:保存了程序运行时的寄存器当中的内容 IO信息 操作系统使用了PCB描述了启动的进程信息,同时也在磁盘当中为正在运行的进程创建了一个文件夹,文件夹的命名是以进程号(pid)命名的,这些文件夹都保存在/proc/[pid]/ 记账信息 cpu使用率,内存使用率,cpu的使用时长
3.6 fork创建子进程
fork函数 pid_t fork(void); 返回值: pid_t < 0:创建失败 pid_t == 0:返回给子进程 pid_t > 0:返回给父进程,大于0的值就是子进程的pid
子进程拷贝父进程的PCB fork调用子进程是在fork之后运行的,如果在fork之前运行,会一直调用子进程,造成死循环。 getpid():谁调用返回谁的进程号 getppid():谁调用返回谁的父进程进程号 子进程的父进程的pid是1的原因是父进程先于子进程退出
可以在父进程里加sleep(3) 如果在代码后面加while(1)循环,父子进程执行完自己的分支判断之后都会执行while(1),那执行while(1)之后父子进程都没有退出。 ps -ef:显示进程的pid和ppid ps -ef | grep [可执行程序名称] 通过 ps aux可以查看进程信息,但是我们并不知道哪一个是父进程,哪一个是子进程,那就需要ps -ef 这个命令,它会显示进程的pid和ppid,这样就能直到父子进程是哪一个。 父进程先运行还是子进程先运行? (1)子进程被创建出来之后,在内核当中是一个PCB,被挂在双向链表当中组织起来的 (2)父进程先运行还是子进程先运行是不确定的,取决于操作系统的调度 (3)先来先服务,抢占式执行 子进程在创建出来之后,子进程的运行和父进程还有关系吗?:没有关系了 子进程在创建出来之后,代码从哪里运行,为什么? 在命令行解释器启动一个进程,该进程的父进程是谁? bash : 命令行解释器
4. 僵尸进程
4.1 模拟僵尸进程
(1)在代码当中创建出来一个子进程;(2)模拟让子进程先于父进程退出(通过fork函数的返回值,让父子进程走不同的分支逻辑,子进程直接退出,父进程别退出( while(1)) 观察:子进程目前的运行状态 在另一个终端打开(双击打开)
4.2 僵尸进程的危害
kill+[pid]:杀死进程 kill -9 [pid]:强杀命令 任何命令都杀不死僵尸进程 僵尸进程在内核当中的task_struct结构体并没有被释放,导致内存泄漏。并且,使用强杀命令也不能将僵尸进程结束掉。
4.3 怎样处理僵尸进程
(1)重启操作系统(不推荐) (2)进程等待 (3)将僵尸进程的父进程kill掉,僵尸进程就会变成孤儿进程,被1号进程所领养
4.4 总结
父进程创建出来一个子进程,子进程先于父进程退出。子进程在退出的时候会向父进程发送一个信号(SIGCHLD),而父进程对于该信号是忽略处理的,导致子进程在退出的时候,没有进程来回收子进程的资源(PCB),子进程就变成了僵尸进程。
5. 孤儿进程
孤儿进程的模拟:(1)父进程创建一个子进程,父进程先于子进程退出,子进程就是一个孤儿进程。(子进程代码当中写一个死循环,父进程直接退出)
子进程变为孤儿进程后,按ctrl+c是不能终止程序的,只能kill掉它
父进程变成了1号进程,当子进程的父进程先于子进程退出之后,子进程会被1号进程所领养。1号进程也称之为:init进程
孤儿进程被1号进程领养了之后,子进程就变成了后台进程
6. 环境变量
6.1 为什么需要环境变量?
环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数
6.2 常见的环境变量
PATH:指定可执行程序的搜索路径 HOME:指定用户的主工作目录(即用户登录Linux系统中时,默认的目录) SHELL:当前Shell,它的值通常是/bin/bash
6.3 环境变量的文件
系统级别的:/tec/bashrc (不推荐直接更改这个文件) 用户级别的:更改环境变量的时候,推荐修改以下两个文件 ~/.bashrc ~/.bash_profile 使用echo $PATH查看环境变量的文件,并用which+命令,查看某个命令在哪个环境变量路径中 使用env可查看所有的环境变量
6.4修改环境变量
命令范式:export 环境变量名称=$环境变量名称:[新添加环境变量的值] 临时修改:直接在命令行当中执行修改命令,只针对当前终端有效,当终端重启之后,也就失效了。 永久修改:将修改命令直接写到环境变量文件当中,使用source+[环境变量文件]使其立即生效。
6.5 环境变量的组织方式
6.6 代码获取环境变量
方法1: main()函数参数 int main(int argc, char* argv[], char* envp[]) agrc:命令行参数的个数,本质上是argv数组的元素个数 argv:具体的命令行参数 envp:环境变量的值 方法2: environ libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时,要用extern声明 方法3: char* getenv(const char* name); 参数:char* , 含义:传递环境变量的名称 , 返回值:返回环境变量的值
7. 进程虚拟地址空间
(1)定义一个全局变量 g_val = 100 (2)父进程创建一个子进程 (3)分别打印父进程和子进程中全局变量的值和地址 父进程和子进程中变量的值和地址是一样的,但是我们现在所看到的地址并不是真正的物理地址,而是虚拟内存地址 在C/C++代码当中使用&符号获得的地址,都是操作系统虚拟出来的地址,而并非真正的内存条当中的物理地址 页表分两页,一页是虚拟地址的映射,一页映射到物理内存
7.1 如何将虚拟地址转化为物理地址
7.1.1 分页式
7.1.2 分段式
7.1.3 段页式
进程间的运行是抢占式执行 使用fork创建出来的子进程,父子进程在执行各自代码的时候,也是抢占式执行的
8. 并行和并发
并行:多个进程同时拥有不同的CPU,进行运算,称之为并行 并发:多个进程在同一时刻只能有一个进程拥有CPU,进行运算,称之为并发
进程独立性 多进程运行,需要独享各种资源,多进程运行期间互不干扰 父进程创建出子进程之后,子进程就和父进程没有任何关系了,各自独立运行
|