1、管道
•
先思考一下:到目前为止,你了解几种进程间交换信息的方法?
•
管道是
Unix IPC
的最古老形式,所有
Unix
系统都支持,有
2
个限制:
•
1
、历史上,管道是半双工的,即数据只能向一个方向流动
•
2
、管道只能在有共同祖先的进程间使用
•
尽管如此,管道仍然是最常用的
IPC
形式
•
#include <
unistd.h
>
•
int
pipe
(
int
fd
[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_t
ftok
(
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
,
int
cmd
,
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
semun
arg
*/);
•
union
semun
{
•
int
val
; /* for SETVAL */
•
struct
semid_ds
*
buf
; /* for IPC_STAT and IPC_SET */
•
unsigned short *array; /* for GETALL and SETALL */
•
};
•
cmd
参数的取值:
GETVAL
、
SETVAL
、
GETALL
、
SETALL
、
IPC_XX
…
•
3
、信号灯操作(
原子
的执行
一组
信号灯操作)
•
int
semop
(
int
semid
,
struct
sembuf
semoparray
[],
size_t
nops
);
•
struct
sembuf
{
•
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
通信机制