进程状态

进程五态模型状态:新建、运行、就绪、阻塞和终止。

新建态:

  • 第一阶段,为一个新进程创建必要的管理系逆袭
  • 第二阶段,让该进程进入就绪状态

终止态:

  • 第一阶段,等待操作系统进行善后处理
  • 第二阶段,释放主存

就绪态:

  • 进程获得了除处理机意外的一切所需资源,一旦获得处理机即可运行

运行态:

  • 进程在处理机上运行

阻塞态:

  • 进程等待某一时间发生而暂时停止运行

活跃就绪:

  • 进程在主存并且可被调度的状态

静止就绪(挂起就绪):

  • 进程被换到辅存的就绪状态,是不能直接调度运行的状态

活跃阻塞:

  • 进程已在主存,一旦等待的事件产生了便进入活跃就绪状态

静止阻塞:

  • 进程对换到辅存的阻塞状态,一旦等待的事件产生便进入了静止就绪状态

进程挂起原因可能是系统故障、用户调试程序、或检查问题。

linux进程状态(STAT)常见参数:

  • D 无法中断的休眠状态(通常 IO 的进程);
  • R 正在运行中,在队列中可过行的;
  • S 处于休眠状态;
  • T 停止或被追踪;
  • W 进入内存交换 (从内核2.6开始无效);
  • X 死掉的进程 (基本很少見);
  • Z 僵尸进程;
  • < 优先级高的进程
  • N 优先级较低的进程
  • L 有些页被锁进内存;
  • s 进程的领导者(在它之下有子进程,典型的systemd);
  • l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads);
    • 位于后台的进程组;

进程调度

调度指标

衡量和比较各种进程调度算法性能的主要因素如下所示:

  • CPU利用率:CPU是计算机系统中的稀缺资源,所以应在有具体任务的情况下尽可能使CPU保持忙,从而使得CPU资源利用率最高。
  • 吞吐量:CPU运行时的工作量大小是以每单位时间所完成的进程数目来描述的,即称为吞吐量。
  • 周转时间:指从进程创建到作进程结束所经过的时间,这期间包括了由于各种因素(比如等待I/O操作完成)导致的进程阻塞,处于就绪态并在就绪队列中排队,在处理机上运行所花时间的总和。
  • 等待时间:即进程在就绪队列中等待所花的时间总和。因此衡量一个调度算法的简单方法就是统计进程在就绪队列上的等待时间。
  • 响应时间:指从事件(比如产生了一次时钟中断事件)产生到进程或系统作出响应所经过的时间。在交互式桌面计算机系统中,用户希望响应时间越快越好,但这常常要以牺牲吞吐量为代价。

这些指标其实是相互有冲突的,响应时间短也就意味着在相关事件产生后,操作系统需要迅速进行进程切换,让对应的进程尽快响应产生的事件,从而导致进程调度与切换的开销增大,这会降低系统的吞吐量。

调度方式

  • 可抢占式,优先级较高的进程可抢占低优先级的进程CPU
  • 不可抢占式,直到运行结束,或时间片用完

调度算法

  • 先来先服务(FCFS),顺序链来调度,非可抢占式
  • 短作业优先(SJF),优先照顾短作业,非抢占式
  • 最短剩余时间优先,可抢占式SJF
  • 高响应比,非抢占式,响应比 = (等待时间+服务时间)/服务时间
  • 时间片轮询(RR),限定时间片
  • 多级反馈队列,设置多个就绪优先级队列,各队列的时间片也不同。当一个就绪进程需要链入就绪队列时,操作系统首先将它放入第一队列的末尾,按FCFS的原则排队等待调度。若轮到该进程执行且在一个时间片结束时尚未完成,则操作系统调度器便将该进程转入第二队列的末尾,再同样按先来先服务原则等待调度执行。

进程通信方式

通信目的

  • 数据传输
  • 共享数据
  • 通知事件
  • 资源共享
  • 进程控制

通信方式

  1. 管道(pipe),流管道(s_pipe)和有名管道(FIFO)
  2. 信号(signal)
  3. 消息队列
  4. 共享内存
  5. 信号量
  6. 套接字(socket)

管道

常见的符号“|”就是用了管道,tail -f * | grep ERROR 即tail进程通过管道井数据传输给grep进程处理。

管道(pipe),流管道(s_pipe)和有名管道(FIFO)

  • 管道:半双工的通信(单向流动),只能在具有亲缘关系的进程间使用(父子进程)
  • 流管道:可以双向传输,但是只能父子进程
  • 有名管道:可以无名字,允许无亲缘关系进程通信

匿名管道函数:

1
2
3
4
5
6
7
#include<unistd.h>
int pipe(int file_descriptor[2];

//更高级函数
#include <stdio.h>
FILE* popen (const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);

有名管道函数:

1
2
3
4
#include <sys/types.h>  
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);

信号量

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(P(信号变量)和发送(V(信号变量))信息操作。

信号量的工作原理:
P(sv)操作:如果sv的值大于0,就给它减1;如果值为零,就挂起该进程的执行
V(sv)操作:如果其它进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1

信号量函数:

1
2
3
4
5
6
7
8
9
10
11
12
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//创建一个信号量或获取一个现有信号量
int semget(key_t key, int num_sems, int sem_flags);

//控制信号量信息,初始值、删除信号量等
int semctl(int sem_id, int sem_num, int command, ...);

//改变信号量值,p操作和v操作
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

消息队列

linux用宏MSGMAX和MSGMNB来限制一条信息的最大长度和一个队列的最大长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//创建和访问一个消息队列
int msgget(key_t key, int msgflg);

//向队列添加消息
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

//获取一个消息
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

//控制消息队列
int msgctl(int msgid, int command, struct msgid_ds *buf);

共享内存

允许两个不相关进程访问的同一个罗杰杜村,共享内存未提供同步机制,可以采用信号量的方式对共享内存(临界区)进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/ipc.h>
#include <sys/shm.h>

//创建和访问共享内存
int shmget(key_t key, size_t size, int shmflg);

//启动对该共享内存的访问,把共享内存连接到当前的进程地址空间
void *shmat(int shm_id, const void *shm_addr, int shmflg);

//当前进程不可用,分离出当前进程
int shmdt(const void *shmaddr);

//控制共享变量
int shmctl(int shm_id, int command, struct shmid_ds *buf);

信号

信号由内核管理,产生方式多种多样,即也可达到进程间通讯:

  • 可以由内核自身产生,比如出现硬件错误、内存读取错误,分母为0的除法等,内核需要通知相应进程。
  • 也可以由其他进程产生并发送给内核,再由内核传递给目标进程。

典型的函数:Linux下的信号处理有 signal() 和 sigaction() 函数

1
2
3
4
5
6
7
8
//回调函数来处理信号
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

//符合POSIX标准的函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

注:POSIX标准,即可移植操作系统接口,是IEEE为要在各种unix操作系统上运行软件而定义的一系列API标准。

套接字

基于TCP/UDP协议的socket用于端到端网络通讯。

优缺点分析

  1. 管道:速度慢,容量有限,只有父子进程通讯
  2. 有名管道:任何进程间都通讯,但速度慢
  3. 消息队列:容量收到系统的限制,且要注意第一次读的时候,要考虑上一次没有读完数据问题
  4. 信号量:不能传复杂的信息,只能用来同步
  5. 共享内存:能够容易控制,速度快,但要保持同步,非进程安全。
  6. 信号:传递信息少或触发某些行为时可用于进程间通讯

参考文献