操作系统进程管理-Linux进程与线程
进程与线程进程的创建fork函数是linux中创建进程的核心函数, fork的原意是叉子, 也就是分叉. fork调用是程序执行的一个分叉点, 从这里开始, 原本的一个执行流变成了两个独立的执行流 创建的子进程继承了父进程的资源 打开的文件描述符 文件系统信息 … 创建的子进程在创建的时候是和父进程一样的内存空间, 会将父进程的地址空间也就是页表复制, 并复制所有的VMA, 但是标记为只读, 在修改的时候会触发page fault, 分配新的物理页, 复制数据, 更新页表项为可写 值得一提的是这里的继承和复制都是深拷贝, 也就是会将fs_struct, mm_struct, file_struct等资源都是深拷贝, 这里虽然是继承过去了, 但是实际上已经和父进程的资源是隔离的了, 只是在最开始的时候数据是完全相同的 doforkfork函数是以一个系统调用的形式存在的, 这个系统调用执行的内容就是执行dofork 1234SYSCALL_DEFINE0(fork){ return do_fork(SIGCHLD, 0, 0, NULL, NULL);} ...
操作系统进程管理-进程实现原理
进程实现原理进程是一个程序运行时的实例, 一个程序要运行起来, 需要硬盘, 内存, CPU, 网络等资源, 如果这些部分都有用户手动来管理, 开发一个程序会变成一个极其繁琐和困难的事情, 操作系统针对这些程序运行时需要的资源抽象出来了进程这个概念. 进程持有并统一管理所有一个程序要运行时需要的资源 对于资源的集合, 在概念中被称为PCB(Process Control Block), 而在Linux中对应的内核对象就是task_struct这个数据结构 123456789101112131415161718192021222324252627282930struct task_struct { // 1. 进程的状态 volatile long state; // 2. 进程的pid pid_t pid; pid_t tgid; //3. 和进程树的关系 (父进程, 子进程, 兄弟进程) struct task_struct __rcu *parent; struct listhead children; ...
操作系统内存管理 - Linux物理内存篇
Linux物理内存物理内存检测在物理内存这个硬件和操作系统之间, 还存在着一个固件层(firmware)也叫BIOS. 它负责硬件自检, 初始化所有硬件设备, 加载操作系统引导程序, 将控制权移交给操作系统并提供结构供操作系统读取硬件信息. 操作系统所需的内存等硬件信息都是通过固件来获取的 在固件ACPI接口规范中定义了探测内存的物理分布规范. 内核请求中断15H , 并设置操作码为E820H, 因为操作码是E820, 所以这个机制也被称为E820 会在detect_memory_e820函数发出15号中断并处理所有结果, 把内存地址范围保存到boot_params.e820_table对象中. boot_params只是一个中间数据, 专门还有一个e820_table全局数据结构来保存内存地址范围, 在e820__memory_setup中会将boot_params.e820_table保存到e820_table中, 并打印出来. 服务器能通过mseg命令来查看到实际的物理内存地址. memblock内存分配器的创建在完成了E820机制检测到可用的内存地址范围以后, 调用e8...
操作系统内存管理 - Linux虚拟内存管理
虚拟内存管理以问题来引入 申请内存申请到的真的是物理内存吗 对虚拟内存的申请如何转化成对物理内存的访问? top命令输出进程的内存指标中VIRT和RES分别是什么含义 堆栈的大小限制是多大, 当堆栈发生溢出以后应用程序会发生什么 进程栈和线程栈是一个东西吗 malloc大概是怎么工作的 虚拟内存和物理页为什么要有虚拟内存 用户进程访问内核数据要加以限制 用户进程之间需要隔离 内存不足的时候swap到硬盘上, 保证系统可正常运行 虚拟地址空间到底是什么在内核中的定义, 每个进程的task_struct都有一个核心对象 - mm_struct类型的mm. 代表的就是进程的虚拟地址空间 1234struct task_struct { ... struct mm_struct *mm;} 在这个虚拟内存空间里面, 每一段已经分配出去的地址范围都是通过一个个虚拟内存区域VMA来表示, 也就是对应到内核中的数据结构vm_area_struct 12345struct vm_area_struct { unsigned long v...
计网-深入理解三次握手的实现原理
三次握手的内部实现原理 不同于八股中简单的对三次握手的流程的介绍, 本文会从在Linux中使用socket建立TCP连接完成的工作的角度深度剖析三次握手 参考: 深入理解Linux网络: 修炼底层内功,掌握高性能原理 (张彦飞) 使用Socket通信的过程 123456// 客户端的核心代码int main(){ int fd = socket(AF_INET, SOCK_STREAM, 0); connect(fd, ....); ....} 12345678// 服务端的核心代码int main() { int fd = socket(AF_INET, SOCK_STREAM, 0); bind(fd, ...); listen(fd, 128); accept(fd, ...); ...} socket函数的作用从开发者的角度我们调用socket函数, 创建一个socket, 然后返回一个句柄用于访问和操作我们这个创建的socket. 从内核的角度来看, 调用这个函数会在内核内部创建...
分布式架构-远程服务调用 (RPC)
远程服务调用 (RPC) 参考: 主要是基于凤凰架构改动, 推荐阅读原文 凤凰架构: 远程服务调用 进程间通信RPC最初出现的时候, 是希望能提供一种像调用本地方法一样调用远程方法的技术, 虽然现在已经不是这样了, 但至少它的初心是这样的 我们是怎么调用一个本地方法的? 12345678// Caller : 方法的调用者, 也就是程序中的main函数// Callee : 被调用的方法, 也就是程序中的println()// Call Site : 调用点, 也就是发生方法调用的指令的位置// Parameter : 参数, 也就是hello world// Retval : 返回值, 由Callee传给Caller的数据public static void main(String[] args) { System.out.println("hello world!");} 完成这样的以一个方法的整体的流程是 传递方法参数: 将方法的参数入栈,将hello world的引用地址入栈 确定方法的版本: 根据println()...
算法-堆
堆核心概念 这里并不讨论堆的实现和基本的原理, 只是讨论这个数据结构的使用场景, 解决的问题 使用堆我们能获得O(logn)的时间复杂度的插入和删除元素, O(1)的时间复杂度的获取某个集合的最大(小)值 如果一个场景需要持续获取某个变化集合的最大(小)值, 我们就能考虑使用堆, 并且一定会有出堆操作!!! 不然我们简单维护一个min或max变量就行了 一般解法1234// 创建小堆Queue<Integer> minHeap = new PriorityQueue<>();// 创建大堆Queue<Integer> maxHeap = new PriorityQueue<>((a,b) -> b-a); 典型例题295. 数据流的中位数 347. 前 K 个高频元素 215. 数组中的第K个最大元素
算法-栈 (单调栈)
栈栈核心特征 (什么时候使用栈)本质上是一种用空间换时间的优化方案, 如果我们缓存数据的方向是从左往右(从右往左), 而我们求解答案的时候是从右往左的, 计算完成以后就不需要了, 符合后进先出的特征, 使用栈结构 比如括号匹配问题, 我们从左向右遍历和缓存数据, 然后在我们匹配到右括号的时候, 从右往左将数据弹出 一般解法1234567891011for (int i = 0; i < nums.length; i++) { // 特殊的情况下, 也就是匹配到了需要解递归的数据的时候 // 元素出栈 while (!stack.isEmpty() && condition) { int j = stack.pop(); ... } // 一般情况下直接将元素入栈 stack.push(i);} 典型题目20. 有效的括号 394. 字符串解码 非典型题目155. 最小栈 单调栈核心特征单调栈是栈的一种特殊的用法, 我们维护的栈是一个大小单调变化的...
操作系统I/O - 多路复用
I/O-多路复用多路复用是解决的什么问题解决的最根本的问题是: 我们怎么让我们的服务器能并发处理更多的数量的请求 最经典的问题就是C10K问题: 服务器怎么并发处理1w个请求 解决这个问题我们就需要考虑到, 连接占用的资源有哪些 文件描述符: Socket实际上是一个虚拟的文件, 也就对应着有相应的文件描述符, 在Linux中一个进程能打开的文件描述符的数量是有限的, 一般来说是1024(默认值) 系统内存: 每个TCP连接在内核中都有对应的数据结构, 也就是每个连接都占用了一定的内存 在这些基础上, 我们该怎么实现并发处理1w个请求呢? 多进程模型? 我们每成功建立一个连接就创建一个进程, 这个时候因为fork()创建的子进程中的文件描述符也是被继承过去, 让子进程来通过已连接Socket来提供服务 但是这种方式很明显是不能解决C10K问题的, 没有哪个系统扛得住创建1W个进程, 并且进程间切换的成本很高, 性能很差 多线程模型 为了解决多进程模型中, 进程的体量很大并且切换成本高的问题, 我们换成多线程模型 当服务器与客户端 TCP 完成连接后,通过 p...
操作系统I/O - 零拷贝
I/O的演进DMA技术在最开始的时候, 读一个文件的过程是 用户进程调用read(), 切换到内核态, CPU向磁盘发起IO请求 磁盘将数据放入到磁盘控制器缓冲区里面, 发送IO中断信号给CPU CPU将数据从磁盘数据控制器缓冲区中拷贝到 PageCache CPU将数据从PageCache中拷贝到用户缓冲区 read()调用返回, 切换到用户态 一共发生了两次用户态和内核态的上下文切换, 发生了两次拷贝, 并且数据的搬运也是交给CPU来操作的, 占用了大量的CPU的时间, 导致了CPU吞吐量下降 解决方式就是引入了DMA(Direct Memory Access)直接内存访问技术, 我们将把数据从磁盘控制器缓冲区搬运到PageCache的工作交给了DMA控制器来进行 现在的读一个文件的流程变成了 用户进程调用read(), 切换到内核态, 向操作系统发起IO请求, 将数据读取到自己的内存缓冲区中, 进程进入到了阻塞状态 CPU收到操作系统发送的指令以后, 将请求发送给DMA, DMA再进一步发送给磁盘 磁盘将数据放入到磁盘控制器缓冲区里面, 发送IO中断信...