目录
Linux进程概念
进程
Linux进程是一个在运行中的程序实例。在Linux操作系统中,每当你运行一个程序的时候,系统会创建一个进程。 进程是操作系统进行资源分配和调度的基本单位。每个进程都有一个唯一的标识符,PID.
进程的特点
- 独立性: 每个进程都有自己的内存的空间和系统资源,与其他进程相互独立
- 状态:进程的状态有很多, 运行中、可终端睡眠、不可中断睡眠、僵尸进程、停止、追踪
- 运行中: 进程正在执行或者等待执行(即已准备好执行但正在等待的CPU资源)在多核处理器,多个进程可以同时处于运行状态。
- 可中断睡眠:这是一种阻塞状态。 进程正在等待某些事件发生或数据可用, 如等待用户输入、文件读写完成或信号量。在这种状态下,进程会被挂起
- 不可中断睡眠: 进程在等待某些条件,但它不能被信号打断。这通常发生在进程正在进行某些关键操作或者资源时,如磁盘的I/O。不可中断睡眠状态的进程必须等待它们正在等待的事件完成后才能继续执行。
- 僵尸状态:当一个子进程已经完成执行,但其父进程还没有调用‘wait()’系统调用来读取子进程的退出状态的时候,该子进程将进入僵尸状态,这种状态中,进程的大部分资源都被释放了,但它在进程表中任然会保留,直到父进程读取它的状态,就像是凶杀现场,需要画一条白线,等待侦探来检查。
- 停止: 进程已经被停止,通常是因为收到了一个信号。比如用户按下Ctrl+Z时,前台进程会收到SIGSTOP的信号,进程会停止,等待被重启(‘fg’ 在前台重启 或 ‘bg' 在后台重启)
- 追踪或者跟踪: 这是一种特殊的停止状态, 进程正在被调试器等工具跟踪或监视。
- 生命周期:进程从创建开始,经过运行,最终会结束。结束的时候,它会给退出状态给操作系统。
- 新建:进程刚刚被创建,通常处于这个状态下进行初始化(分配PID,分配内存中)
- 就绪: 进程已准备好运行,等待被分配到CPU上。在新建状态完成初始化,进程转入就绪状态
- 运行:进程正在CPU上执行。当进程从就绪状态获得了CPU的时间片,它就变成了运行状态。
- 等待/阻塞:进程因等待某种资源或者事件(如I/O操作、信号等)而暂停运行。
- 终止: 进程完成执行或被终止。 一旦进程执行完毕或因某种原因被系统终止了,进入终止状态,等待操作系统回收资源。
- 多任务:Linux支持多任务,可以同时运行多个进程。
- 父子关系:创建进程的时候,一个父进程可以有一个或者多个子进程。子进程会继承父进程的某些属性。
- 进程间通信:进程可以通过各种机制(如管道、信号量、信号、消息队列、共享内存、套接字等)
- 管道: 这是一个在Linux中一种常用的进程间通信方式,它允许一个进程的输出成为另一个进程的输入,管道通信是单向的。数据只能从一个方向流向另一个管道,数据会被缓存在缓冲区中,他不是将数据直接写进磁盘中,而是在内存中传递
- 无名管道:无名管道通常用于有共同祖先的进程间通信(如父子进程)。这种管道在系统内部是匿名的,没有对应的文件系统名字。
- 有名管道:有名管道(FIFO), 它们在文件系统中有一个名字,通过这个名字,不相关的进程也可以访问。
- 信号量:信号量的本质是一个受保护的整型计数器。 它可以被初始化为非负值,通常表示可用资源的数量。主要是用于同步进程的操作。就像是电影院卖票,一共有多少票,进程拿到票就可以进去,但是票是一定的。
- 消息队列:进程处理用户请求并将任务消息发送到队列中去,另一个进程定期从队列中读取这些消息并执行相应的任务, 比如数据库更新等。这种分离确保了即使处理任务的进程由于某些原因暂时停止工作,用户请求也不会丢失
- 共享内存: 它允许两个或者多个进程共享一个给定的储存区域。这是最快的进程间通信方式,因为进程是直接对内存进行读写,而不是通过操作系统转发数据。
- 套接字(socket) :可完成不同计算机的进程通信, 利用网络的机制,在不同机器的进程完成通信。可以完成双向通信。
- 管道: 这是一个在Linux中一种常用的进程间通信方式,它允许一个进程的输出成为另一个进程的输入,管道通信是单向的。数据只能从一个方向流向另一个管道,数据会被缓存在缓冲区中,他不是将数据直接写进磁盘中,而是在内存中传递
线程
Linux和windows不同,并没有所谓的线程,在Linux中,线程(也称为轻量级进程)是一种使得进程内多个执行路径能够并行进行的机制。每个线程都共享进程内共享资源,如内存空间和文件描述符等,但它们各自独立拥有程序计数器(指令指针,指示计算机的控制单元下一条要执行的指令在内存中的地址)、寄存器集合和栈空间。
线程的特点
- 轻量级:线程比进程拥有更小的资源开销。在同一个进程中创建和管理线程比启动和管理独立的进程要节省资源
- 共享内存空间:同一个进程内的线程共享相同的内存空间。这意味着它们可以更加高效的共享数据,但也需要同步机制来避免竞态条件。
- 独立执行:尽管线程共享内存,每个线程都运行在独立的执行路径上。每个进程都有自己的程序计数器和栈。
- 系统调度:在Linux中,线程被内核调度和管理。对于内核来说线程和进程相同,都是可调度的执行单位。
- 栈空间的存储:每个线程拥有自己的栈空间。存在栈下面和堆的上面
- 线程间的通信方式,在同一个进程内不同的线程之间进行数据交换和同步机制。由于所有线程共享进程的内存空间,因此它们直接能相互访问相同的数据,但是也有同步和数据一致性的挑战。
- 共享内存: 线程可以直接读写共享数据
- 条件变量: 用于线程之间的事件通知,当条件满足的时候,一个线程可以唤醒一个或者多个等待的进程这个条件的线程
- 信号量: 用于限制会共享资源的访问,也可以用作线程间的信号机制。主要用于控制对于共享资源的访问,电影票卖完了,就不能进入
- 二元信号量: 只能取0或者1的值,常用于实现互斥锁,控制对共享资源的互斥访问
- 技术信号量:值可以大于1,表示有多个相同的资源可以被多个进程同时访问
- 事件或信号:线程可以设置事件或发送给其他线程, 已通知某个特定的事件发生。
- 屏障:在所有线程都到达了某个点后再一起继续执行,在并行计算中很常见,用于同步多线程的计算阶段。
- 锁的机制: 包括互斥锁/量、读写锁、自旋锁
- 互斥锁(mutex): 提供了以排他方式防止数据结构被修改的方式,互斥锁用于实现互斥,即在同一时间内只允许一个线程访问某个资源
- 读写锁(reader-writer lock): 读写锁允许多个读取这同时访问资源,但写入者会互斥(即同时只能由一个写入者或多个读取者)
- 自旋锁(Spinlocks):自旋锁在等待锁释放时会在循环中不断检查锁的状态,称为”自旋“,用于锁持有事件极短的情况,因为它避免了线程上下文切换开销。
- 序列锁:序列锁是一种特殊类型的不写所,用于保护读操作多于写操作的数据结构
- 避免死锁的方式:
- 鸽巢算法:有更多的进程(鸽子)请求资源,而可用资源(鸽巢)有限,根据鸽巢原理,必然会有至少一个资源被多个进程共享,系统可能需要排队或限制资源的分配
- 银行家算法:当一个进程请求资源时候,系统判断如果分配后是否会处于安全状态。如果是,则分配;否则,让进程等待
- 循环等待:对所有资源类型进行排序, 并按序申请资源,避免循环等待的情况。
- 非抢占:确保已分配的资源可以被抢占。如果进程得不到其他所需资源,系统可以抢占该进程所拥有的资源
- 死锁检测:定期是用算法检测资源分配图中的循环等待条件
- 进程终止:终止一个或者多个进程直到打破循环等待。
进程和线程之间共享和私有的资源
进程的共享和私有
共享 | 私有 |
代码段 | 地址空间 |
公共数据 | 堆 |
进程目录 | 全局变量 |
进程ID | 栈 |
寄存器 |
线程的共享和私有
共享 | 私有 |
堆 | 线程栈 |
地址空间 | 寄存器 |
全局变量 | 程序计数器 |
静态变量 |
进程和线程的区别
- 资源和内存分配共享
- 进程:每个进程都有自己的独立空间,进程间的资源和数据不共享,需要通过 进程间通信来进行数据交换,一个进程崩溃后,在保护模式下不会影响其他进程
- 线程: 同一个进程内的线程共享内存空间和资源,但每个线程都有自己的栈空间和程序计数器。这使得线程间的数据共享更加容易,一个线程的错误可能会导致整个进程的崩溃。
- 系统开销
- 进程:创建新进程的开销较大,因为它设计到赋值父进程的资源和地址空间。进程间切换也涉及到完整的上下文切换,包括内存、I/O资源
- 线程:线程的开销较小。线程间的切换仅涉及到必要的内核数据结构的改变,不需要地址空间的切换。
- 依赖关系
- 进程:进程都是独立执行的,不会依赖其他进程
- 线程:线程是进程的一部分,依赖于进程的存在