跳转至

一 权限管理

1.1 文件类型和访问权限

  • d,rwx,r-x,r-x:文件类型,user,group,others
  • 类型有:d文件夹,-普通文件,l软连接,b块设备文件(磁盘、光驱),p管道文件,c字符设备文件(屏幕等串口设备),s套接口文件
  • 对于文件和目录:r(4,读/浏览),w(2,写/删除移动),x(1,执行/具有进入目录的权限),-表示不具有该权限
  • chmod 权限 文件名:如chmod u+w /home/abc.text

二 进程

1.1 基本概念

  • 课本概念:一个执行的程序
  • 内核概念:操作系统资源分配的基本单位。
  • 实质上:对某种结构体的描述,再加以组织。

1.2 PCB-task struct

Linux中进程控制块PCB-------task_struct结构体结构 - 童嫣 - 博客园 内容分类: - 标示符(PID/tgid): 描述本进程的唯一标示符,也叫任务ID,用来区别其他进程线程。 - 亲缘关系: - 状态: 任务状态,退出代码,退出信号等。通过 - 进程权限: - 进程调度: 相对于其他进程的优先级调度器类和调度实体,调度策略,CPU使用等。 - 信号处理: - 程序计数器: 程序中即将被执行的下一条指令的地址。 - 内存指针: 包括程序代码和进程相关数据(mm_struct)的指针,还有和其他进程共享的内存块的指针 - 上下文数据: 进程执行时处理器的寄存器中的数据(休学例子,要加图CPU,寄存器)。 - 内核栈:包括了thread_info 和 pt_regs。和不同体系结构有关,上下文主要就是保存在这里面 - 文件系统、I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 - 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

  • 其他信息

1.3 组织进程 TODO

可以在内核查看,TODO - 双向循环链表:进程的组织意识依靠双向循环链表完成的。

1.4 系统调用fork()创建进程

  • fork两个返回值:0(child)和子进程pid(father)
  • 写时拷贝(COW)技术父子:进程共享代码,任意一方写入时,单独分配内存空间
  • 通常需要if进行分流。

1.5 进程状态 TODO

RSDTtXZ Z(zombie)僵死进程:子进程退出而父进程没有读到子进程退出的返回代码就会产生。 - 始终保持终止状态在进程表,并且一直等待父进程读取退出状态代码。 - PCB也要一直维护,创建又不回收会造成内存泄漏

1.6僵死进程和孤儿进程 TODO

1.7 进程优先级

三 环境变量 TODO

四 程序地址 空间

1.1 分页,虚拟空间地址

场景:fork一个进程子进程,子进程先输出地址和值,父进程输出地址和值,会发现地址竟然完全相同但值不同。 - 虚拟地址:巨大,分页机制,磁盘 - 物理地址:CPU

1.2 进程调度队列

  • 两个模块:分别管理活跃进程和过期进程,结构完全一样
  • nr_active:总共有多少个运行状态的进程
  • bitmap(5):140个优先级,用5* 32个比特位是否为空。
  • queue(140):双向队列。多个CPU需要考虑进程个数的负载均衡问题
    • 140中:100~139为普通优先级,可以自己设置
    • 0~99为实时优先级,可以暂时不考虑。
  • 调度方法:
    • 过期队列都是时间片消耗结对束的进程。
    • 活动队列上进程处理完后,过期队列的进程进行时间片重新计算。
    • 交换active和expired指向的内容,就相当于有一批新的互动进程!!(O1算法

五 进程控制

5.1 进程创建fork(前面有讲)

5.2 进程终止 _ exit()

进程常见的退出方法有: - 从main返回:return n相等于exit(n),main的返回值即exit的参数。 - 调用exit(int status):调用定义的清理函数,关闭流、冲刷缓存。再调用_exit - 调用_exit(int status): - 以及异常退出(ctrl+c,信号终止)

5.3 进程等待wait/waitpid

  • wait(int*status);:成功返回pid,失败返回-1.
  • waitpid(pid_t pid, int *status, int options):
    • pid=-1,等待任意子进程。pid>0,等待其进程id与pid相等的子程序。
    • status:查看进程是否退出\查看进程退出码
    • options:WNOHANG--子进程没结束则返回0不等待。 任意时候调用两个代码,且子进程存在且正常运行,进程可能阻塞,否则立刻返回。 如果不存在该子进程,立刻出错返回。

5.4 进程程序替换exec()

调用exec:进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。 - 并不创建新进程,所以调用exec前后该进程的id并未改变 - (只有出错返回值)调用成功加载新的程序,从启动代码开始执行不再返回。调用出错则-1.

5.5 思考函数和进程之间的相似性

exec(fork)/exit就像call/return。 被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。

六 基础IO

  • 复习C文件IO相关操作
  • 认识文件相关系统调用接口
  • 认识文件描述符,理解重定向
  • 对比fd和FILE,理解系统调用和库函数的关系
  • 理解文件系统中inode的概念
  • 认识软硬链接,对比区别
  • 认识动态静态库,学会结合gcc选项,制作动静态库

1.IO相关操作和系统调用

  • open打开:返回文件描述符或-1(失败),文件名,(从头)r读、r+读写、(覆写)w、w+、(追加)a、a+。w+、a+自带创建。
  • read读:fd,字符缓冲区,长度,返回读取长度
  • write:字符串,长度,输入输出流stdin,stdout,stderr的fd,对应0、1、2。
  • close:关闭fd对应文件

2.理解文件描述符

  • filestruct、fd本质。0、1、2的关闭、fd的分配规则。
  • 重定向:>,>>,<
  • 系统调用:dup2

3.文件元数据与inode

  • 模式、硬链接数、文件所有者、组、大小、最后修改时间、文件名(stat可查看更多信息)
  • 磁盘:先理解磁盘的块设备存储,硬盘分区block,存储inode信息和块信息、结构信息、数据区
  • inode:创建新文件的方式(找空闲i节点存储文件信息,存储数据到磁盘块abc、inode记录块abc分配情况,添加inode和块信息abc到目录文件)。所以磁盘找信息实际上是找inode。
  • 硬链接(inode引用,增删计数)、软链接(名字引用)

4. 动态库和静态库

  • 静态库(.a):编译链接阶段直接添加到可执行文件中,快但大。
  • 动态库(.so):运行时才去链接,执行文件小,多程序共享

七 进程间通信(IPC)

省流

  • 匿名管道、命名管道:最古老
  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

1. 管道(本质为文件)

  • 最古老,数据流,本质为文件。生命周期随进程,半双工。
  • 匿名管道:pipe(fd{2})创建,0为读端,1为写端。fork后各自关掉不用的描述符
  • 读写规则:无数据可读时,管道满时。一端文件描述符被关闭的情况。
  • 父子通信,通过两个管道实现。
  • 命名管道:FIFO文件管理,解决具有只有亲缘关系的管道通信问题,实际也是文件。
  • ![[Pasted image 20250403110818.png]]

2.共享内存(最快)

  • 最快的IPC形式,数据传递不再涉及到内核,不涉及系统调用。

3. 消息队列(生产者消费者模型)

  • 实际上,可以说是异步的远程调用,面向数据。一发一收一调用。
  • 案例对比:生产者消费者模型

4.信号量(同步互斥)

概括:进程互斥、临界资源互斥资源、临界区

  • 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
  • 在进程中涉及到互斥资源的程序段叫临界区

八 进程信号

我们在C/C++当中除零,内存越界等异常,在系统层面上,是被当成信号处理的。

  • 终端按键产生:键盘按键ctrl+c,触发硬件中断,发送信号SIGINT,执行终止程序的相关程序。
  • 系统函数产生:如kill产生SIGSGEV
  • 软件条件产生:SIGPIPE,SIGALARM
  • 硬件条件产生:如除0导致运算单元异常发送SIGFPE,非法内存地址段导致MMU错误发送SIGSEGV。 唉记点常用的就差不多得了:
  • SIGINT:终止进程
  • SIGQUIT:终止进程并保存
  • SIGSEGV:产生段错误(比如野指针异常),/kill。
  • SIGPIPE:软件产生的信号,管道
  • SIGALARM:告诉内核seconds秒后发送,默认终止进程。

  • 信号捕捉:signal(信号类型,函数指针)。捕捉到就调用回调函数。

九 多线程

  1. 了解线程概念,理解线程与进程区别与联系。
  2. 学会线程控制,线程创建,线程终止,线程等待。
  3. 线程分离与线程安全概念。
  4. 线程同步。
  5. 使用互斥量,条件变量,posix信号量,以及读写锁。
  6. 基于读写锁的读者写者问题。

9.1 线程是什么

  • 一个程序的执行路线,“一个进程内部的控制序列”
  • 在Linux中,CPU看到的还是PCB(task_struct),所以说是“轻量级线程
  • 多个线程共享同一地址空间。

9.2 线程优缺点

  • 优点:创建代价小,切换时OS做的东西少,资源占用小,多处理器利用,等待慢速IO过程中,程序可执行其他任务或IO密集时,同时等待不同IO。
  • 缺点:计算密集型的性能损失(线程数量太多,同步和调度开销),健壮性较低,缺乏访问控制,编程难度提高。
  • 线程异常:单线程崩溃(如除0)会导致整个进程崩溃,触发信号机制。其他线程一起退出。

9.3 Linux 进程VS线程

  • 线程自己的数据:线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级。
  • 共享以下进程资源与数据:文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id。
  • pthread_t是什么?该类型的ID,就是进程地址空间的一个地址 ![[Pasted image 20250404100152.png]]

9.4 线程等待

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出的线程的地址空间
  • pthread_join:挂起等待,直到对应id的线程结束

9.5 线程互斥

  • 背景概念:临界资源(共享资源),临界区(访问临界资源的代码),互斥(保证只有一个执行流进入临界区),原子性(不会被任何调度机制打断)
  • 互斥量:一般线程有局部变量和共享变量,共享变量在多个线程并发执行会带来些问题,因此引入了互斥量。一般解决该问题需要三个点:代码必须互斥,只允许一个线程进入临界区,如果线程不在临界区执行则不能阻止其他线程进入临界区。本质上就需要一把,叫互斥量。
  • --不是原子操作:分为load,update,store。即存寄存器,减,取寄存器三个步骤

9.6 可重入/线程安全

  • 线程安全:多线程并发执行同一段代码,不会出现不同的结果
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,称之为重入。
  • 可重入,不可重入:一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题。
  • 常见线程不安全:不保护共享变量的函数,函数状态被调用时发生变化,返回指向静态变量的指针,调用线程不安全函数的函数。
  • 常见线程安全只读无写入,原子操作,多线程切换接口无二义性
  • 常见不可重入:malloc和free(全局链表管理堆),标准IO,可重入函数体使用静态数据结构。
  • 常见可重入:无全局、静态变量,不用malloc和new,不返回静态、全局数据,使用本地数据。
  • 可重入即线程安全的,不可重入有可能线程安全,全局变量不可重入也不线程安全。
  • 可重入函数属于线程安全函数的一种

9.7 锁概念--死锁

  • 死锁概念:多个进程线程占有不释放的资源,但又互相申请其他进程的资源导致永久等待状态
  • 死锁四个必要条件:互斥,请求保持(当前),(使用完前)不剥夺,循环等待
  • 避免死锁:破坏四条件,加锁顺序一致,资源一次性分配,银行家算法。

9.8 Linux线程同步

  • 条件变量:当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。这种情况下需要使用条件变量。
  • 同步概念与竞态条件:
    • 同步:数据安全情况下,按照顺序访问临界资源,有效避免饥饿问题
    • 竞态:时序问题,导致程序异常。
  • 条件变量为什么需要互斥量:条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。
  • 条件变量使用规范:解锁和等待必须是原子操作,代码暂时就不放了。

9.9 生产者消费者模型

  • 解耦、支持并发、支持忙闲不均
  • queue模拟阻塞队列

9.10 POSIX信号量semaphore

  • 初始化0,销毁、等待(-1),发布信号量(+1)。
  • 环形队列生产消费模型:数组+模运算模拟环形,计数器辅助计算满或空(或者预留一个空的位置)--可以用信号量代替计数器在多线程上模拟。

9.11线程池

  • 维护着多个线程,等待着监督管理者分配可并发执行的任务。
  • 避免了在处理短时间任务时创建与销毁线程的代价,够保证内核的充分利用,还能防止过分调度。
  • 应用场景:大量线程,且任务时间短/性能要求苛刻/接受突发性的大量请求。
  • 示例:创建固定数量线程池,

9.12线程安全单例模式

  • 饿汉,懒汉,懒汉(双检查锁),懒汉(call once),懒汉(c11)
  • 注意双检查锁+volatile防止过度优化

9.13 STL,智能指针是否线程安全?

STL并非,主要为了挖掘性能。 uniqueptr安全,shadredptr涉及到引用计数,但是用了原子操作CAS也安全。

9.14其他常见的锁

  • 悲观锁:担心被其他线程修改,取数据前先加锁。
  • 乐观锁:乐观认为不会被修改。但是在更新前会判断有没有被修改(通过版本号机制和CAS)
  • 原子操作CAS:需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁
  • 非公平锁
  • 公平锁

9.15读写问题

  • 读写锁:读得多,加锁降低效率,解决多读少写问题。
  • 写独占,读共享,读锁优先级高。
  • 三种请求情况:无锁(可读写)、读锁(可读不写)、写锁(不读不写)

十 网络编程套接字

10.1 前置知识

  • 源IP地址、目的iP地址、端口号、进程ID(pid)、TCP协议和UDP协议、网络字节序(大小端)

10.2 Socket编程接口

  • socket(domain,type,protocol):TCP\UDP,服务端客户端
  • bind(socket,address,len):TCP、服务端
  • listen(socket,backlog):TCP、服务端
  • accept(socket,address,len):TCP、服务端
  • connect(sockfd,addr,len):TCP、服务端

10.3 Socket的address结构

  • 地址类型、端口号、IP地址

10.4 TCP和UDP实现(略)

10.5 多个连接

  • 多线程版本-> 线程池版本

10.7 TCP协议通讯流程

![[Pasted image 20250405010036.png]]

服务器初始化: - 调用socket创建FD - 调用bind,将当前的FD和ip/port绑定在一起;如果这个端口已经被其他端口占用了,就会bind失败 - 调用listen,声明当前这个FD作为一个服务器的FD,为后面accept做好准备 - 调用accept,并阻塞,等待客户端连接。

建立连接的过程(三次握手): - 调用socket,创建fd - 调用connect,向服务器发送连接请求 - connect会发出SYN段并阻塞等待服务器应答(第一次) - 服务器收到客户端的SYN,会应答一个SYN-ACK表示“同意建立连接”(第二次) - 客户端收到SYN-ACK后会从connect()返回,同时应答一个ACK段(第三次)

数据传输过程 - TCP协议提供全双工的通信服务(双方同时写数据)。 - 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待; - 读写数据:这时客户端调用write()发送请求给服务器,服务器收到后read返回,对客户端的请求进行处理,服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求 - 如此循环

断开连接的过程: - 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次); - 服务器收到FIN后,进入CLOSE_WAIT状态,会回应一个ACK,同时read会返回0(第二次) - read返回之后,服务端就知道客户端关闭了连接,那么也调用close(),服务器会再次向客户端发送FIN(第三次) - 客户端收到FIN后,进入TIME_WAIT状态,再返回一个ACK给无服务器(第四次)

在学习socket API时要注意应用程序和TCP协议层是如何交互的。

十一 网络基础

10.1 HTTP协议

  • URL构成
  • URLencode和decode

10.2 TCP,UDP构成

  • UDP:源端口+目的端口+UDP报文长度+检验和+数据
  • TCP:源端口+目的端口+序号+确认序号+报头长度+6位标志位+窗口大小+检验和+紧急指针+选项+数据(没有报文长度)

10.3 ACK确认应答机制(TCP)

TCP每个字节数据都有编号,ACK带有确认序列号,告诉发送者下次从哪里发

10.4超时重传机制

收不到ACK,倍数时间时间发一次,一定次数后强制关闭连接

10.5连接管理机制

前面省略.... TIME_WAIT:客户端要等待一个最大报文生存时间,才会进入CLOSE状态。所以主动结束服务端进程,并不会断开底层的SOCKET连接。

CLOSE_WAIT状态:大量的出现意味着服务端没有正确关闭,需要加上close。

10.6滑动窗口

  • 需要开辟发送缓存区,多发多收,提高吞吐率,收一个向后移一个
  • 丢包重传:
    • 数据包抵达,ACK丢了,不要紧可以通过后面ACK确认
    • 数据包直接丢了,三个来自服务端的重复应答(如1001),重传数据

10.7 流量控制

  • 为什么流量控制?发送端发送太快导致接收端缓冲区被打满,继续发送会导致丢包。
  • 接收端:超过承载,发送一个能接受的窗口大小值提醒发送端更新
  • 发送端:如果接收端满了,窗口会置为0。此时发送端也不再发送数据,但是需要定期发送一个窗口探测数据段,便于接收端把窗口大小告诉发送端

10.8 拥塞控制

  • 为什么需要拥塞控制?刚开始就发送大量数据,可能会引发问题。特别是当前网络状态非常拥堵。
  • 慢启动:慢启动阈值前倍数增长,慢启动阈值后线性增长。每次窗口大小和流量控制发来的窗口取最小
  • 超时重发:阈值减半,窗口变1,重新慢启动。

10.9 延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小,即使窗口再放大一些, 也能处理过来; - 数量限制,N个包答一次 - 时间限制,超过最大延迟时间答一次。

10.10 捎带应答

ACK的时候顺便带个消息回去。

10.11 面向字节流

  • 其实就是TCPscocket在内核建立了两个缓存区(收发),因此可以做到全双工

10.12 粘包问题

  • 问题原因:TCP没有报文长度的字段,但是有序号
  • 解决:明确边界
    • 定长的包:保证固定大小读就行
    • 变长的包:可以在包头处定一个总长度字段,也可以在包和包之间使用明确分隔符应用层协议,程序员自己确定)
  • UDP是否存在“粘包问题”?不存在,有报文长度字段,要么不收,要么全收,不会只收半个。

10.13 UDP实现可靠传输

抄点TCP的机制。 - 引入序列号 - 引入ACK确认应答机制 - 引入超时重传

十一 高级IO

11.1 五种IO模型

  • 阻塞IO:内核数据准备好前,系统调用会一直存在,所有套接字都是阻塞IO
  • 非阻塞IO:轮询--反复尝试读写文件描述符,比较浪费CPU,特定场景用
  • 信号驱动IO:内核将数据准备好的时候,发SIGIO通知应用程序IO
  • IO多路转接:流程上类似阻塞IO,实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态
  • 异步IO:内核在数据拷贝完成时, 通知应用程序(信号驱动是告诉应用程序何时可以开始拷贝数据) 任何IO都是:先等待(一般时间消耗最大),后拷贝

11.2 同步通信VS异步通信

同步:调用者者主动等待这个调用的结果,没有得到结果之前,该调用就不返回 异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果。当处理结束后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用.

11.3 IO多路复用之Select

  • 参数,最大fd值+1 ,读fd集、写fd集、异常fd集、等待时间
  • 集合结构:数组(位图),用位表示要监视的FD集合(如fd=5,对应00010000)
  • 执行过程:加入fd,则将位图位置置为1.
  • 原本只加入了fd=1、2、5,若fd=1,fd=2都发生可读事件,select返回时,set变成00000011,此时fd = 5就被清空了。

11.3 IO多路复用之poll

  • 参数:poll函数监听结构fds,fds数组长度,超时时间
  • 结构fds又分为fd,请求事件和返回事件
  • 优点:poll改用一个指针替代了三个位图的方式,接口使用更方便,且没有最大数量限制。
  • 缺点:和select依旧需要轮询,而每次调用poll都需要将大量pollfd拷贝到内核中。同时连接的大量客户端同一时刻可能很少处于就绪状态,随着监视的描述符数量增长,效率也会降低。

11.4 I/O多路复用之epoll

  • 作用:是为处理大批量句柄而作了改进的poll
  • epoll_create:创建句柄,用完必须close关闭
  • epoll_ctl:epoll的事件注册函数,参数为epoll句柄,动作,fd,内核需要监听的事件、
    • 其中这个动作op就包含:注册、修改和删除
  • epoll_wait:收集在监控中已经发送的事件,参数为epfd,events数组,events大小,超时时间

11.5 epoll工作原理

  • create创建红黑树和双链表根节点
  • 这些事件都会挂载到红黑树中,重复添加的事件就可以通过红黑树高效识别
  • 所有添加到epoll的事件都会与设备建立回调关系(事件发生回调方法)
  • 发生的事件会被添加到rdlist双链表中
  • epoll中,对于每一个事件,都会建立一个epitem结构体,记录红黑树和双向链表节点信息。句柄细腻和所属eventpoll对象,以及期待发生的事件类型
  • epoll_wait检查是否有事件发生时,检查双链表中是否有opitem即可
  • 不为空,则将事件复制到用户态,同时返回事件数量,时间复杂度O1 总结一下, epoll的使用过程就是三部曲:
  • 调用epoll_create创建一个epoll句柄;
  • 调用epoll_ctl, 将要监控的文件描述符进行注册;
  • 调用epoll_wait, 等待文件描述符就绪

11.6 epoll的优点

  • 接口使用方便:
  • 数据拷贝轻量:
  • 事件回调机制:
  • 没有数量限制:

11.7 边缘触发