进程:进程是指独立地址空间的指令序列进程的五种状态:新建,就绪,运行,睡眠,僵死进程间通信:是不同进
进程:进程是指独立地址空间的指令序列
进程的五种状态:新建,就绪,运行,睡眠,僵死
进程间通信:是不同进程之间进行一些"接触",这种接触有简单,有复杂。机制不同,复杂度也不同。通信是一个广义上的意义,不仅指大批量数据传送,还包括控制信息的传送,但使用方法是基本相同的。
基本的进程通信机制
1.传统UNIX-IPC机制:信号和管道
2.SystemV的IPC机制:共享内存、信号量和消息队列
3.起源于Unix BSD版本的套结字(Socket)
4.远程过程调用(RPC)
信号:Unix系统中使用的最古老的进程间通讯的方法之一,用于向一个或多个进程发送异步事件的信号。信号可以类比于DOS下的INT或者是Windows下的事件。在有一个信号发生的时候,相应的信号就会发送给相应的进程。
信号机制的实现
1.信号包括待处理信号和被阻塞信号
2.如果产生了一个被阻塞的信号,它一直保留待处理,直到被解除阻塞。
3.系统保存每一个进程如何处理每一种可能的信号的信息。
4.系统判断进程是希望忽略这个信号还是让内核处理。进程通过执行系统调用改变缺省的信号处理。
对信号的处理
1.初始化信号集,只有在信号集里面的信号才会被考虑
2.安装信号处理器。所谓信号处理器,就是指定了一些对信号的处理方法。在安装的时候,一定要对特定的信号赋予正确的信号处理函数。
信号相关函数
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);为进程安装信号处理器,struct sigaction数据结构是用来保存信号处理器的相关信息。
int sigemptyset(sigset_t *set);将信号集合清空。
int sigfillset(sigset_t *set);将信号集合设置成包含所有的信号,在对信号进行操作以前一定要对信号集进行初始化。
int sigaddset(sigset_t *set, int signo);向信号集中加入signo对应的新信号。
int sigdelset(sigset_t *set, int signo);从信号集中删除signo对应的一个信号。
int sigismember(const sigset_t *set, int signo);判断某个信号是否在信号集中。
int sigprocmask(int how,const sigset_t *set, sigset_t *oset);设置进程的信号屏蔽码。信号屏蔽码可以用来在某段时间内阻塞一些信号集中的信号。
管道通信:是最古老的Unix IPC工具,一个进程从管道一头写数据,另一个进程从管道另一头读数据,以实现它们之间通信的共享方式,又称pipe文件。由于发送和接收都是利用管道进行通信的,故称为管道通信。通信方式是单向的。管道类型分为:无名管道、命名管道
管道通信的思想
1.发送进程可以源源不断的从pipe一端写入数据流,在规定的pipe文件的最大长度(如4096字节)范围内,每次写入的信息长度是可变的。
2.接收进程在需要时可以从pipe的另一端读出数据,读出单位长度也是可变的。
基本管道调用函数
int do_pipe(int *fd);创建管道
static int pipe_release(struct inode *inode, int decr, int decw);管道释放
无名管道II
显示了每一个file数据结构包含了不同的文件操作例程的向量表的指针:一个用于写,另一个从管道中读。这掩盖了和通用的读写普通文件的系统调用的不同。当写进程向管道中写的时候,字节拷贝到了共享的数据页,当从管道中读的时候,字节从共享页中拷贝出来。
命名管道:又名FIFO,它不是临时的对象,而是文件系统中的实体,可以用mkfifo命令创建。系统必须处理在写进程打开FIFO之前打开FIFO读的进程,以及在写进程写数据之前读的进程。它使用和无名管道一样的数据结构和操作。
(写入端)[Fd1]→pipe(fd)→[Fd0](读出端)
信号量:信号量(Semaphore)和信号是不同的东西,信号是实现约定的固定的值,而信号量是一个变量记录着某些特定信息,它的使用主要是用来保护共享资源,使得该资源在一个时刻只让一个进程拥有。
信号量的数据结构:使用semid_ds数据结构表达信号量。系统中所有的semid_ds数据结构都由semary指针向量表指向。每一个信号灯数组中都有sem_nsems,通过sem_base指向的一个sem数据结构来描述
信号量机制的实现
1.对信号量的操作只有两个:P、V。
2.为了在逻辑上便于组织信号量,信号量机制中有一个概念是信号量组。我们在一个信号量组中创建相关的信号量,这样逻辑上清晰也便于管理。
3.在使用之前同样需要对他们进行初始化:生成或打开信号量组,向其中生成或删除指定的信号量。
4.一个信号量必须属于一个信号量组,否则不能被系统所使用。
5.信号量和信号量组是不会被系统所自动清理的,所以在进程退出前,需要及时清理生成的那些信号量。
信号量的相关函数
int semget(key_t key, int nsems, int semflg);创建一个新的信号量组或获取一个已经存在的信号量组。
int semop(int semid, struct sembuf *sop, int nsops);一次对一个或多个信号量进行操作,用于P、V操作。
Int semctl(int sem_id, int semnum, int cmd, union semun arg);用来获取一些信号量的使用信息或者是来对信号量进行控制。
共享内存
共享内存是进程通信的一个重要方法,为进程提供了直接通信的手段。操作系统中一个或多个进程通过同时出现在它们的虚拟地址空间的内存通讯,该虚拟内存被每个共享进程的页表所引用,它们的地址无需相同。进程对共享内存的访问是受控的,信号量等机制实现了共享内存访问的同步
共享内存的简单原理
每一个新创建的内存区域都用一个shmid_ds数据结构来表达。这些数据结构保存在shm_segs向量表中。Shmid_ds数据结构描述了这个共享内存取有多大、多少个进程在使用它以及共享内存如何映射到它们的地址空间。由共享内存的创建者来控制对于这块内存的访问权限和它的key是公开或私有。如果有足够的权限它也可以把共享内存锁定在物理内存中。每一个希望共享这块内存的进程必须通过系统调用粘附(attach)到虚拟内存。这为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。进程可以选择共享内存在它的虚拟地址空间的位置或者由Linux选择一块足够的的空闲区域。这个新的vm_area_struct结构放在由shmid_ds指向的vm_area_struct列表中。通过vm_next_shared和vm_prev_shared把它们连在一起。
共享内存的基本函数
int shmget(key_t key,int size,int shmflg);建立新的共享内存或一个已存在的共享内存描述字
void *shmat(int shmid,const void *shmaddr,int shmflg);将物理共享内存粘附到进程虚拟地址空间
int shmdt(const void *shmaddr);进程从其虚拟地址空间分离共享内存
int shmctl(int shmid,int cmd,struct shmid_ds *buf);查询及设置一个共享内存
共享内存机制的实现
在使用一个共享内存之前我们调用shmat得到共享内存的开始地址,使用结束以后我们使用
shmdt断开这个内存。
进程通过系统调用粘附到虚拟内存,即创建了一个新的描述这块共享内存的vm_area_struct数据结构,这个新的vm_area_struct结构放在由shmid_ds指向的vm_area_struct列表中。通过vm_next_shared和vm_prev_shared把它们连在一起。
虚拟内存在粘附的时候其实并没有创建,而发生在第一个进程试图访问它的时候。
第一个访问共享内存页的进程使得这一页被创建,而随后访问的其他进程使得此页被加到它们的虚拟地址空间。
当进程不再需要共享虚拟内存的时候,它们从中分离出来。只要仍旧有其他进程在使用这块内存,这种分离只是影响当前的进程。
当共享这块内存的最后一个进程从中分离出的时候,共享内存当前在物理内存中的页被释放
消息队列:消息队列是比较高级的一种进程间通信方法,实现一个或多个进程间message传送,一个消息队列可以被多个进程所共享(IPC就是在这个基础上进行的);如果一个进程的消息太多一个消息队列放不下,也可以用多于一个的消息队列(不过可能管理会比较复杂)。
消息队列的基本函数
int msgget(key_t key, int msgflg);获取一个存在的消息队列的ID,或者是根据跟定的权限创建一个消息队列。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);用来从msqid_ds中获取很多消息队列本身的信息。
int msgsnd(int msqid, void *msgp, size_t msgsz, int msgflg);用于向队列发送消息。
int msgrcv(int msqid, void *msgp, size_t msgsz, long int msgtyp, intmsgflg);从队列中接收消息。
消息队列的实现机制
消息队列,是一个队列的结构,队列里面的内容由用户进程自己定义。实际上,队列里面记录的是指向用户自定义结构的指针和结构的大小。要使用message queue,首先要通过系统调用(msgget)产生一个队列,然后,进程可以用msgsnd发送消息到这个队列,消息就是如上所说的结构。别的进程用msgrcv读取。消息队列一旦产生,除非明确的删除(某个有权限的进程或者用ipcrm命令)或者系统重启。否则,产生的队列会一直保留在系统中。而且,只要有权限,就可以对队列进行操作。消息队列和管道很相似,实际上,管道就是用户消息为1个字节的队列。
消息队列的写进程
每一次一个进程试图向写队列写消息,它的有效用户和组的标识符就要和队列的ipc_perm数据结构的模式比较。如果进程可以想这个队列写,则消息会从进程的地址空间写到msg数据结构,放到消息队列的最后。每一个消息都带有进程间约定的,应用程序指定类型的标记。
消息队列的读进程
从队列中读是一个相似的过程。进程的访问权限一样被检查。一个读进程可以选择是不管消息的类型从队列中读取第一条消息还是选择特殊类型的消息。如果没有符合条件的消息,读进程会被加到消息队列的读等待进程,然后运行调度程序。当一个新的消息写到队列的时候,这个进程会被唤醒,继续运行。
消息队列的简单流程
发送进程 接收流程
Send(m) Receive(m )
Begin begin
向系统申请一个消息缓冲区 P(SM) 等待接的消息的个数
P(mutex) 使用公用缓冲区 P(mutex) 使用公用缓冲区
将发送区消息m送入新申请的消息缓冲区 摘下消息队列中的消息m
把消息缓冲区挂入接收进程的消息队列 将消息队列m从缓冲区复制到接收区
V(mutex)释放缓冲区 释放缓冲区
V(SM)向接收进程发送消息 V(mutex) 释放公用缓冲区
End end
Socket
独立于具体协议的网络编程接口;
在ISO模型中,主要位于会话层和传输层。
网络编程接口:UNIX BSD的套接字(socket)、UNIX System V的TLI
BSD Socket(伯克立套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。
Socket的类型
流式套接字:提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复地发送且按发送顺序接收.内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制,FTP即用此
数据报套接字:提供了一个无连接服务.数据包以独立包形式被发送,不提供无差错保证,数据可能丢失或重复,并且接受顺序无序,网络文件系统NFS
原始套接字(SOCK_RAW):该接口允许对较低层次协议,如IP,ICMP直接访问
基本套接字调用
创建套接字 socket();
绑定本机端口 bind();
建立连接 connect();
接受连接 accept();
监听端口 listen();
数据传输 send(), recv()等
关闭套接字 close();
Socket相关数据结构
struct sockaddr_in
{
short int sin_family; /*通信类型*/
unsigned short int sin_port; /*端口号,网络直接顺序*/
struct in_addr
}
本文由来源 21aspnet,由 javajgs_com 整理编辑,其版权均为 21aspnet 所有,文章内容系作者个人观点,不代表 Java架构师必看 对观点赞同或支持。如需转载,请注明文章来源。