1、管道管道特指无名管道,如shell中的管道操作符“|”就是一个无名管道。它只有一个传输方向。
#include <unistd.h>
int pipe(int filedes[2]);
|
pipe
系统调用用于建立一个无名管道。管道是一个linux文件,所以对pipe创建的管道可以用open, close, write, read,
close等函数来访问。它使用两个文件描述符filedes[0]和filedes[1]分别对其进行读出和写入,且filedes[0]只读
(O_RDONLY),filedes[1]只写(O_WRONLY)。而且读或者写的时候,必须关掉另外一个文件描述符。pipe成功返回0,出错返回
-1并设置errno。
#include <stdio.h>
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
|
popen和pclose是标准C中更简易的管道实现。popen将创建一个管道并fork一个用exec执行command的子进程,mode决定了对这个command的标准输入输出的读(r)或者写(w)。
popen成功返回一个文件流指针供读取command的输出使用。失败返回-1并设置errno。
pclose则关闭该文件流。
2、FIFO
FIFO也称为有名管道,使用前者为称呼的更多。
在shell里面可以通过mkfifo创建一个有名管道,对有名管道的处理在shell里面通常可以使用“>”和“<”这两个重定向操作符。例如
$ mkfifo -m 644 fifo1
$ cat < fifo1&
$ ls > fifo1 |
-m表示fifo的访问模式即权限位。cat和ls的重定向可以在两个shell窗口中分别执行以使对管道的作用更直观一些。例如在x下的终端执行ls > fifo1,然后用ctrl+alt+f1进文本控制台执行cat < fifo1接收。
在C中创建fifo的系统调用和shell中很类似:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
|
pathname即要创建FIFO的路径和文件名,mode即访问模式。mode会被当前进程的umask修改。对FIFO的打开、关闭、读取、写入、删
除的使用分别通过使用open, close, read, write,
unlink这些操作文件的系统调用来完成。注意FIFO的两端在读写前都要先打开,且O_NONBLOCK标志在FIFO的另一端没有对应的输出/输入
时会导致write/read调用立即返回。
3、SysV IPC
众所周知,70年代后主流的UNIX主要分为System V和BSD两大分支,现代的UNIX以及类UNIX操作系统基本上都是这两大支流的综合以及进化。BSD最有名的是它的套接字socket及其TCP/IP协议栈,而SysV最早让人瞩目的特性估计就是它的IPC对象机制了。
IPC不属于Linux下的文件概念范畴,它只存在于内核。通过使用ipcs和ipcrm这两个专门的shell命令来查看和管理。故IPC对象也需要专门的系统调用而不能使用open, read, write等操作文件的函数来使用。
SysV的IPC对象包括共享内存、消息队列和信号灯三种。通过ipcget函数(shmget, msgget, semget)创建它们,IPC对象一个特性是使用了关键字对象key(类型为key_t,显然定义于sys/types.h),get函数可以通过识别关键字而判断是创建新的IPC对象还是链接到一个已有的IPC对象。
如何使用key有三个传统方法:
用IPC_PRIVATE作为关键字,保证创建新的IPC结构。
把关键字保存在公共的头文件中。这可以保证包含这个头文件的程序都能访问到相同的关键字,但进程一般无法判断要创建还是要使用一个已有的IPC结构,而且可能出现关键字已经被占用的情况。
使用ftok函数。安装manpages-posix manpages-posix-dev这些包后可以用man 3 ftok来查看ftok的用法。
# include <sys/types.h>
# include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
|
ftok通过输入一个路径和一个项目标识符来生成关键字,可以把这个路径和项目标识符放到公共的头文件或者预定义的配置文件中。
ftok不能保证生成唯一的关键字,存在着潜在隐患,建议尽量不要使用并忽略它。
除了关键字外,IPC还接受一个flag标志控制get的行为,其中IPC_CREAT这个标志在指定关键字还没有IPC对象时将创建一个新的IPC对象。
IPC使用模式(mode_t mode)描述IPC对象的读或写的属性(信号灯是读SEM_R和修改SEM_A),但没有执行这个属性。
使用ipcget创建队列后,将产生一个ipc_perms结构记录这个ipc的属性。并可以通过ipcctl(shmctl, msgctl, semctl)用IPC_SET标志去修改它们。IPC_SET和IPC_RMID两个标志只能在进程id等于uid或者cuid或者为超级用户时才能使用。
struct ipc_perms{
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
ulong seq;
key_t key;
}
|
ipc使用WAIT等待这个术语对应linux文件的BLOCK阻塞状态。
4、共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flags);
|
shmget创建一个共享内存IPC,size为内存段大小,最大不超过处理器本身的页大小(BUFSZ),flags为IPC_CREAT, IPC_EXCL,以及权限位(属性模式)逻辑或的结果。shmget执行成功,则返回共享内存IPC的标识符。否则返回-1并设置errno。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
char *shmat(int shmid, char *shmaddr, int flags);
int shmdt(char *shmaddr);
|
shmat把共享内存IPC附加映像到当前进程的地址空间shmaddr中去,shmaddr为了让系统自动分配可以用的地址,一般都设置为0,flags设置为SHM_RDONLY则限制该段为只读,否则默认是可读写的。shmat成功返回该映像的地址,失败返回-1并设置errno。
shmdt在共享内存IPC使用完毕后,把映像地址shmaddr分离出进程的地址空间。
5、消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flags);
|
msget仅仅使用key和flags来创建消息队列IPC,并返回该消息的标识符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flags);
|
msgsnd把ptr所指向的msgbuf结构的消息以nbytes添加到队列msqid的末尾,并使用flags控制其行为。
msgbuf在
中的定义为
struct msgbuf{
long mtype;
char mtext[BUFSZ];
}
|
msgbuf结构只是一个模板,它的mtext成员的长度并没有被限定而应程序员根据情况自己定。如果msgbuf以null结尾则送到队列时null要被截掉。
flags只能是0或者IPC_NOWAIT。后者说明msgsnd在队列满时立即返回,否则将等待直到:1) 队列有了可以容纳此消息的空间;2) 队列被删除;3) 捕捉到一个信号,处理后返回。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flags);
|
msgrcv从队列msqid中读取一条nbytes长的消息并把它和消息类型一起写到ptr所指向的结构中。type决定了从队列的什么地方取出消息,为0则按先进先出返回队列的第一条消息;大于0则与消息类型匹配,返回匹配的第一条消息,可以用来设置优先级;小于0则取绝对值,返回一个消息类型匹配为小于等于这个绝对值的第一条消息。如果flags设置了MSG_NOERROR位,在返回的消息比nbytes多时将被截短;如果flags为IPC_NOWAIT,则在没有收到消息时立即返回,否则将等待直到:1) 有了指定type的消息;2) 队列被删除;3) 捕捉到一个信号并处理后返回。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
|
msgctl用于对msgid的控制,cmd包括了IPC_RMID —— 删除队列, IPC_STAT —— 把队列的msqid_ds结构填充到buf, IPC_SET —— 修改msg_perms设置队列的属性(UID, GID, 访问模式, 队列最大字节数等)。
7、信号灯
信号灯也可以说是一种锁,但它可以用来控制除了文件以外的更多资源。信号灯的初始值一般为一个正数,决定了可以分配的资源数,为进程分配一个资源后自减,减到0后被锁住。SysV IPC要求信号灯必须定义为一个集合。创建信号量时则指定此集合中的值。
双态信号灯是最简单的一种,0表示锁定,无资源;1表示解锁,有一个可用资源。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flags);
|
semget通过key(IPC_PRIVATE等)创建nsems个(集合)新的或者访问一个(nsems为0)已经存在的信号灯,并使用flags(IPC_CREAT、8进制访问模式等)控制它的行为。成功在返回信号灯描述符,失败返回-1并设置errno。
操作信号灯的常用函数为semop。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, unsigned nops);
|
nops为sembuf结构数组集合的元素个数。sembuf结构的定义为
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
}
|
这里sem_num是信号灯在集合中的编号,从0开始。
sem_op为正数则释放信号灯控制的资源并增加信号灯的值;sem_op为负数则先考察信号灯减去sem_op绝对值的结果:大于0则使用信号灯,等于0则等待资源被释放,小于0则等待到信号灯的值增加到大于等于sem_op绝对值为止;sem_op为0则进程等待信号灯的值为0然后返回。
上面说的等待都是在sem_flg没有IPC_NOWAIT的前提的,sem_flg标志包括IPC_NOWAIT和SEM_UNDO等。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
|
semctl使用cmd控制semid中的semnum信号灯,并以联合arg中的成员作为参数。
联合semun的定义为:
union semun{
int val;
struct semid_ds *buf;
unsigned short int *array;
}
|
cmd包括了
GETVAL —— 返回信号灯当前解锁或者加锁的状态;
SETVAL —— 以联合arg的成员val(即arg.val)设置信号灯的当前状态;
GETPID —— 返回上次调用semop的进程的PID;
GETNCNT —— 返回正在信号灯上等待的进程数;
GETZCNT —— 返回等待信号灯的值为0的进程数;
GETALL —— 返回和semid关联的集合中所有信号灯的值;
SETALL —— 以联合arg的成员array(即arg.array)设置和semid关联的集合中所有信号灯的值;
IPC_RMID —— 删除含semid的信号灯;
IPC_SET —— 设置信号灯的访问模式(权限位);
IPC_STAT —— 复制信号灯的semid_ds到arg.buf中。
阅读(2024) | 评论(0) | 转发(0) |