中国科学技术大学计算机系 陈香兰(0512-87161312) xlanchen@ustc.edu.cn Autumn 2010 Linux操作系统分析 中国科学技术大学计算机系 陈香兰(0512-87161312) xlanchen@ustc.edu.cn Autumn 2010
进程间同步和通信
进程间通信 IPC,Inter-Process Communication Unix系统提供的基本的IPC包括: 1、管道和FIFO(有名管道) 2、消息 3、信号量 4、共享内存区 5、套接字 2018/12/8 Linux OS analysis
1、管道(pipe) 管道是所有Unix都提供的一种IPC机制 管道是半双工的,数据只能向一个方向流动; 一个进程将数据写入管道, 另一个进程从管道中读取数据 数据的读出和写入: 写入的内容每次都添加在管道缓冲区的末尾, 每次都是从缓冲区的头部读出数据。 需要双方通信时,需要建立起两个管道; 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程); 2018/12/8 Linux OS analysis
在shell中使用管道的例子 命令:“ls | more” 使用pipeline “|”将两个命令”ls”和“more”连接起来,使得ls的输出成为more的输入 也可以使用如下的两个命令 命令1:“ls > tmp” 命令2:”more < tmp” 命令1把ls的输出重定向到tmp文件中; 命令2把more的输入重定向到tmp文件 2018/12/8 Linux OS analysis
创建一个管道 管道可看成是被打开的文件,但并没有真实的 文件与之对应 pipe()系统调用用来创建一个新的管道 #include <unistd.h> int pipe(int filedes[2]); 管道两端分别用描述符filedes[0]和filedes[1]描述 管道两端的功能是固定的: filedes[0]只能用于读,称为管道读端; filedes[1]只能用于写,称为管道写端。 若试图从写端读,或者向读端写都将导致错误发生。 一般文件的I/O函数都可用于管道,如close、read、write等 2018/12/8 Linux OS analysis
使用管道的典型程序 testpipe.c 管道只能在具有亲缘关系的进程之间进行通信 任意的两个进程不可能共享同一个管道 通过fork传递管道的描述符 任意的两个进程不可能共享同一个管道 无法打开已经存在的管道 2018/12/8 Linux OS analysis
FIFO 管道的一个重大限制是它没有名字,因此 只能用于具有亲缘关系的进程间通信,在 有名管道(named pipe或FIFO)提出后, 该限制得到了克服。 FIFO,有名管道 特殊的文件类型: 1,严格遵循先入先出的读写规则 2,类似管道,在文件系统中不存在数据块,而是与一块内核缓冲区相关联 3,有名字,FIFO的名字包含在系统的目录树结构中,可以按名访问 2018/12/8 Linux OS analysis
以及:open,close,read,write等普通文件操作 FIFO的操作: 以及:open,close,read,write等普通文件操作 2018/12/8 Linux OS analysis
FIFO举例 创建一个FIFO:createfifo.c 向FIFO写:writefifo.c 从FIFO读:readfifo.c 2018/12/8 Linux OS analysis
createfifo.c 2018/12/8 Linux OS analysis
writefifo.c 2018/12/8 Linux OS analysis
readfifo.c 2018/12/8 Linux OS analysis
2、消息队列 消息队列就是一个消息的链表。 可以把消息看作一个记录,具有特定的格式以及特定的优先级。 对消息队列有写权限的进程可以按照一定的规则向消息队列添加新消息; 对消息队列有读权限的进程则可以从消息队列中读走消息。 2018/12/8 Linux OS analysis
消息队列的创建 int msgget(key_t key, int msgflg) 根据给定的键值,返回对应的消息队列 若能找到,则返回已有的;否则,创建一个新的 2018/12/8 Linux OS analysis
发送消息 int msgsnd(int msqid, //目标消息队列 struct msgbuf *msgp, //待发送的消息 int msgsz, //消息的大小 int msgflg); //标志 对于发送消息来讲,msgflg有意义的标志为 IPC_NOWAIT:指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待 2018/12/8 Linux OS analysis
接收消息 读消息标志msgflg可以为以下几个常量的或: int msgrcv(int msqid, //msqid为消息队列描述字 struct msgbuf *msgp, //消息返回后存储这里 int msgsz, //指定消息内容的长度 long msgtyp, //请求读取的消息类型 nt msgflg); 读消息标志msgflg可以为以下几个常量的或: IPC_NOWAIT:如果没有满足条件的消息,立即返回,此时,errno=ENOMSG IPC_EXCEPT:与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息 IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。 2018/12/8 Linux OS analysis
消息队列的其他操作 对由msqid标识的消息队列执行cmd操作: IPC_RMID:删除消息队列 int msgctl(int msqid, int cmd, struct msqid_ds *buf); 对由msqid标识的消息队列执行cmd操作: IPC_STAT:获取消息队列信息,返回的信息存贮在buf中; IPC_SET:设置消息队列的属性,要设置的属性存储在buf中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。 IPC_RMID:删除消息队列 2018/12/8 Linux OS analysis
3、信号量 Semphore,用来对资源进行并发控制访问 通常是一个计数器 当进程需要访问资源,但资源不可用时,将计数值-1,并阻塞 如果资源可用,值>0 如果不可用,值<=0 当进程需要访问资源,但资源不可用时,将计数值-1,并阻塞 当进程释放资源,使得资源有资源可用时,就唤醒被阻塞的进程 2018/12/8 Linux OS analysis
semop用来获取或释放信号量对应的资源 获得信号量集描述符 semop用来获取或释放信号量对应的资源 2018/12/8 Linux OS analysis
对semop中sembuf结构的解释 sembuf中包含下列几个分量: 其中,sem_flg可以是 sem_op的值确定要进行的操作 unsigned short sem_num; //要操作的信号量的序号 short sem_op; //要进行的操作 short sem_flg; //与操作相关的一些标记 其中,sem_flg可以是 IPC_NOWAIT:当期望的操作无法完成时,直接返回 和SEM_UNDO:自动释放标记 sem_op的值确定要进行的操作 >0:释放资源 =0:wait-for-zero <0:申请资源 2018/12/8 Linux OS analysis
Semctl对信号量进行控制,可以用来删除一个信号量 第4个参数可选,是一个union,其含义根据cmd进行解释 union semun { int val; struct semid_ds *buf; unsigned short * array; struct seminfo *__buf; } 2018/12/8 Linux OS analysis
对semctl中cmd等的解释 cmd可以是: IPC_STAT,将信号量集的信息复制到第四个参 数所提示的空间中,此时第四个参数为:struct semid_ds *buf。此时第二个参数无用 IPC_SET,与上一个操作相反 IPC_RMID,删除信号量集,不使用第四个参数 GETALL/SETALL,使用第四个参数:unsigned short* array,获取/设置所有信号量的值 GETVAL/SETVAL,前者使用第4个参数:int val,获得/设置指定信号量的值 GETNCNT、GETPID、GETZCNT 2018/12/8 Linux OS analysis
Cmd使用举例 union semun args; args.val=1; semctl(sem_id,sem_no,SETVAL,args); int i=semctl(sem_id,sem_no,GETVAL); semctl(sem_id,sem_no,IPC_RMID); 2018/12/8 Linux OS analysis
使用信号量互斥 usesem.c 注意:示例程序仅仅是为了说明进程之间的互斥 2018/12/8 Linux OS analysis
4、共享内存 允许两个或多个进程通过把公共数据放入一个共享内存区来访问它们 获得或创建一个共享内存区的IPC标志符 将一个共享内存区“附加”到一个进程上, 使得进程可以访问共享内存区的内容 进程通过shmaddr指定并获得共享内 存区在该进程中的起始地址 2018/12/8 将指定位置的共享内存区从进程中分离出去 Linux OS analysis
两个进程通过共享内存进行通信 testshm.c testshm2.c 2018/12/8 Linux OS analysis
5、套接字socket 套接字不仅可以用来实现网络间的进程通信,也可以用来实现本地的进程间通信 相关调用包括: Socket Listen Bind Connect/accept Send/recv,read/write Close … 2018/12/8 Linux OS analysis
Socket建立一个套接字 对于基于TCP/IP的编程,通常为: sockfd=socket(AF_INET, SOCK_STREAM,0) 2018/12/8 Linux OS analysis
地址绑定 将一个套接字与一个地址绑定 网络地址: struct sockaddr { unsigned short sa_family; char sa_data[14]; }; address family 例如:AF_INET 14 个字节,协议地址 2018/12/8 Linux OS analysis
Internet的地址 struct sockaddr_in { /* for convenience */ short int sin_family; unsigned short int sin_port; /* 2 bytes */ struct in_addr sin_addr;/* 4 bytes */ unsigned char sin_zero[8]; }; sin_port和sin_addr要使用网络字节序,跟主机上的不一定一致,需要转换: htons=host to net short htonl=host to net long 2018/12/8 Linux OS analysis
Server端需要进行地址绑定 int sd; struct sockaddr_in my_addr; sd = socket(AF_INET, SOCK_STREAM, 0); bzero(&(my_addr),sizeof(struct sockaddr_in); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(MYPORT); my_addr.sin_addr.s_addr=INADDR_ANY; bind(sd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)); bind填写了服务器的地址和端口信息,最大的端口值为65535 2018/12/8 Linux OS analysis
服务器调用listen设置侦听 第二个参数指定最多客户数 listen将立即返回 listen(sd,5); 2018/12/8 Linux OS analysis
服务器调用accept接收客户连接请求 Accept将会blocking直到有客户连接 sin_size = sizeof(struct sockaddr_in); newsd = accept(sd, &their_addr, &sin_size); Accept将会blocking直到有客户连接 返回与客户端相关的newsd,并将客户端的信息存放在their_addr中 2018/12/8 Linux OS analysis
TCP服务器编程模型 socket(...); bind(...); listen(...); while(1) //循环服务器 { accept(...); while(1) { read(...); process(...); write(...); } close(...); } 2018/12/8 Linux OS analysis
客户端调用connect请求连接 在serv_addr中指明要连接的服务器地址和端口 成功以后,就可以进行send/recv或者read/write了 2018/12/8 Linux OS analysis
struct sockaddr_in dest_addr; sd = socket(AF_INET, SOCK_STREAM, 0); bzero(&(dest_addr),sizeof(struct addr_in)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(DEST_PORT); dest_addr.sin_addr.s_addr = inet_addr(DEST_IP); connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)); 2018/12/8 Linux OS analysis
TCP客户端编程模型 socket(…) connect(…) while(1) { read(…) … write(…) } close(…) 2018/12/8 Linux OS analysis
server.c client.c 2018/12/8 Linux OS analysis