
目录一、操作系统引导编辑二、虚拟机三、进程之间的通信四、信号一、操作系统引导CPU通过ROM主存特定地址中的程序将磁盘中的MBR磁盘第一块---主引导记录加载到内存上CPU执行磁盘引导程序通过分区表找到C盘活动分区并且将C盘中的”找到启动管理器程序“加载到内存中CPU执行”找到启动管理器程序“通过根目录找到启动管理器程序执行完成操作系统的启动工作。CPU通过ROM上的固有程序将磁盘上的特定程序加载到内存上CPU执行程序找到C盘上的特定程序找到启动管理器执行程序启动操作系统二、虚拟机第一类直接运行在硬件上单核CPU实现将CPU的每个时间片进行划分每个虚拟机器获得一定的时间片磁盘内存空间划分出来给不同的虚拟机器类似于操作系统对硬件资源的划分让每个进程有独立的资源虚拟管理程序是运行在内核态的而操作系统运行在用户态当它执行特权指令时会被VMM截获VMM会负责特权指令的执行。第二类老师让我们在电脑上安装Liunx虚拟机....三、进程之间的通信两个进程之间数据比如一段文本、一个结构体、一个文件路径的交互叫做通信共享存储共享区共享在进程P用shm_open系统调用向操作系统申请共享内存区两个进程使用mmap系统调用将共享区映射到各自虚拟地址空间 数据形式和存放位置由进程自主控制属于高级通信方式进程之间互斥访问共享区数据结构共享给共享空间限定特定数据结构如固定长度数组必须按预定格式读写数据属于低级通信方式消息传递“消息”直接通信P进程在自己的地址空间构造完整消息用send原语发送消息这里是系统调用指明接收进程ID 操作系统将消息复制到内核空间并挂到该ID进程的消息队列接收进程Q使用receive原语接收消息指明发送进程P的ID操作系统将消息从内核空间复制到进程Q地址空间。每个进程PCB中包含消息队列存储其他进程发送给它的消息间接通信P进程通过系统调用申请信箱可创建多个如信箱a、bP进程使用send原语发送消息到指定信箱不指明接收进程接收进程Q使用receive原语从指定信箱接收消息管理特点允许多个进程向同一个信箱发送消息允许多个进程从同一个信箱接收消息不需要指明具体接收/发送进程只需指定信箱。管道通信P进程在循环队列的一端写入Q进程从另一端读取管道pipe文件本质是内存中的固定大小缓冲区。 进程之间互斥访问pipe文件。写满就不能写读取完就不能读取数据一旦被读出就从管道中消失pipe允许多个写进程只允许一个读进程与高教社2014年真题一致实际系统如Linux允许多个读进程轮流读取读进程条件只要管道不空就可读 写进程条件只要管道不满就可写 不需要等待完全写满或读空四、信号这就是Linux早期所有的信号信号代表特定的事件的发生用于通知一个进程一个事件的发生每个进程的PCB中都有unsigned int pending [31]记录哪些信号已经产生但还在等待处理unsigned blocked [31]记录进程当前主动屏蔽了哪些信号handler 表这是一个函数指针数组sighandler_t 数组它记录了每个信号对应的处理动作是默认处理、忽略还是执行你写的自定义函数发送信号的本质动作操作系统内核将目标进程 PCB 中 pending 位图里对应信号的比特位由 0 置为 1。发送信号方式用户进程通过系统调用发送A 给B 发信号 / 自己给自己发信号内核自己发不走系统调用比如你按了键盘或者程序发生了“除以零”的致命错误。这些硬件中断或异常会直接触发内核内核在内部直接调用原语给目标进程发信号这属于内核的内部操作。信号是怎么被处理的内核不会在进程专心干活用户态时强行打断它而是采用“软插队”进程从内核态返回用户态的那一刻内核会检查 PCB 里的pending中有没有未处理的信号。如果有就去 handler 表里查信号对应的函数指针内核会修改进程的寄存器把指令指针 EIP 改成信号处理函数的地址把旧现场压入栈底。然后让 进程 去执行。进程回到用户态后以为自己还在正常跑其实已经跳转去执行你提前写好的“信号处理函数”了。处理完后通过一个特殊的系统调用sigreturn恢复旧现场进程继续若无其事地干原来的活。每一种信号都有默认处理程序有的信号处理函数可以自定义进程通过系统调用自定义某些信号的处理程序信号与异常的关系异常是“因”信号是“果”。 异常是硬件层面的底层事件而信号是操作系统将这种底层事件“翻译”并传递给用户程序的软件机制。异常是 CPU 在执行指令时突然遇到无法继续正常执行的情况从而被迫触发的内部中断。它完全发生在硬件和内核层面用户程序根本感知不到异常的发生。常见的异常包括除零异常执行了除以 0 的操作。缺页异常Page Fault访问了一个虚拟地址但对应的物理内存还没分配。非法指令异常执行了 CPU 根本不认识的机器码。段错误Segmentation Fault试图访问没有权限的内存地址比如往只读内存里写数据。当 CPU 触发异常时它会立刻暂停当前进程陷入内核态执行对应的异常处理程序。此时内核会充当“翻译官”根据异常的类型决定下一步怎么做情况一内核自己能搞定默默处理比如缺页异常。内核发现你访问的内存地址是合法的只是还没分配物理页。内核就会在内核态悄悄帮你分配内存、更新页表然后让进程若无其事地继续运行。整个过程用户程序毫无察觉。情况二内核搞不定必须通知用户程序转化为信号比如除零异常或段错误。内核在内核态无法修复这种逻辑错误它必须把“锅”甩给用户程序。怎么甩就是给该进程发送一个对应的信号通知它出了什么事除零异常 ➡️ 内核发送SIGFPE算术异常信号段错误 ➡️ 内核发送SIGSEGV段错误信号非法指令 ➡️ 内核发送SIGILL非法指令信号当内核把异常翻译成信号发给进程后进程在返回用户态时就会收到这个信号。此时命运就掌握在用户程序手里了默认行为通常是自杀如果用户程序没有对SIGSEGV等致命信号做特殊处理进程就会立刻终止并可能在终端打印出Segmentation fault (core dumped)。这就是我们平时写 C 语言时经常遇到的“程序崩溃”。自定义处理起死回生如果用户程序提前用signal()注册了处理函数进程就会去执行那段代码。比如你可以写一个SIGSEGV的处理函数在里面打印一句“哎呀内存访问越界了但我正在尝试恢复...”然后优雅地退出或进行补救。你可以把整个流程想象成一场“交通事故处理”异常汽车撞上了护栏硬件/底层发生了事故CPU 被迫停下。内核交警到达现场。如果是小刮擦缺页异常交警自己帮你修好让你继续开如果是严重车祸段错误交警无法现场修车。信号交警给你用户程序打了一个电话发送SIGSEGV信号告诉你“车撞坏了你看着办吧”。信号处理函数你接到电话后决定是叫拖车优雅退出还是尝试自己把车推走自定义恢复逻辑。以下为提问所以进程A能给进程B发送信号是因为B进程属于A?这是一个非常关键的误解进程 A 能给进程 B 发送信号并不是因为 B 属于 A。事实上在操作系统中进程之间是完全平等且相互隔离的不存在“谁属于谁”的从属关系。进程 A 之所以能给进程 B 发信号完全取决于“权限Permissions”和“规则”。在 Linux 等现代操作系统中内核判断 A 能不能给 B 发信号主要看以下两条铁律1. 规则一UID 相同同属一个主人如果进程 A 和进程 B 的真实用户 IDUID相同那么 A 就可以给 B 发信号。通俗解释这就好比你在一家公司同一个操作系统用户里虽然你们在不同的部门不同的进程没有上下级关系但既然都是同一个老板UID名下的员工你就有权限去“拍他的肩膀”发信号。2. 规则二拥有超级用户权限Root如果进程 A 是以超级用户Root / UID0的身份运行的那么它可以给系统里的任何进程发信号。通俗解释这就相当于你是公司的最高管理员或董事长你可以直接去敲任何员工的门甚至直接开除他发送SIGKILL信号。补充你可能混淆了“父子进程”和“信号发送”你之所以会有“B属于A”的错觉可能是因为在实际开发中我们最常遇到的场景是父进程给子进程发信号比如子进程跑飞了父进程把它杀掉。父子关系确实存在“谁创建了谁”的关系A 调用fork()创建了 BA 是父B 是子。发信号的关系父子进程发信号本质上依然是因为它们的 UID 相同子进程继承了父进程的 UID符合了上面的规则一。反过来也完全成立子进程 B 同样可以给父进程 A 发信号比如子进程执行完毕后内核会自动给父进程 A 发送一个SIGCHLD信号通知 A 来回收自己的尸体。总结操作系统里没有“谁属于谁”才能发信号的说法。只要你的权限够大Root或者你们归同一个用户管UID 相同哪怕这两个进程八竿子打不着A 也能给 B 发信号。UID怎么理解UID 的全称是User ID用户身份标识符。你可以把它理解为操作系统里的“数字身份证”。在操作系统的眼里它其实是个“脸盲”它不认识“张三”、“李四”这些人类名字也不认识“微信”、“QQ”这些软件。它只认数字。每一个在系统里登录的用户或者每一个运行程序的“身份”都会被分配一个唯一的数字这就是 UID。为了让你彻底弄懂我们从以下三个维度来剖析1. 为什么要有 UID为了安全和隔离操作系统是一个多用户环境。比如一台服务器上可能同时有“管理员”、“开发A”、“开发B”在用。如果没有 UID开发 A 就能随便看开发 B 的私密文件甚至把 B 的程序给杀了那系统就乱套了。有了 UID操作系统就能严格划分地盘文件归属你创建一个文件系统就会在文件的属性里打上你的 UID 标签。只有 UID 相同的人或者 Root才能修改它。进程归属你启动了一个程序这个程序对应的进程也会继承你的 UID。2. UID 是怎么分配和识别的在 Linux 系统中UID 通常是一个整数。有几个非常经典的 UIDUID 0这是超级用户Root。拥有至高无上的权限可以无视一切规则为所欲为。UID 1 ~ 999通常是系统保留给系统服务用的。比如 Nginx 服务、MySQL 服务它们为了安全通常不会用 Root 运行而是分配一个专门的低权限 UID。UID 1000 及以上这是分配给真实的人类用户的。比如你创建的第一个普通用户UID 通常就是 1000。3. 回到刚才的“发信号”话题现在你明白为什么“UID 相同就能发信号”了吧当进程 A 想给进程 B 发信号时内核会做一个极其简单的算术题“进程 A 的 UID 进程 B 的 UID 吗”如果相等内核认为“哦这俩是同一个老板手下的自己人允许发信号”如果不相等内核就会拦截“对不起你俩不是一个部门的你没有权限去打扰他。”除非你是 UID0 的 Root。打个通俗的比方UID 就像是小区业主的“门禁卡”。你进程 A想进 302 室进程 B敲门发信号。保安操作系统内核会查你的门禁卡UID。如果你们俩的卡号一样说明你们是同一个业主保安会放行。如果卡号不一样保安就会把你拦下。这样解释是不是就把 UID 这个概念彻底具象化了