Linux进程、线程、协程的区别
进程
进程是操作系统中的一个独立执行单元。
每个进程都有自己的独立内存空间,包括代码段、数据段、堆栈等。
进程之间通常需要通过进程间通信(IPC)来交换数据和信息。
进程启动和销毁开销较大,因为需要分配和释放独立的内存空间。
进程之间隔离度高,一个进程的崩溃不会直接影响其他进程。
线程
进程内的一个执行单元,共享进程的内存空间。
可以看作是轻量级的进程,它们之间共享相同的地址空间和资源。
之间可以更轻松地通信,因为它们共享相同的内存。
线程的启动和销毁开销相对较小,因为它们共享进程的资源。
线程之间共享进程的文件描述符和打开文件等。
协程
是一种轻量级的执行单元,不同于线程和进程,它由程序员显式控制,而不是由操作系统调度。
通常运行在同一个线程内,不涉及线程切换或进程切换。
可以被看作是用户态线程,它们由应用程序自己管理,不依赖于操作系统的调度。
通常用于处理高并发、异步编程或需要大量I/O操作的情况,因为它们可以避免线程切换的开销。
的切换由程序员控制,更容易理解和调试
- 开销:
创建和销毁进程通常比线程开销更大,因为进程需要分配独立的地址空间和资源。这使得进程的创建和销毁相对较慢。
线程的创建和销毁开销较小,因为它们共享相同的进程资源。线程的切换也比进程的切换快速。 - 通信:
进程间通信(IPC)通常需要额外的系统调用,如fork()、exec()、wait()等,或者使用专门的IPC机制。
线程之间通信更容易,可以直接共享进程内存,通过全局变量或互斥锁****等机制进行数据共享和同步。
- 容错性:
进程之间是相互独立的,一个进程的崩溃通常不会影响其他进程。
线程共享相同的地址空间,一个线程的错误可能导致整个进程崩溃,因此线程之间的容错性较差。
并行性:
进程通常用于实现多个独立的任务,可以在多核处理器上并行执行。
线程更适合执行并行任务,因为它们共享相同的资源和地址空间,更容易实现数据共享和协同工作。
资源管理:
进程之间的资源管理较为独立,一个进程的资源不会直接影响其他进程。
线程之间的资源共享需要谨慎管理,以避免竞态条件和死锁等问题。
总的来说,进程和线程都有各自的优势和适用场景。进程适合处理相对独立的任务,而线程适合实现并行计算和更轻量级的任务划分。在实际应用中,通常需要根据具体需求和性能要求来选择使用进程还是线程。
进程的描述及task_struct(include/linux/sched.h&&task.h&&kernel/sched/sched.h&&include/fork.c&&kernel/exit.c)
-
include/linux/sched.h:task_struct 结构的核心定义通常位于这个文件中。这是进程调度和管理的核心头文件,包含了进程管理的数据结构和函数原型。
-
kernel/fork.c:进程的创建和复制过程通常在这个文件中实现
-
kernel/exit.c:进程的退出和销毁过程也会涉及到 task_struct 结构,相关的处理通常在这个文件中。
说明task_struct
因为它是 Linux 内核的核心数据结构之一,各个子系统和模块都需要访问它
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/mm_types.h>
struct task_struct {
pid_t pid; // 进程标识符 用于在系统中唯一标识一个进程
long state; // 进程状态 表示进程的当前状态,如运行、就绪、休眠等。具体的状态由宏定义表示,例如 TASK_RUNNING、TASK_INTERRUPTIBLE 等。
int static_prio; // 静态优先级 是在进程创建时分配
int normal_prio; // 动态优先级 在运行时根据进程的行为和响应时间进行调整
struct thread_info *thread_info; // 线程信息 指向与进程相关的线程信息。线程信息包括了线程栈等信息。
unsigned long flags; // 进程状态标志,包含了与进程状态相关的各种标志位,例如是否被追踪、是否在内核态等。
struct files_struct *files; // 文件描述符表 指向进程的文件描述符表,用于管理打开的文件和文件操作
struct mm_struct *mm; // 地址空间 指向进程的地址空间描述符,用于管理进程的虚拟内存。
struct task_struct *parent; // 父进程 指向父进程的 task_struct 结构,用于表示进程的父子关系。
struct list_head children; // 子进程链表 用于维护进程的层次结构关系。
struct list_head sibling; // 兄弟进程链表 用于维护进程的层次结构关系。
struct pt_regs *thread; // 执行上下文 用于实现 wait 等系统调用。
struct sched_entity se; // 调度信息 包含了进程的调度信息,用于进程的调度。
wait_queue_head_t wait_chldexit; // 子进程退出等待队列
struct signal_struct *signal; // 信号处理器包 含了与信号处理相关的信息,用于管理进程接收到的信号。
struct user_struct *user; // 用户信息 包含了与用户相关的信息,如资源限制等。
char comm[TASK_COMM_LEN]; // 进程名字 进程的名字,通常是进程的可执行文件名。
spinlock_t alloc_lock; // 进程锁 用于同步操作,以确保多线程环境下的数据一致性。
};
其中pid_t pid、longstate、sturct mm_struct_mm、struct list_head children 和 struct list_head sibling、struct sched_entity、struct signal_struct signal、char comm[TASK_COMM_LEN]最重要。
linux进程slab分配器&slub介绍(mm/slab.c)
- SLAB 是一种用于高效内存分配和管理的内存分配算法和数据结构
- 它的核心思想是将内存划分为多个大小相同或相近的块,然后按需分配和释放这些块,以减少内存分配和释放的开销。
最重要的算法和数据结构
-
双向链表(Doubly Linked List): SLAB 内存分配器使用双向链表来管理空闲内存块。每个 Cache 中的 Slab 包含一个 Free List,这是一个双向链表,用于存储可用的内存块。这允许在内存分配和释放时高效地添加和删除内存块。
-
哈希表(Hash Table): 为了快速查找适当的 Cache,SLAB 内存分配器使用哈希表。哈希表将不同大小的对象映射到相应的 Cache,以便快速定位。
实现 O(1) 时间复杂度 -
Buddy 系统(Buddy Allocation 二叉树): SLAB 内存分配器通常依赖于物理内存管理机制,例如 Buddy 系统,来分配 Slab Pages。Buddy 系统使用二叉树来管理物理内存块,并能够高效地分配和释放连续的内存块。 该算法的核心思想是将可用内存块划分为大小相等的块
-
LRU 算法(Least Recently Used 最近最少使用): SLAB 内存分配器可能使用 LRU 算法来管理缓存中的 Slabs。LRU 算法用于确定哪个 Slab 应该被回收,以便在需要时重新使用。
它的核心思想是保留最近被访问或使用过的数据,而淘汰最长时间未被使用的数据。 -
分配和释放算法: SLAB 内存分配器使用自定义的分配和释放算法,以确保高效的内存分配和释放。这些算法通常包括从 Free List 中获取内存块、将内存块返回到 Free List、Slab 的回收和再分配等
-
自旋锁(Spin Lock): 为了在多线程或多核环境中保护共享数据结构,SLAB 内存分配器可能使用自旋锁来实现同步。自旋锁允许线程在等待资源变得可用时自旋(忙等待),以避免进入睡眠状态。
-
形成链表->变成二叉树->形成哈希:pid->task_struct
进程声明周期
- 就绪状态(Ready):
进程处于就绪状态时,它已经准备好执行,但尚未分配到CPU时间片。多个就绪状态的进程等待操作系统的调度,以便在CPU上执行。
-
运行状态(Running):
进程进入
-
睡眠状态(Sleeping):
进程可能会由于等待某些事件(例如I/O操作完成、信号等)而进入睡
-
停止状态(Stopped):
进程可能会被显式地停止,通常由用户或其他进程发出停止信号(如
-
僵死状态(Zombie):
当一个进程已经终止但其父进程尚未调用wait()或waitpid()来收回其终止状态时,该进程会进入僵死状态。僵死进程不占用系统资源,但仍然保留在进程表中,以供父进程查询其退出状态。