目录
1.冯诺依曼体系结构
小结:
2.操作系统
概念:
结构示意图:
理解操作系统:
用户使用底层硬件层次图:编辑
3.进程
概念
结构示意图
task_ struct内容分类
典型用法示例
观察进程:
了解 PID PPID
查看进程
1.冯诺依曼体系结构
- 输入设备:键盘、鼠标、摄像头、话筒、磁盘、网卡等,这些设备用于向计算机输入数据和指令。
- 输出设备:显示器、声卡、磁盘、网卡等,用于将计算机处理的结果输出展示。
- CPU:由运算器和控制器组成,是计算机的核心部件,负责数据的运算和控制计算机各部件协调工作。
- 存储器:内存,用于临时存储计算机正在运行的程序和数据。
CPU 不和外设直接打交道,CPU 只和内存打交道。
外设 (输入和输出) 的数据,不是直接给 CPU 的,而是先要放入内存中。
程序由代码和数据组成,未被加载到内存时,以二进制文件形式存储在磁盘等外部存储设备中。
数据流动:数据在计算机体系结构中流动并进行加工处理,从一个设备到另一个设备本质上是一种拷贝操作。
数据设备间的拷贝的效率,决定了计算机整机的基本效率。
越靠近cpu,容量越小,速度越快,价格越高。
小结:
- 存储特性:距离 CPU 越近的存储设备,数据处理效率越高,但成本也越高。
- 硬件数据流动规则:
- CPU 不直接与外设交互,仅与内存进行数据交换。
- 外设的数据需先存入内存,再由内存传递给 CPU 处理。
- 程序运行机制:
- 程序由代码和数据组成,在运行时需要加载到内存中,因为 CPU 只会从内存中读取代码和数据。
- 当程序未被加载到内存时,以二进制文件形式存储在磁盘等外部存储设备中。
2.操作系统
概念:
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。
笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)
操作系统是进行软硬件资源管理的软件。
从广义角度看,它包括操作系统的内核以及操作系统的外壳(周边程序),外壳给用户提供使用操作系统的方式;
从狭义角度看,操作系统只是指其内核。内核是操作系统的核心部分,负责管理系统的硬件资源、提供基本的服务等;而外壳则是基于内核之上的部分,为用户提供更友好的交互界面和操作方式。
结构示意图:
理解操作系统:
操作系统是进行软硬件资源管理的软件,而任何管理离不开这句话:
先描述,后组织。
以管理学生为例: 管理学生的本质是对学生的数据进行管理。
凡是对特定的对象进行管理都是 先描述,后管理。
在操作系统中驱动层将操作系统的请求转化为硬件能理解的命令。操作系统识别到相应的设备,创建相应的对象,硬件通过驱动层将硬件设备的相关属性上传到操作系统,通过链表的形式以便操作系统进行管理。
用户使用底层硬件层次图:
我们经常使用的 scanf 和 printf 等本质需要接触键盘和显示器等底层硬件。但是我们不可能绕过管理系统进行接触。操作系统保证其稳定性,用户又不能直接接触操作系统,这时候就需要用到操作系统提供的 系统调用接口 来满足用户的需求,也能保证操作系统的稳定性。
按照之前的知识,每种操作系统提供的调用接口让用户使用,但在学习C/C++等语言在Windows、Linux不同系统下,我们使用 scanf 和 printf 的方式没有变化,说明我们使用的不是系统调用接口,而是通过用户操作接口。我们拿之前在Linux写的C语言程序查看依赖库:
ldd [选项] [可执行文件或共享库路径]
libc就是我们C语言的依赖库:
库函数对系统调用的封装:
许多高级编程语言中的标准库函数会对系统调用进行封装,以提供更方便、更友好的编程接口。例如,C 标准库中的printf函数,它内部会调用 write 系统调用来将数据输出到标准输出设备(如终端)。这种封装隐藏了系统调用的底层细节,提高了编程效率。
同时,一种编程语言至少需要一套语法,一套标准库,一套编译器。在不同的操作系统下,库函数实现 printf 调用的系统调用接口不同,对其进行封装可以实现跨平台性,实现在不同操作系统下也能实现 printf。
3.进程
概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
结构示意图
PCB 在 Linux 为 struct task_struct
task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
以开机、开机举例:
操作系统关机时以二进制文件形式存储在磁盘中。开机后,内核被加载到内存并初始化。初始化过程中,内核会静态创建第一个进程(如 init)的 PCB,并存入内存。后续的进程如(fork( ))运行时动态分配PCB和资源,随后CPU通过调度器选择PCB开始工作,系统完成启动。
进程task_struct在不同的队列中,就能分配不同的资源。
ls 、file、程序的执行本质就是让系统创建进程并运行
--- 我们自己写的代码形成的可执行 == 系统命令 == 可执行文件。
在linux中运行的大部分执行操作,本质都是运行进程!!!
file 是 Linux 系统中一个常用的命令行工具,用于识别文件的类型。它通过分析文件的内容、元数据或特定标识(而非仅依赖文件名后缀),判断文件属于哪种类型(如文本文件、可执行程序、压缩包、图片等)。
基本语法
file
file [选项] 文件名/路径
常用选项
-b:仅显示文件类型描述,不包含文件名(简洁输出)。
-i:显示文件的 MIME 类型(如 text/plain、application/x-executable)。
-z:尝试分析压缩文件内部的文件类型(如 .zip、.tar.gz 中的文件)。
-L:如果文件是符号链接,显示链接指向的目标文件的类型(默认显示链接本身为 “symbolic link”)。
典型用法示例
观察进程:
编写一个简单的代码并生成程序。
#include <stdio.h>
#include <unistd.h>int main(){while(1){printf("I am a process\n");sleep(1);}}
~
运行程序使用命令为:
ps axj|grep process axj顺序没有要求
ps axj:ps 是查看进程状态的命令,axj 是组合选项:
a:显示所有用户的进程(包括非当前终端的进程)。
x:显示没有控制终端的进程(如后台运行的进程)。
j:以作业控制格式输出,主要包含 PPID(父进程 ID)、PID(进程 ID)、PGID(进程组 ID)、SID(会话 ID) 等列,适合查看进程间的父子关系和会话信息。
| grep process:管道符 | 将前一个命令的输出传递给 grep 过滤,grep process 用于筛选出包含 “process” 字符串的行(匹配进程名、命令行参数等,进程的默认命名与程序的名字一样)。
ps axj|grep process 本身也是一个进程。所以终止process程序后,还能看见process有关的进程。
想要直观地了解属性可以加入
head -1&&ps axj
head -1 只取输出的第一行,即表头行(例如:PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND),用于说明各列的含义。
ps axj| head -1&&ps axj |grep process #打印进程信息后,取表头再打印进程信息
了解 PID PPID
操作系统的进程调度器通过 一个唯一的标识符 来区分不同进程,对进程进行调度、分配 CPU 时间片等操作 ,所以有了PID,PID 是每个进程在系统中的唯一数字标识,就像每个人的身份证号一样。
PPID 记录创建当前进程的父进程的 ID,从而构建起进程间的父子关系,形成进程树状层次结构。这种结构有助于系统对进程进行管理,比如在进程清理时,当父进程结束,系统可以根据 PPID 找到所有子进程并进行相应处理(如在某些情况下,父进程退出后,子进程会被重新挂载到 init 进程下,init 进程的 PID 为 1 )。同时,系统管理员也可以通过查看进程的 PPID,了解进程的创建来源和相互关系,便于排查问题。
getpid:用于获取当前进程的进程 ID(PID),返回值类型为pid_t,无参数。
getppid:用于获取当前进程的父进程 ID(PPID),返回值类型也为pid_t,同样无参数。
写个简易代码来观察
#include <stdio.h>
#include <unistd.h>int main(){pid_t pid = getpid();pid_t ppid = getppid();while(1){printf("I am a process PID=%d PPID=%d\n",pid,ppid);sleep(1);}}
想要连续观察进程的状态,可以添加shell脚本实时观察:
hile :;do ps axj| head -1&&ps axj |grep process;sleep 1; done 每隔一秒打印一次
多次运行编写的 process ,每次的子进程 PID 都不一样 父进程 PPID 不变。
观察父进程,发现父进程由bash而来
我们是否也能创建自己的子进程呢?
答案是肯定的,我们来了解一下fork。
fork() 是 Unix/Linux 系统中用于创建新进程的核心系统调用,通过复制当前进程(父进程)来生成一个几乎完全相同的新进程(子进程)。以下是对其关键特性的解析:
核心功能
进程复制:fork() 会复制当前进程的所有资源(代码段、数据段、堆、栈、文件描述符等),生成一个新的子进程。
调用后双进程执行:调用 fork() 后,父进程和子进程会同时从 fork() 的下一行代码开始执行,但返回值不同:
进程类型 fork()
调用返回值说明 父进程 子进程的 PID(正数) 父进程通过返回的 PID 识别和管理子进程 子进程 0 子进程通过返回 0 标识自己是子进程 失败情况 -1 通常因内存不足、进程数超限等原因导致创建失败
差异项 | 父进程 | 子进程 |
---|---|---|
代码和数据来源 | 从磁盘加载而来 | 是父进程的 “副本”,默认继承父进程的代码和数据 |
进程 ID(PID) | 有唯一的 PID | 有唯一的 PID,与父进程不同 |
父进程 ID(PPID) | - | 等于父进程的 PID |
内存锁 | 可能有通过 mlock () 等函数锁定的内存区域 | 不继承父进程的内存锁 |
资源统计 | 有累计的资源使用统计,如 CPU 时间、内存占用等 | 资源使用统计重置为 0 |
文件锁 | 可能有通过 flock () 等函数锁定的文件 | 不继承父进程的文件锁 |
内存与资源的 “写时复制(COW)”
为提高效率,fork() 使用 ** 写时复制(Copy-On-Write)** 技术:初始时,子进程与父进程共享同一内存空间,不实际复制数据。
当任一进程(父或子)修改内存数据时,才会复制受影响的内存页,确保修改独立。
我们写一段代码简单了解一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");return 1;} else if (pid == 0) {while(1){printf("Child process: ID=%d PID = %d, PPID = %d\n", pid , getpid(), getppid());sleep(2);}} else {while(1){printf("Parent process: ID=%d PID = %d, Child PID = %d\n",pid , getpid(), pid);sleep(2);}}return 0;
}
可以观察到有两个进程在同时运行:
删除子进程:
删除父进程
无论删除子进程还是父进程都不影响其独立性。
进程一定要有独立性
之前 进程 = 内核数据结构 task_struct + 代码 + 数据
父进程和子进程都有对应的task_struct结构,代码是只读的,父进程和子子进程的数据是独立的。
fork()返回两个值可以理解为分别从子进程和父进程的数据中返回的。
本质上是父进程和子进程在各自独立的内存空间中,操作系统对同一个变量赋予了不同的值。
查看进程
除了之前 ps 命令查看进程,我们能在 /proc 路径下查看进程:
proc 路径下的进程按照 PID 命名。
我们来查看进程的详情
ls /proc/2699451 -l
删除可执行程序后,程序路径显示删除,但是当前进程仍在运行。
这是因为我们删除的是磁盘的文件,进程已经加载到内存里面且文件很小,没有影响到其他进程且调度器也没有更紧急的需求,所以仍然可以运行。
以前我们学过 fopen ("log.txt",“w”);
他是怎么打开识别到当前路径的呢?
我们先创建一个程序执行
然后我们修改 log.txt 生成的路径
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{chdir("/home/new");FILE *fp = fopen("log.txt", "w");(void)fp;fclose(fp);while(1){printf("I am a process, pid: %d\n", getpid());sleep(1);}return 0;
}
重新编译后再次运行程序:
文件成功改变路径。
fopen ("log.txt",“w”);
fopen函数调用时,他会寻找自己进程的cwd,当我们把文件名参数输入(不是绝对路径时),他默认会拼接当前进程的路径。所以在当前目录下生成我们需要的"log.txt"文件。
再见ヾ( ̄▽ ̄)Bye~Bye~