目录
1. 冯诺依曼
计算机的硬件体系结构
五大硬件单元:输入设备(如:键盘),输出设备(如:显示器),运算器,控制器
所有的设备都是围绕内存工作的。CPU不能直接从输入设备取数据,而是要去存储器取数据(存储器最主要的工作就是数据缓冲)
- 例子:QQ聊天发送消息
键盘输入信息(电信号转换为数字信号)键盘捕捉到我们的输入信息——>放内存缓冲起来——>交给CPU对信息进行处理(翻译输入了哪些字符)——>处理好了再次发给存储器——>通过输出设备网卡(通过网络进行发送信息,将我们的数字信号再次转换为电信号)——>到达对方电脑,对方的网卡接受我们的信号——交给CPU (处理信息)——>放入内存进行输出——>输出设备显示器
2. 操作系统
- 概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。
笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)
- 设计OS的目的
与硬件交互,管理所有的软硬件资源为用户程序(应用程序)提供一个良好的执行环境
- 定位
在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
3, 进程概念
-
进程基本概念
用户的角度:是运行中的程序。
操作系统的角度:是程序运行的动态描-PCB,其中包含程序运行的各项信息,实现操作系统对于运行中程序管理。(pcb描述信息:标识符-PID,内存指针,程序计数器,上下文数据进程状态,进程优先级,IO信息,记账信息)
linux下:进程就是task_struct结构体。是操作系统对程序运行的描述,通过这些描述完成对进程的管理。
课本概念:程序的一个执行实例,正在执行的程序等(程序:软件——就是程序员所写的代码,程序本质上都存储在硬盘,因为程序都存储在文件中,文件存储在硬盘里)。
内核观点:担当分配系统资源(CPU时间,内存)的实体。 -
描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
4. 进程状态
4.1 概念
-
描述当前进程处于什么状态应该如何被操作系统调度管理。
-
进程状态种类:就绪、运行、阻塞。
4.2 linux下的进程状态分类:
- 运行态-R:正在运行的,以及拿到时间片就能运行的。
- 可中断休眠态-S:能够被打断的休眠状态。
- 不可中断休眠态-D:不可被打断的阻塞状态(最典型的就是磁盘休眠)
- 停止态-T:停止运行。
- 僵尸态-Z:程序退出后的中间等待处理状态。
僵尸进程:僵尸态的进程,退出后资源没有完全被释放的进程 。
产生:子进程先于父进程退出,为了保存自己的退出返回值,因此没有完全释放资源,等待父进程处理。
避免:进程等待。
处理:退出父进程。
危害:资源泄露(内存+进程数量)。
5 进程创建
5.1 fork函数初识
输入命令:man fork
通过复制调用进程(谁调用他就复制谁)来创建一个子进程。子进程从fork之后进行运行。(这个复制是复制了大部分信息,不是复制全部信息比如进程ID就不会复制的。)
返回值:如果成功复制子进程则返回子进程PID,在父进程中返回子进程PID ;在子进程中返回0;失败返回-1。
- 基本使用
- vi main.c :只打印一个“leihoua~~”信息
vi main.c
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 printf("leihoua~~\n");
8 return 0;
9 }
编译一下:
gcc main.c -o main
运行一下
./main
- 修改main.c :在打印信息之前进行fork();
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 fork();
8 printf("leihoua~~\n");
9 return 0;
10 }
~
编译运行一下(步骤如一种)
父进程和子进程都打印了信息。fork()之后他们俩同时从fork之后运行程序。
3. 再次修改main.c:把print函数放到fork函数之前
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main()
6 {
7
8 printf("leihoua~~\n");
9 fork();
10 return 0;
11 }
编译运行:
我们发现只输出一次打印信息。因为子进程是fork之后才创建的,fork之后父进程和子进程都运行了return 0.
4. 为什么要创建子进程?
(1)分摊任务处理压力。(2)可以让子进程做另一件事情。这种最常用。
继续修改main.c 根据fork函数的返回值来区分父子进程
vi main.c
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int main()
6 {
7
8 pid_t pid = fork();
9 if(pid >0)
10 {
11 printf("i m parent\n");
12
13 }else if(pid == 0)
14 {
15 printf("i m child~\n");
16
17 }
20 printf("hello !\n");
23 return 0;
24 }
编译运行一下
5.2 僵尸进程
产生原因:子进程先于父进程退出,为了保存退出原因,因此等待父进程获取状态释放资源。
危害:资源泄露(内存资源的泄露,进程数量的占有)
处理:退出父进程。
- vim main.c 代码
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7
8
9 pid_t pid = fork();
10 if(pid > 0)
11 {
12
13 printf("i m parents~\n");
14
15 }else if(pid == 0)
16 {
17 printf("i m a child\n");
18 sleep(6);
19 exit(0); //退出进程;
20 }
21 while(1){
22
23 printf(" i m a zoomProcess");
24 sleep(1);
25 }
26 return 0;
27 }
- vim makefile代码
3 main:main.c
4 gcc $^ -o $@
- 输入:make
从上图可以看出我们无法进行任何其他操作,必须中断这个僵尸进程,才能继续输入命令。所以我们必须结束这个僵尸进程。
- 如何关闭僵尸进程(kill是杀不死僵尸进程的,我们只能关闭僵尸进程的父进程)
(1)查看僵尸进程的父id
输入:ps -ef
(2)查看进程状态(比较详细信息)
输入:ps -aux
(3)筛选一下我们的进程main
输入:ps -aux | grep main
(4)由于我的shell无法通过ctrl +C进行进程终止,我都是使用Ctrl+Z结束了我的僵尸进程。可以看到上图很多Z状态进程的父进程(就是Z上面的T)都是T状态。为了杀死T状态的进程我这里使用了 kill -9 PPID
5.3 孤儿进程
产生原因:父进程先于子进程退出,子进程就会成为孤儿进程。
特性:运行在后台,父进程变为1号进程(孤儿进程退出后不会成为僵尸进程)。
守护进程:特殊的孤儿进程,在孤儿进程的基础上脱离与终端之间的关系(比如新建回话)。
- vim main.c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7
8
9 pid_t pid = fork();
10 if(pid > 0)
11 {
12
13 printf("i m parents~\n");
14
15 sleep(6);
16 exit(0); //退出进程;
17 }else if(pid == 0)
18 {
19 printf("i m a child\n");
20 }
21 while(1){
22
23 printf(" i m a orphonProcess");
24 sleep(1);
25 }
26 return 0;
27 }
-
makefile和不用变
-
继续make一下执行 ./mian
此时我们发现父进程退出了,但是我们仍然可以执行命令。此时子进程并没有,而是shell占据终端。但是它并没有退出,而是在后台继续运行。 -
查看孤儿进程的父进程 输入命名 ps -ef | grep main
孤儿进程的父进程就变成了1号进程。
6. 环境变量
保存程序运行环境的变量,特性:具有进程之间的传递性。
6.1 常见环境变量
- PATH : 指定命令的搜索路径 (程序默认的运行路径)
比如现在我们在shell中输入mian它就不能直接运行,因为shell运行文件是要在指定路径下运行,我们需要告诉shell,main文件在哪里,我们输入 ./main ,此时才可以运行。
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash。
6.2 和环境变量相关的命令
-
env: 显示所有环境变量
-
echo: 显示某个环境变量值(打印某个指令变量的数据)
-
export: 设置一个新的环境变量(声明一个环境变量)
-
set: 显示本地定义的shell变量和环境变量
-
unset: 清除环境变量
6.3环境变量的接口
- 通过系统调用获取环境变量:char *getenv(char *name),输入:man getenv
name :环境变量名称;
返回值:对应name环境变量的数据,如果找不到返回NULL
6.3 例子,获取环境变量的 PATH 的值
- vim env.c
1 #include<stdio.h>
2 #include <unistd.h>
3 #include<stdlib.h>
4
5
6 int main()
7 {
8 char *ptr = getenv("PATH");
9 if(ptr == NULL)
10 {
11 printf("there is no PATH\n");
12 }else {
13 printf("%s\n",ptr);
14 }
15
16 return 0;
17 }
- makefile
2 env:env.c
3 gcc $^ -o $@
- make
6.4 环境变量的特性具有进程之间的传递性的例子
- vim env.v
1 #include<stdio.h>
2 #include <unistd.h>
3 #include<stdlib.h>
4
5
6 int main()
7 {
8 char *ptr = getenv("MYVAL");
9 if(ptr == NULL)
10 {
11 printf("there is no MYVAL\n");
12 }else {
13 printf("%s\n",ptr);
14 }
15
16 return 0;
17 }
- makefile文件和上一个例子一样,不用变。make一下。体会环境变量相较于普通变量的传递性:
7. 程序地址空间
本质上就是操作系统为进程通过mm_struct描述的虚拟的地址空间,让每个进程都能访问一个完整独立的连续的虚拟地址,经过映射之后,实现物理内存上的离散存储,提高内存利用率。提高内存访问控制。
7.1 虚拟地址空间
同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址