进程以及进程间通讯

1 进程

1.1 进程的“生老病死”

1.1.1 进程状态

(1)进程刚被创建出来时,处于TASK_RUNNING状态,此状态可以是正在队列排队等待执行也可以是占用CPU正在运行。

(2)刚被创建的进程处于“就绪”状态,等待系统调度,内核中的函数sched()称为调度器,会根据各种参数选择一个等待的进程去占用CPU。

(3)进程处于“执行”状态时,可能会由于某些资源不可得而被置为“睡眠态/挂起态”,有TASK_INTERRUPIBLE或TASK_UNINTERRUPIBLE,后者是深度睡眠,不能响应信号。

(4)当进程收到SIGSTOP或SIGTSTP中的一个信号时,状态会变为“暂停态”,该状态下的进程不参与调度,但系统不会释放资源。

(5)“僵尸态”即死亡后携带死亡信息,系统不会回收资源。父进程会关心子进程的死亡信息,因为父进程可以看到交代子进程办的事办的怎么样。

(6)父进程电泳wait()/waitpid()来查看孩子的“死亡信息”,顺便将该孩子的状态设置为EXIT_DEAD,即死亡态。

(7)父进程并不能保证准时的去收尸,因为父进程可能还有其他的事要做,或者父进程还有可能先于子进程挂掉。前一种情况,可能让祖先进程会收养这些孤儿进程。后一种情况需要使用信号异步通知机制,让一个孩子在变成僵尸时给父进程发送一个信号,父进程接收到信号就立处理。(如果两个孩子同时死亡能?)

1.1.2 进程的祖先

  init.是所有进程的祖先,从内存空间的角度看,进程之间是相互独立的。

1.2 进程的语言

  有如下几种。

(1)无名管道(PIPE)和有名管道(FIFO)

(2)信号(signal)

(3)system V-IPC之共享内存

(4)system V-IPC之消息队列

(5)system V-IPC之信号量

(6)套接字

1.2.1 管道

  无名管道(PIPE)

  无名管道的特征如下。

  (1)没有名字,因此无法使用open()

  (2)只能用于亲缘进程间通信

  (3)半双工工作方式:读写端分开

  (4)写入操作不具有原子性,因此只能用于一对一的简单通信情形

  (5)不能使用lseek()定位

  有名管道(FIFO) 

  有名管道的特征如下。

  (1)有名字,存储于普通文件系统之中

  (2)任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符

  (3)跟普通文件一样,使用统一的read()/write()来读/写

  (4)跟普通文件不一样,不能使用lseek()定位

  (5)具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏。

  (6)First In First Out,最先被写入的数据,最先被读出来

  小结

  PIPE只能亲缘通信的原因,PIPE是一种特殊的文件,但虽然它是一种文件,却没有名字!因此,一般进程无法使用open()来获取它的描述符,它只能在一个进程中被创建出来,然后通过继承的方式将它的文件描述符传递给子进程。

  PIPE与FIFO、socket一样,这些管道文件都不能使用lseek()来进行所谓的定位,因为它们的数据不像普通文件那样按块的方式存放在块设备上,而更像一个看不见源头的水龙头,无法定位。

  管道文件不可以只在有读端或者写端的情况下被打开。

1.2.2 信号

  重要概念

  设置阻塞、解开阻塞、信号相应函数、设置信号、信号集、实时信号、非实时信号、普通响应函数、响应扩展函数

 

  信号SIGKILL和SIGSTOP是两个特殊的信号,不能被忽略、阻塞或捕捉,只能按默认动作来响应。

  

  非实时信号不排队、会丢失

  实时信号排队,不会丢失

 

  发送信号不携带消息 kill()和signal()

  发送信号携带消息sigqueue()和sigaction()

 

  在响应函数内部访问任何共享资源,都必须和多线程一样,使用同步互斥机制来确保访问的安全性。

1.2.3 消息队列

  前述的管道,这种通信机制的一个弊端是:无法在管道中读取一个“指定”的数据,因为这些数据没有做任何标记,进程只能按次序地逐个读取。而消息队列提供一种带有数据标识的特殊管道。

  消息队列的使用简单,但它和管道一样,都需要“代理人”的进程通信机制:内核充当了这个代理人,内核为使用者分配内存、检查边界、设置阻塞,以及各种权限监控。但是代理人机制的效率都不高,因为两个进程的数据传递并不是直接的,而是通过内核,因此它们都不适合传输海量数据。

 

1.2.4 共享内存

   共享内存是效率最高的IPC, 因为它抛弃了内核这个“代理人”,直截了当地将一块裸露的内存放在需要数据传输的进程面前,让它们自己做,代价是:这些进程必须小心谨慎地操作这块裸露的共享内存。

  使用共享内存的一般步骤如下。

  (1)获取共享内存对象的ID。int shmget(key_t key, size_t size, int shmflg)

  (2)将共享内存映射至本进程虚拟内存空间的某个区域。void *shmat(int shmid, const void *shmaddr, int shmflg)  

  (3)当不再使用时,解除映射关系。 int shmdt(const void *shmaddr)

  (4)当没有进程再需要这块共享内存时,删除它。int shmctl(int shmid, int cmd, struct shmid_ds *buf)

  注意:

  (1)共享内存只能以只读或者可读写方式映射,无法以只写方式映射。

  (2)解除映射后,进程不能再允许访问SHM。

1.2.5 信号量

  不是用来传输数据的,而用来协调各进程或线程工作的。

  一些基本概念如下:

  (1)多个进程或线程有可能同时访问的资源称为共享资源,也称临界资源。

  (2)访问这些资源的代码称为临界代码,这些代码区称为临界区。

  (3)程序进入临界区之前必须对资源进行申请,这个动作称为P操作。

  (4)程序离开临界区之后必须释放相应地资源,这个动作称为V操作。

posted @ 2018-08-24 11:10  Jaydon16  阅读(139)  评论(0编辑  收藏  举报