1、管道
•先思考一下:到目前为止,你了解几种进程间交换信息的方法?
•管道是Unix IPC的最古老形式,所有Unix系统都支持,有2个限制:
•1、历史上,管道是半双工的,即数据只能向一个方向流动
•2、管道只能在有共同祖先的进程间使用
•尽管如此,管道仍然是最常用的IPC形式
•#include <unistd.h>
•int pipe(intfd[2]);
# 创建管道,fd[0]用来读,
fd[1]用来写
•有一个参数需要注意:如果多个进程同时写入同一个管道,为避免数据的交叠,单个write调用写入的数据长度必须<=某个值
•POSIX标准规定这个值至少是512,大多Unix的实现都远大于这个值
•一个简单的管道例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int n, fd[2];
char line[64];
pid_t pid;
if( pipe(fd) < 0 )
{
perror("pipe error");
exit(0);
}
if( (pid = fork()) < 0 )
{
perror("fork error");
exit(0);
}
else if( pid > 0 ) /* parent */
{
close(fd[0]);
write(fd[1], "hello world\n", 12);
}
else /* child */
{
close(fd[1]);
n = read(fd[0], line, 64);
write(STDOUT_FILENO, line, n);
}
exit(0);
}
/* 更有意思的实现是借助于重定向 */
•在“信号”一章,借助于信号机制实现父子进程间同步
•如何借助于管道实现父子进程间同步?
static int pfd1[2], pfd2[2];
void TELL_WAIT(void)
{
if( pipe(pfd1) < 0 || pipe(pfd2) < 0 )
{
perror("pipe error");
exit(0);
}
}
void TELL_PARENT(pid_t pid)
{
if( write(pfd2[1], "c", 1) != 1 )
{
perror("write error");
exit(0);
}
}
void WAIT_PARENT(void)
{
char c;
if( read(pfd1[0], &c, 1) != 1 )
{
perror("read error");
exit(0);
}
if( c != 'p' )
{
perror("WAIT_PARENT: incorrect data");
exit(0);
}
}
void TELL_CHILD(pid_t pid)
{
if( write(pfd1[1], "p", 1) != 1 )
{
perror("write error");
exit(0);
}
}
void WAIT_CHILD(void)
{
char c;
if( read(pfd2[0], &c, 1) != 1 )
{
perror("read error");
exit(0);
}
if( c != 'c' )
{
perror("WAIT_CHILD: incorrect data");
exit(0);
}
}
•练习题:如何使用管道、fork、重定向、及bc命令,实现算数运算?
•这种技术称为“协作进程”Coprocesses
2、popen、pclose函数
•#include <stdio.h>
•FILE *popen(const char *cmdstring,
const char *type);
# fork,然后执行cmdstring
•int pclose(FILE *fp);
# 关闭文件指针,等待进程结束,必须与popen配对使用
•管道的常见用途是:创建一个连接到另一个进程的管道,然后读其输出或给它输入
•popen的目的是简化这种管道操作
•fp =
popen(cmdstring, "r")
•fp =
popen(cmdstring, "w")
•cmdstring的执行方式是:sh -c
cmdstring
•即借助于shell执行,所以cmdstring中可以指定任何shell可以识别的元素
•思考:如何通过popen实现bc命令的调用?
3、FIFO(命名管道)
•与普通管道相比,借助于FIFO,不相关的进程之间也可以交换数据
•#include <sys/stat.h>
•int mkfifo(const char *pathname, mode_t mode);
# 创建命名管道
•用处1,在shell的多个管道线之间交换数据:
•mkfifo fifo1
# 创建命名管道(mkfifo同样是一个命令的名字)
•prog3 < fifo1 &
•prog1 <
infile | tee fifo1 | prog2
•用处2,用于客户机/服务器模型中,实现client、server的通信
4、XSI IPC
•三种类型的IPC(消息队列、共享内存、信号灯)有很多相似之处,共性如下:
•1、标识符与键值
•标识符与文件描述符的身份类似,是非负整数
•KEY(键值),与文件名称的身份类似,区别在于键值是整数
•键值可以指定为IPC_PRIVATE,将总是创建一个新的IPC结构
•可以通过ftok生成一个键值(并不保证唯一)key_tftok(const
char *path, int id);
•2、权限结构
•每个IPC对应一个ipc_perm结构,定义属主及权限
•3、参数限制
•4、优缺点
•它们是整个系统范围的,进程结束后它们可能仍然残留,
•不能使用操作文件的那些函数操作,不得不引入了10多个专门的函数
5、消息队列
•通过msgget创建/获取一个消息队列,使用msgsnd向队列中添加消息,使用msgrcv从队列中收取(同时删除)消息
•每条消息有一个long整数形式的消息类型,一个非负长度,及相应的数据
•消息队列的读取不限于先进先出,可以根据类型收取
•1、创建一个新的或获取一个现有的队列
•#include <sys/msg.h>
•int msgget(key_t key,
int flag);
•返回msqid用于后续操作
•2、消息队列的控制操作
•int msgctl(int
msqid, int
cmd, struct
msqid_ds *buf);
•可以执行3种控制操作,cmd分别对应IPC_STAT、IPC_SET、IPC_RMID
•struct
msqid_ds包含权限、队列容量上限等信息
•3、消息发送
•int msgsnd(int
msqid, const void *ptr,
size_t nbytes,
int flag);
•第二个参数的定义可以参考
•struct {
• long
mtype; /* positive message type */
• char
mtext[512];}
/* message data, of length nbytes */
•如果flag指定为IPC_NOWAIT,消息发送/接收的行为是非阻塞的
•4、消息接收
•ssize_t
msgrcv(int
msqid, void *ptr,
size_t nbytes, long type,
int flag);
•如果flag指定为MSG_NOERROR,超长消息(>
nbytes)将被截断,剩余部分丢弃;
•否则,超长消息的接收将返回错误
•type == 0,读取队列中的第一条消息
•type > 0,读取指定类型的第一条消息
•type < 0,读取类型<=|type|的第一条消息
/* msg.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define KEYTEST 0x123000
struct myinfo
{
long type;
char mtext[64];
};
int main(void)
{
int qid;
struct myinfo qbuf;
// create the msg queue
if( (qid = msgget(KEYTEST, IPC_CREAT | IPC_EXCL | 0666)) < 0 )
{
perror("msgget");
exit(0);
}
// msgsnd
qbuf.type = 3;
strcpy(qbuf.mtext, "This is a test message");
// notice the length 4
if( msgsnd(qid, &qbuf, 4, 0) < 0 )
{
perror("msgsnd");
exit(0);
}
}
/* msg1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define KEYTEST 0x123000
struct myinfo
{
long type;
char mtext[64];
};
int main(void)
{
int qid;
struct myinfo qbuf;
// create the msg queue
if( (qid = msgget(KEYTEST, 0)) < 0 )
{
perror("msgget");
exit(0);
}
// msgrcv
memset(&qbuf, 0, sizeof(qbuf));
if( msgrcv(qid, &qbuf, sizeof(qbuf.mtext), 0, 0) < 0 )
{
perror("msgrcv");
exit(0);
}
printf("type=%d, msg=[%s]\n", qbuf.type, qbuf.mtext);
}
/* msg2.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define KEYTEST 0x123000
int main(void)
{
int qid;
// create the msg queue
if( (qid = msgget(KEYTEST, 0)) < 0 )
{
perror("msgget");
exit(0);
}
// msgctl, remove the queue
if( msgctl(qid, IPC_RMID, 0) < 0 )
{
perror("msgctl");
exit(0);
}
}
6、共享内存
•共享内存允许多个进程共享一块内存区域,是最快速的IPC机制
•1、创建/获取一段共享内存
•#include <sys/shm.h>
•int shmget(key_t key,
size_t size,
int flag); #
创建或获取一段内存
•2、控制操作
•int shmctl(int
shmid, intcmd,
struct shmid_ds *buf);
•3、连接共享内存
•void *shmat(int
shmid, const void *addr,
int flag);
•参数addr通常指定为0,让系统自行选择起始地址
•如果flag包含SHM_RDONLY,这段内存将是只读的
•4、断开连接,int
shmdt(void *addr);
•共享内存段被连接到进程空间的哪个地址?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#define ARRAY_SIZE 40000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE 0600 /* user read/write */
char array[ARRAY_SIZE]; /* uninitialized data = bss */
int main(void)
{
int shmid;
char *ptr, *shmptr;
printf("array[] from %lx to %lx\n", (unsigned long)&array[0],
(unsigned long)&array[ARRAY_SIZE]);
printf("stack around %lx\n", (unsigned long)&shmid);
if( (ptr = malloc(MALLOC_SIZE)) == NULL )
{
perror("malloc");
exit(0);
}
printf("malloced from %lx to %lx\n", (unsigned long)ptr,
(unsigned long)ptr + MALLOC_SIZE);
if( (shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0 )
{
perror("shmget");
exit(0);
}
if( (shmptr = shmat(shmid, 0, 0)) == (void *)-1 )
{
perror("shmat");
exit(0);
}
printf("shared memory attached from %lx to %lx\n",
(unsigned long)shmptr, (unsigned long)shmptr + SHM_SIZE);
if( shmctl(shmid, IPC_RMID, 0) < 0 )
{
perror("shmctl");
exit(0);
}
exit(0);
}
•回顾一下内存映射IO:
•可以映射/dev/zero设备
•也可以匿名映射:mmap(0, SIZE,
…, MAP_ANON | MAP_SHARED,
-1, 0)
7、信号灯
•信号灯是一种进程同步机制(想想十字路口的红绿灯)
•信号灯其实是一个集合,里面可以包含多个灯
•1、创建/获取信号灯
•#include <sys/sem.h>
•int semget(key_t key,
int nsems,
int flag);
# nsems指定集合内的灯的数目
•2、信号灯控制
•int semctl(int
semid, int
semnum,
int cmd, ... /* union
semunarg */);
•union semun {
• int
val; /* for SETVAL */
• structsemid_ds *buf;
/* for IPC_STAT and IPC_SET */
• unsigned short *array; /* for GETALL and SETALL */
•};
•cmd参数的取值:GETVAL、SETVAL、GETALL、SETALL、IPC_XX
…
•3、信号灯操作(原子的执行一组信号灯操作)
•intsemop(intsemid,
structsembufsemoparray[],
size_tnops);
•structsembuf {
• unsigned short
sem_num; /* member # in set (0, 1, ..., nsems-1) */
• short
sem_op; /* operation (negative, 0, or positive) */
• short
sem_flg; /* IPC_NOWAIT,
SEM_UNDO */
•};
•4、一个示例
/* cat sem.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define KEYTEST 0x12300
typedef union /* 信号灯取值联合 */
{
int val;
struct semid_ds *buf;
unsigned short *array;
} USEMUN;
int main(void)
{
int id;
USEMUN un;
// create
if( (id = semget(KEYTEST, 1, IPC_CREAT | IPC_EXCL | 0666)) < 0 )
{
perror("semget");
return(-1);
}
// initialize
un.val = 1;
if( semctl(id, 0, SETVAL, un) < 0 )
{
perror("semctl");
return(-1);
}
}
/* cat sem1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define KEYTEST 0x12300
int main(void)
{
int id;
struct sembuf sb;
// get
if( (id = semget(KEYTEST, 0, 0)) < 0 )
{
perror("semget");
return(-1);
}
// operation lock
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO; /* 进程退出时,回滚操作过的信号灯值 */
if( semop(id, &sb, 1) < 0 )
{
perror("semop");
return(-1);
}
printf("OK, I get it, sleep 30 now ...\n");
sleep(30);
// operation unlock
sb.sem_op = 1;
if( semop(id, &sb, 1) < 0 )
{
perror("semop");
return(-1);
}
}
•共享内存通常与信号灯一起使用,寻求进程同步机制
•信号灯与记录锁的比较:
•信号灯更快;记录锁方便
8、客户机/服务器特性
•广义而言,一个进程请求另外一个进程为其服务,都可以称为C/S模型
•他们可以采用各种IPC通信机制