Willson Chen

Stay Hungry, Stay Foolish.

Linux 内核(二)

Linux 内核(二)

进程管理

进程的定义

  • 进程:程序关于某数据集合的一次运行活动,是系统进行资源分配和调度的独立单位。

PCB(进程控制块)

  • 操作系统管理和控制进程的数据结构,具体实现是task_struct。
  • 包括以下信息:
  • 进程标识与关系信息:
    • PID:非负整数,进程/线程的唯一标识,一般情况为顺序编号。
    • TGID:非负整数,线程组的唯一标识,主线程上等于PID。
    • PGID:非负整数,同一组进程的标识,子进程上会继承父进程的PGID。
    • SID:非负整数,会话领导进程的PID,如终端shell的PID。
  • 进程状态信息:
    • state:进程状态枚举,生命周期中所处的阶段。
    • exit_state:进程结束状态,正常退出还是异常。
    • flags:标志位,记录多个布尔类型的状态。
  • 调度信息:优先级、调度策略、调度参数等。
  • 内存管理信息:如页表、内存空间布局等。
  • CPU上下文信息:如寄存器状态。
  • 进程资源信息:如打开文件、信号量、共享内存等。
  • 信号处理信息:如待处理信号队列、信号处理函数等。

进程状态

  • 抽象看包括:
    • 运行状态:进程正在CPU上运行。
    • 就绪状态:进程已经具备运行条件,只是等待CPU调度。
    • 阻塞状态:进程在等待某个事件发生(如I/O操作、获取资源等)。
    • 创建状态:进程正在被创建。
    • 结束状态:进程已经执行完毕,占有资源将被回收。

进程状态转化图:
image

进程的创建

fork

  • fork(分叉):创建一个与原进程几乎完全相同的进程,新进程被称为原进程的子进程。
  • 写时复制:子进程复制了父进程的task_struct,虚拟内存指向与父进程相同的物理内存,只有当子进程对虚拟内存进行写操作时,才会将父进程的虚拟内存复制一份,并修改其内容。

clone

  • clone(克隆):选择性地继承父进程的资源,可以选择与父进程共享一个虚拟空间,从而创造线程。

进程上下文切换

  • 进程地址空间切换:
    • 页表基址寄存器:设置为新进程的一级页表地址。
    • TLB:TLB条目记录进程的ASID(地址空间标识),减少切换空间时的TLB失效。
  • 处理器状态切换:
    • CPU寄存器会保存在PCB的CPU上下文结构中,切换时快速恢复。

进程调度

  • 定义:从就绪进程中挑选进程分配CPU资源。
  • 目标:
    • 公平性:所有进程都能得到合理时间。
    • 效率:最大限度利用CPU,减少空闲。
    • 响应时间:确保交互式进程能及时响应。
  • 策略:
    • 先来先服务:按到达就绪队列的先后顺序调度。
    • 短进程优先:优先调度执行时间短的。
    • 时间片轮转:按时间片获得时间,强制切换。
    • 优先级调度:按nice值,越小优先级越高。
  • CFS(完全公平调度)
    • Linux内核广泛采用的基于虚拟运行时间的公平调度模型。
    • 使用红黑树动态维护进程优先级。
    • 每次调度时选择红黑树最左边(虚拟运行时间最小)的进程执行。
    • 虚拟运行时间=实际运行时间/权重,nice值越小权重越大。

进程间通信

信号

  • 信号:信号是进程间通信和控制的一种方式,用于通知接收进程某个事件已经发生。
  • 信号编号:唯一标识,通过kill -l查看所有信号。
  • 信号类型:与信号编号一一对应的逻辑名称。如:
    • SIGINT:中断进程,来自终端如Ctrl+C。
    • SIGKILL:立即杀死进程,不能被捕获或忽略。
    • SIGTERM:正常结束进程,可以被捕获或忽略。
    • SIGQUIT:退出并生成core文件,可以被捕获或忽略。
    • SIGSTOP:暂停进程,不能被捕获或忽略。
    • SIGCONT:继续运行暂停的进程,不能被捕获或忽略。
    • SIGUSR1/SIGUSR2:用户自定义信号。
  • 信号处理方式:每个信号都有默认处理方式。如:
    • 终止进程:如SIGKILL
    • 忽略信号:如SIGCHLD,通知父进程有子进程退出。
    • 产生core文件:如SIGQUIT,core是核心转储文件,包含内存和寄存器的内容。
    • 自定义处理函数:通过signal或sigaction注册处理函数。
  • 信号传递机制:
    • kill命令行:如kill -SIGKILL PID。
    • kill函数:传入PID和信号类型。
    • raise函数:向自身进程发送信号。
    • sigqueue:向进程发送信号,带参数,搭配sigaction使用。

管道

  • 在内核中创建一个缓冲区,多个进程通过读写该缓冲区实现通信。
  • 特点:
    • 数据只能单向流动。
    • 管道没有数据时,读取的进程会阻塞。
    • 管道放满数据时,写入的进程会阻塞。
    • 管道容量受内核限制。
    • 打开管道的所有进程终止时,管道自动消失。
  • 匿名管道:
    • 只能用于具有亲缘关系的进程间通信。
    • 命令行管道符“|”表示前面进程的输出作为后面进程的输入。
  • 命名管道:
    • 通过mkfifo函数创建,需指定管道文件路径和读写权限。

共享内存

  • 共享内存:多个进程将同一块物理内存映射到自己的虚拟空间实现数据共享。
  • 特点:
    • 速度最快。
    • 生命周期随内核。
    • 不带同步和互斥。

消息队列

  • 消息队列:由内核提供的支持多进程访问的队列。
  • 特点:
    • 生命周期随内核。
    • 自带同步和互斥。

信号量

  • 信号量:用于多进程对共享资源进行互斥访问。
  • 本质:通过计数器控制对共享资源的访问次数。
  • 原理:内核的计数器、等待队列、自旋锁。

套接字

  • 套接字:用于相同或不同主机上进程之间的通信。
  • 原理:通过TCP或UDP协议进行通信。

网络管理

  • 网络协议栈
    • linux 内核网络协议栈只涉及l2/l3/l4层。
    • 接收时,从l2网络设备驱动程序,传递到l3,再到l4。
    • 发送时相反。
  • 通信过程
    • TCP 和 UDP 协议通信都是通过操作系统的 Socket 来实现。
    • 服务端调用 socket()函数,指定网络协议和传输协议创建 Socket。
    • 服务端调用 bind()函数,将 Socket 绑定到 IP 地址和端口号。
    • 可能存在多个网卡,所以需要指定 IP 地址。
    • 服务端调用 listen()函数,将 Socket 设置为监听模式,等待客户端连接。
    • 可以通过 netstat 命令查看端口号是否被监听。
    • 服务端调用 accept()函数,从内核获取客户端连接,如果没有连接则阻塞。
    • 客户端调用 socket()函数,指定网络协议和传输协议创建 Socket。
    • 客户端调用 connect()函数,传入 IP 地址和端口号,向服务端发起连接请求。
    • 客户端与服务端通过三次握手建立TCP 连接。
    • 双方通过read()和write()函数读写数据。