冯诺-依曼体系结构冯·诺伊曼体系结构von Neumann architecture是 1945 年冯·诺伊曼在《EDVAC 报告草案》中提出、奠定了现代通用计算机基础的体系结构核心思想是存储程序stored-program。我们常见的计算机如笔记本。我们不常见的计算机如服务器大部分都遵守冯诺依曼体系。部件作用运算器 ALU算术/逻辑运算控制器 CU取指、译码、发控制信号、管理时序存储器存放指令和数据统一编址输入设备把外部信息送入计算机输出设备把结果送出运算器 控制器 CPU再加上主存构成“主机”输入输出设备统称外设。核心特征存储程序程序指令和数据一样以二进制形式存在同一存储器里可像数据一样被读写、修改——这是与早期“插线板编程”机器的根本区别。指令顺序执行CPU 按 PC程序计数器指向的地址逐条取指、译码、执行默认顺序推进遇跳转才改变流向。指令与数据共用存储器和总线同一时刻只能要么取指令、要么取数据总线分时复用。工作原理取指—译码—执行循环PC → 取指令 → 译码 → 取操作数 → 执行 → 写回 → PC 更新 → 循环这就是进程时看到的“CPU 一条条执行指令”的底层模型。主要优点与缺陷优点通用性强程序可存储、可加载、可修改硬件不必为每个任务重新设计。冯·诺伊曼瓶颈CPU 与存储器速度差距大且指令和数据共享一条总线取指与取数争带宽限制吞吐。缓解手段包括 Cache、流水线、多级存储、预取等。CPU不能直接与输入设备和输出设备连接也是因为CPU和它们的速度差距太大了. 差了大概6次方. 所以引入了储存器, 先将输入设备的要处理的数据加载到内存, CPU处理后将数据输出到内存中等待数据设备的调用.操作系统概念任何计算机系统都包含一个基本的程序集合称为操作系统(OS)。笼统的理解操作系统包括:内核(进程管理内存管理文件管理驱动管理)其他程序(例如函数库shell程序等等)目的方便性提供友好接口用户无需关心硬件细节。有效性效率提高资源利用率与系统吞吐如多道程序、并发、缓冲、虚存。可扩充性结构清晰便于添加新功能/新硬件。开放性遵循标准便于互操作。更本质的目标并发、共享、虚拟、异步——让多个任务安全、高效地共用一台机器。功能功能内容进程管理进程创建/撤销、调度、同步互斥、通信、死锁处理。你学的 fork、进程状态、父进程回收都属于这块内存管理分配回收、地址映射、虚拟内存、分页/分段、页面置换、内存保护文件管理文件的组织、目录、读写、权限、磁盘空间分配Linux “一切皆文件”设备管理驱动、缓冲、中断处理、设备分配与 I/O 调度用户接口命令行 shell、图形界面、系统调用编程接口其他保护与安全、网络通信、错误检测与处理理解管理相当与先对需要处理的数据, 描述出来(即使用结构体struct或class类,将需要处理的数据储存起来), 再对这些数据进行组织(使用学习的数据结构或STL容器, 将整体的数据组织联系起来)系统调用和库函数概念在开发角度操作系统对外会表现为一个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用。系统调用在使用上功能比较基础对用户的要求相对也比较高所以有心的开发者可以对部分系统调用进行适度封装从而形成库有了库就很有利于更上层用户或者开发者进行二次开发。这里就体现了操作系统对于用户是不信任的, 但是又想让用户调用操作系统对硬件进行向下管理, 所以就有了系统调用的接口, 但是由于系统调用的接口对于用户还是太复杂, 就对接口进行适当封装形成了库.这里就像我们生活中的银行, 我们将钱存入银行, 其实就是将钱放入银行金库. 可实际上我们并没有去银行的金库将钱放入. 这也是银行对我们的不信任.(群众里面有坏人) 所以实际上银行设计了有ATM和人工窗口, 这些接口对钱进行调用. 可是对于年迈的老人来说这些银行的流程太复杂, 所以好的银行大厅就会有一些银行的工作人员对前来办理业务的人进行帮助.(这就相当与封装了系统调用的接口)进程一、进程的概念程序 vs 进程程序是磁盘上的静态可执行文件代码数据进程是程序的一次运行实例是系统进行资源分配和调度的基本单位。同一个程序跑多次就是多个进程。组成进程 程序代码 数据 PCB。Linux 用task_struct描述 PCB记录 PID、PPID、状态、优先级、地址空间、打开的文件、信号、 CPU 上下文等。PID 与 PPID每个进程唯一 PIDPPID 指向创建它的父进程。PID 1 是 systemd或 init没有父进程负责收养孤儿。地址空间每个进程有独立的虚拟地址空间代码段、数据段、堆、栈、共享库互不干扰。PCB 在 Linux 里是task_structPCB 是概念名Linux 的具体实现就是struct task_struct定义在include/linux/sched.h几千行。每个进程对应一个task_struct实例由alloc_task_struct()分配。task_struct不包含代码和数据的字节内容它装的是元数据 指针struct task_struct { pid_t pid, tgid; // 身份 volatile long state; // 状态 R/S/D/Z struct mm_struct *mm; // 地址空间描述代码/数据在哪 struct files_struct *files; // 打开的文件表 struct fs_struct *fs; // cwd、root struct signal_struct *signal; // 信号处理 struct task_struct *parent, *children, *sibling; // 家族链 struct list_head tasks; // 挂到全局进程链表 ... };代码和数据实际存放在该进程的虚拟地址空间里用户态内存由 mm_struct 页表描述、映射到物理页。mm_struct 里再指向一串 vm_area_struct代码段、堆、栈、共享库各一段。内核实际是“一组 struct 通过指针连成数据结构”task_struct 不是孤立的它同时挂进多种组织全局双向链表 tasks遍历所有进程父子链表 children/siblingpstree 看到的树PID 哈希表按 PID 快速查找CFS 调度器的红黑树按 vruntime 排序等待队列等二、Linux 下查看进程的基本操作ps—— 静态快照最基础ps # 当前终端的前台进程 ps aux # 所有进程BSD 风格USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ps -ef # 所有进程System V 风格UID PID PPID C STIME TTY TIME CMD ps -ef | grep nginx # 过滤某程序 ps -u lcb # 指定用户 ps -p 102617 # 指定 PID ps -o pid,ppid,stat,cmd -p 102617 # 自定义列 ps aux --sort-%cpu # 按 CPU 降序 ps -eLf # 含线程LWP ps -ef --forest # 树状显示父子关系top / htop —— 动态实时top # 实时刷新按 P 按CPU排序、M 按内存、k 杀进程、q 退出 htop # 更友好需安装支持鼠标、树状、搜索 F3按名字查找pgrep -a nginx # 输出 PID 命令行 pidof nginx # 只输出 PID pgrep -af python.*test # 匹配完整命令行pstree —— 进程树pstree -p # 显示 PID能看清 sshd - bash - test 的父子链/proc —— 内核暴露的进程信息ls /proc/PID/ # 该进程的各类信息 cat /proc/PID/status # 状态、UID、内存、线程数 cat /proc/PID/cmdline # 启动命令行 ls -l /proc/PID/exe # 指向可执行文件 ls -l /proc/PID/cwd # 当前工作目录 ls -l /proc/PID/fd # 打开的文件描述符作业控制当前 shell 内的进程jobs # 查看后台/暂停作业 ./test # 后台运行 CtrlZ # 暂停到后台 bg %1 / fg %1 # 转后台继续 / 调回前台终止与调整kill PID # 发 SIGTERM(15)礼貌请求退出 kill -9 PID # SIGKILL强杀不可捕获 kill -l # 列出所有信号 pkill -f nginx # 按名字/命令行杀 renice -n 5 -p PID # 调整优先级其他常用who / w # 当前登录用户及终端 lsof -p PID # 进程打开的文件/端口 ss -tlnp # 端口与对应进程fork()创建子进程通过上面的知识我们了解到了程序没一次运行就会创建一个进程, 那么我们能不能在程序运行时创建一个进程呢? 这时我们就要用到fork()了.我们在Linux下运行这个段代码#includestdio.h#includesys/types.h#includeunistd.hintmain(){intretfork();if(ret0){perror(fork);return1;}elseif(ret0){//childprintf(I am child : %d!, ret: %d\n,getpid(),ret);}else{//fatherprintf(I am father : %d!, ret: %d\n,getpid(),ret);}sleep(1);return0;}如果以我们学习C语言的理解看到if-else语句,肯定只会有进入其中一个代码段执行, 但我们运行后发现,ret 0和ret 0这两个代码段的代码都执行了.1.为什么fork给父子返回各自的不同返回值? 2.为什么一个函数会返回两次??本质子进程是父进程在 fork 时刻的“克隆”fork()由内核的copy_process()完成分配新的task_struct复制父进程的地址空间、文件表、信号设置等然后让新进程加入调度队列。子进程从 fork() 返回处开始执行返回值 0不会重走 fork 之前的代码。