进程与线程

1. 进程

1.1. 程序操作

1.1.1. 创建

  创建的是原进程的子进程,子进程会复制父进程的PCB(进程控制块),二者之间代码共享,数据独有,拥有各自的进程虚拟地址空间。

#include <unistd.h>
pid_t fork(void);
  • 写时拷贝技术  子进程创建后,与父进程映射访问同一块物理内存,当父或子进程对值进行修改时,会给子进程重新在物理内存中开辟一块空间,并将数据拷贝过去。
  • pid_t: 创建子进程失败,会返回-1; 创建子进程成功,父进程返回pid号,子进程返回0。

1.1.2. 终止

  • 正常终止
    • main函数返回
    • 调用exit函数: status 可以通过 wait(int *status) 接收; 会刷新缓冲
        #include <stdlib.h>
        void exit(int status);
      
    • 调用_exit函数: status 可以通过 wait(int *status) 接收; 不会刷新缓冲
        #include <unistd.h>
        void _exit(int status);
      
  • 异常退出
    • Ctrl+C
    • 被信号终止

1.1.3. 等待

  如果子进程先于父进程退出,而父进程并没有关心子进程的退出状况,从而无法回收子进程的资源,就会导致子进程变成僵尸进程进程等待的作用就是父进程对子进程收尸!父进程通过进程等待的方式,回收子进程的资源,获取子进程的退出状态。

  • wait

      #include <sys/types.h>
        #include <sys/wait.h>
        pid_t wait(int *status);
    
    • 利用阻塞,等待子进程退出。
    • 一次只能等待一个
    • pid: 成功,子进程的pid; 失败(没有子进程),返回-1;
    • status: 进程退出信息
  • waitpid

      //头文件同wait的头文件
        pid_t waitpid(pid_t pid, int *status, int options);
    
    • 返回值:
      1. 等待成功,返回pid
      2. options为WNOHANG时,没有等待到子进程,马上退出不阻塞,返回0
      3. 出错则返回-1
    • pid:
      1. -1: 随便等待一个进程
      2. pid号: 明确指定一个进程等待
    • options:
      1. 0: 阻塞等待,等待到一个就退出
      2. WNOHANG: 没有等待到子进程,马上退出不阻塞,返回0

1.1.4. 进程退出信息status

  • 低16位存放信息,高16位不用
  • 高8位 : 退出码, exit 的传入值或者 return
      (status >> 8) & 0xFF;
    
  • 低8位 : 异常退出信息
    • 第7位: core dump标志位;0,不产生;1,产生
        (status >> 7) & 0x1;
      
    • 低7位: 子进程是否异常退出;0,正常;非0,终止的信号
        status & 0x7F;
      

1.2. 进程状态

1.2.1. top指令查看:

  • R (running): PCB被放入CPU的可执行队列中;CPU上执行的RUNNING状态、而将可执行但是尚未被调度执行的READY状态
  • S (sleeping): 睡眠状态,PCB被放入等待队列,可以通过中断唤醒。
  • D (disk sleep):睡眠状态,不能中断被唤醒。内核的某些处理流程是不能被打断的。用来保护进程。
  • T (stopped): SIGSTOP信号让进程进入暂停,SIGCONT信号能将进程恢复。
  • t (tracing stop): 在被追踪时,进程暂停;典型应用就是 gdb断点 。调试进程退出,被调试的进程才能恢复TASK_RUNNING状态。
  • Z (zombie): 进程退出后,PCB没有被回收。父进程没有对子进程收尸,就只能等父进程退出后,让爷进程来统一收尸。
  • X (dead): 马上会被彻底销毁

1.2.2. 执行时的状态:

进程主要有三种状态:运行态、就绪态、阻塞态。

  • 「运行态」和「就绪态」可以相互转换,通常由系统的进程调度引起的。
  • 遇到阻塞代码,进程进入「阻塞态」,等待被唤醒;没有足够的运行资源时,进程会被挂起,进入「阻塞态」等待资源充足。
  • 进程由「阻塞态」被唤醒,就会进入「就绪态」,等待被调度。

1.3. 进程间通信

1.3.1. 管道

父子进程间通讯。

1.3.2. 命名管道

去除了父子进程间通信的机制,通常用来汇聚多个客户端进程与服务端进程的通信。

1.3.3. 消息队列

独立于进程存在,进程间可以通过消息队列来传递数据,典型的模式是生产者-消费者模型。

1.3.4. 信号

一个进程可以给另一个进程发送信号SIGN来触发某些操作。

1.3.5. 共享内存

多个进程上不同的地址空间可以映射到同一块物理内存上,实现数据的共享。因为不涉及数据的拷贝,所以这是一种高效的通信方式。需要注意的是,多个进程并行时,需要通过同步机制保护共享内存的访问。

1.3.6. Socket 通信

调用TCPUDP进行通讯。

1.3.7. 信号量

用来统计资源数量的。

  • 整型信号量
    • 0: 繁忙,没有资源可用。
    • > 0: 资源有空闲。
    • 不能为负值
  • 记录型信号量
    • 0: 繁忙,没有资源可用,也没有人过来等待资源。
    • > 0: 资源有空闲。
    • < 0: 没资源,等待队列下有人在那儿等着。

1.4. 僵尸进程和孤儿进程

  • 僵尸进程: 父进程运行完毕,没有对「已经退出的子进程」进行收尸。
  • 孤儿进程: 当父进程退出了,但是它的「子进程还没有退出」,这些子进程就变成了孤儿进程。孤儿进程只是暂时的,系统会在父进程退出时启动寻父机制,为子进程找到一个新的父亲。

1.5. 进程同步

进程同步: 进程同步是指控制进程按照一定顺序执行。只有处于临界区(指访问共享内存的代码片段)的进程才需要同步。

  • 忙等待互斥(自旋锁):当某个变量不满足条件时,会一直轮询直到变量值发送改变。用于忙等待的锁称为自旋锁
  • 信号量: 是一个整型变量,用来实现计数器功能,主要提供 downup 操作(即 PV 操作),这两个操作都是原子性的。当执行 down 操作使信号量值变为 0 时,会导致当前进程睡眠,而执行 up 操作 +1 时,会同时唤醒一个进程。
  • 管程: 管程是由一个过程、变量和数据结构组成的一个集合,把需要控制的那部分代码独立出来执行,它有一个重要的特性,同一时刻在管程中只能有一个活跃的进程。为了避免一个进程一直占用管程,引入了条件变量和 wait 和 signal 操作。当发生当前进程无法运行时,执行 wait 操作,将当前进程阻塞,同时调入在管程外等待的另一进程执行,而另一个进程满足条件变量时,会执行 signal 操作将正在睡眠的进程唤醒,然后马上退出管程。
  • 改造线程互斥锁: 这个得看系统支不支持。

2. 线程与进程

线程:进程中活动的对象,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程会维护自己的堆,栈和寄存器值。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。

进程: 资源分配的基本单位,程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程「就绪队列」,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

3. 进程和程序

  • 一个进程是程序执行时的一个实例,一个程序可以对应多个进程,但一个进程只能对应一个程序。
  • 进程是动态的,而程序是静态的。
  • 进程有一定的生命期,而程序是指令的集合,本身无运动的含义。

4. 线程

4.1. 线程的意义

  • 在一个进程中会存在多种活动任务,需要有多个独立调度的单元来使这些任务可以并行的执行,这些单元就是线程。
  • 线程比进程更轻量,它们比进程更快的创建,也更容易撤销。线程间切换的开销也比进程小,由于进程拥有大量的资源,当切换到另一个进程的时候,需要保存当前进程的所有资源,而线程间的切换只需要保存当前堆栈和相应寄存器的内容。

4.2. 线程同步

  • 互斥锁: pthread_mutex_那一套
  • 条件量: 引入信号机制,优化后的抢锁机制。
  • 信号量: 同进程。
  • 自旋锁: 同进程。

5. 并发和并行

并发: 一段时间内可以同时运行多道程序,对于CPU而言就是程序来回切换。 并行: 多个CPU同时运行程序,真正物理上的并发。

results matching ""

    No results matching ""