#include <unistd.h>
/* 成功返回0,出错返回-1 */
int pipe(int* fd);
该函数通过参数fd返回两个文件描述符,其中fd[ 0 ]为读而打开,fd[ 1 ]为写而打开, 且fd[ 1 ]的输出为fd[ 0 ]的输入
单进程中的管道几乎没有任何用处,因为进程内部的数据交换根本不需要通过管道来进行. 通常调用pipe的进程会接着fork一个子进程,并在父进程和子进程两端分别关闭读或写的文件描述符,通过这种方式就能实现父子进程间的通讯了.
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if(pid >0) /* 父进程 */
{
printf("parent running\n");
fflush(stdout);
close(fd[0]); /* 关闭读fd,父进程负责发送消息 */
write(fd[1],"this is a messae from parent\n",255);
close(fd[1]);
waitpid(pid,NULL,0);
}
else
{
printf("child running\n");
fflush(stdout);
close(fd[1]);
if(dup2(fd[0],STDIN_FILENO) != STDIN_FILENO)
{
printf("dup2 error\n");
return -1;
}
close(fd[0]);
if(execl("/bin/echo_from_stdin.sh","/bin/echo_from_stdin.sh",(char*)0) < 0)
{
printf("exec echo failed\n");
}
/* char s[255+1]=""; */
/* printf("%s\n",gets(s)); */
}
return 0;
}
child running parent running
this is a messae from parent
这两个函数用于进程用system运行外部命令,并创建管道与该外部命令子进程进行交流
#include <stdio.h>
/* 若type为"r",则返回连接到cmd标准输出的FILE* */
/* 若type为"w",则返回连接到cmd标准输入的FILE* */
FILE* popen(const char* cmd,const char* type);
/* 关闭标准IO留,等待popen中cmd运行结束,再返回其终止状态,出错则返回-1 */
int pclose(FILE* fp);
上面pipe的例子可以改写为:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>
int main()
{
FILE* fp = popen("/bin/echo_from_stdin.sh","w");
fputs("message from parent\n",fp);
pclose(fp);
return 0;
}
message from parent
当用标准IO读写管道时,一定要注意 标准IO的缓冲机制. 缓冲很容易造成读写管道时的阻塞,甚至引起死锁.为此经常要用`setvbuf'函数更改缓冲类型.
当有多个进程同时对一个管道进行写操作时, 若一次写入的字节数大于PIPE_BUF,则可能会乱序写入.
FIFO又叫命名管道,它与pipe的不同在于:pipe只能由共同祖先进程创建,然后通过fork函数在多个子进程之间使用. 而通过FIFO,无共同祖先进程的进程也能交换数据.
FIFO也是一种文件类型,stat结构中的st_mode可以标明文件是否为FIFO类型. 可以用S_ISFIFO宏对此进行测试.
FIFO只能保证是半双工的.
创建FIFO的类似于创建文件.
#include <sys/stat.h>
/* mode参数说明与open函数相同 */
int mkfifo(const char* fifo_path,mode_t mode);
一旦用mkfifo创建了fifo,就可以使用open,close,read,write,unlink对其像处理文件一样使用FIFO.
但使用open打开FIFO时,非阻塞标志(O_NONBLOCK)的意义有点不同:
XSI IPC包括三种IPC:信息队列,信号量,共享存储器. 它们之间有许多相似之处.
每种XSI IPC结构中都有一个非负整数作为标识符. 但该标识符仅为IPC对象内部使用,其对外与一个键(key)相连.
创建IPC结构时都需要指定一个键(key_t),该键由内核变换为IPC结构的标识符.
多个进程共享同一个IPC结构一般有如下几种方法:
#include <sys/ipc.h>
key_t fork(const char* path,int id);
XSI IPC为每个IPC结构都设置了一个ipc_perm结构, 该结构规定了权限和所有者. 它至少包括下列成员:
#include <sys/ipc.h>
struct ipc_perm{
uid_t uid; /* 所有者的有效用户id */
gid_t gid; /* 所有者的有效组id */
uid_t cuid; /* 创建者的有效用户id */
git_t cgid; /* 创建者的有效组id */
mode_t mode; /* access mode */
/* 其他成员 */
};
IPC的缺点有:
IPC的优点有:
消息队列是消息的链接表,该链接表存放在内核中并由消息队列标识符标识.
msgget用于创建一个新队列或打开一个现存的队列.
#include <sys/msg.h>
/* 成功则返回消息队列的ID,出错则返回-1 */
int msgget(key_t key,int flag);
每个消息队列都有一个msgid_ds结构体与之关联,用于说明队列的当前状态
struct msqid_ds{
struct ipc_perm msg_perm; /* ipc的权限说明 */
msgqnum_t msg_qnum; /* 队列中的消息个数 */
msglen_t msg_qbytes; /* 队列中能存储的消息最大容量 */
pid_t msg_lspid; /* 最后一次在该队列上调用msgsnd()的进程id */
pid_t msg_lrpid; /* 最后一次在该队列上调用msgrcv()的进程id */
time_t msg_stime; /* 最后一次在该队列上调用msgsnd()的时间 */
time_t msg_rtime; /* 最后一次在该队列上调用msgrcv()的时间 */
time_t msg_ctime; /* 最后一次在该队列发生变化的事件 */
/* 其他结构成员 */
}
msgctl函数对消息队列执行队中操作
#include <sys/msg.h>
int msgctl(int queueid,int cmd,struct msgid_ds* buf);
其中cmd参数说明了对queueid指定的队列要执行的命令:
#include <sys/msg.h>
#include <sys/ipc.h>
void show_queue_ds(const struct msqid_ds* queue_ds)
{
printf("msg_qnum=%d\n",queue_ds->msg_qnum);
printf("msg_qbytes=%d\n",queue_ds->msg_qbytes);
printf("msg_lspid=%d\n",queue_ds->msg_lspid);
printf("msg_lrpid=%d\n",queue_ds->msg_lrpid);
}
int main(int argc, char *argv[])
{
int queueid = msgget(IPC_PRIVATE,IPC_CREAT);
struct msqid_ds queue_ds;
msgctl(queueid,IPC_STAT,&queue_ds);
show_queue_ds(&queue_ds);
return 0;
}
msg_qnum=1630404681 msg_qbytes=1627419888 msg_lspid=1628099819 msg_lrpid=192msgsnd将新消息添加到队列尾端.
#include <sys/msg.h>
/* 参数flag可以为0或IPC_NOWAIT. */
int msgsnd(int msqid,const msgbuf* ptr,size_t nbytes,int flag);
struct msgbuf
{
long mtype; /* 消息类型 */
char mtext[TEXTSIZE]; /* 信息数据,TEXTSIZE需要比msgsnd中的参数nbytes大 */
}
默认情况下,若消息队列已满,msgsnd函数会阻塞直到有空间要发送,或该队列被删除(返回EIDRM),或捕捉到一个信号,从信号捕捉函数返回(返回EINTR) 但若参数flag设置为IPC_NOWAIT,则msgsnd会立即出错返回EAGAIN.
msgrcv用于从队列中取消息.
#include <sys/msg.h>
/* 成功返回消息的数据部分的长度,出错返回-1 */
ssize_t msgrcv(int msqid,msgbuf* ptr,size_t nbytes,log type,int flag);
参数type指明了我们想获得哪种类型的消息
| type == 0 | 返回队列中的第一个消息 |
| type > 0 | 返回队列中消息类型为type的第一个消息 |
| type < 0 | 返回队列中消息类型<=type绝对值的消息,类型值最小的消息优先(可作为优先级来用) |
参数flag控制了msgrcv的行为
| flag的值 | 说明 |
|---|---|
| MSG_NOERROR | 若消息大于nbytes,则消息被截断,否则会出错返回E2BIG(消息仍留在队列中) |
| IPC_NOWAIT | 操作不阻塞,若找不到指定类型的消息,则返回-1,errno设置为ENOMSG |
信号量是一个计数器,用于标明多进程间共享资源的剩余数量.
每个信号量集合由一个无名结构体表示
struct {
unsigned short semval; /* 信号量的值 */
pid_t sempid; /* 上次操作该信号量的进程id */
unsigned short semncnt; /* 等待semval>curval的进程数 */
unsigned short semzcnt; /* 等待semval == 0的进程数 */
/* 可能还有其他成员 */
};
#include <sys/sem.h>
int semget(key_t key,int sem_num,int flag);
#include <sys/sem.h>
/* 返回值的意义根据cmd的不同而不同 */
int semctl(int semid,int semnum,int cmd);
int semctl(int semid,int semnum,int cmd,union semun arg);
union semnum{
int val; /* cmd为SETVAL时 */
struct semid_ds* buf; /* cmd为IPC_STAT或IPC_SET时 */
unsigned short* array; /* cmd为GETALL或SETALL时 */
};
struct semid_ds
{
struct ipc_perm sem_perm; /* ipc权限 */
unsigned short sem_nsems; /* 信号量集合中信号量的个数 */
time_t sem_otime; /* 最后执行semop()的时间 */
time_t sem_ctime; /* 最后改变semid_ds结构体的时间 */
/* 其他成员 */
};
cmd参数说明
| IPC_STAT | 取当前信号量集合的semid_ds结构,存放在arg.buf中 |
| IPC_SET | 根据arg.buf中的值,更新该信号量集合的sem_perm.uid,sem_perm.gid或sem_perm.mode. 执行该函数的进程其有效用户ID必须为sem_perm.cuid/sem_perm.uid或超级用户权限 |
| IPC_RMID | 立即删除该信号量集合.执行该函数的进程其有效用户ID必须为sem_perm.cuid/sem_perm.uid或超级用户权限 |
| GETVAL | 返回信号量集合中第semnum个信号量的值 |
| SETVAL | 设置信号集合中第semnum个信号量的值,该值由arg.val指定 |
| GETPID | 返回上次操作信号量的进程PID |
| GETNCNT | 返回信号量集合中第semnum个信号量的semncnt的值 |
| GETZCNT | 返回信号量集合中第semnum个信号量的semzcnt的值 |
| GETALL | 获取信号量集合中所有信号量的值,并将它们存放在arg.array中 |
| SETALL | 根据arg.array中的值设置信号量集合中所有信号量的值 |
#include <sys/sem.h>
/* 成功返回0,失败返回-1 */
int semop(int semid,struct sembuf semop_array[],size_t npos);
struct sembuf
{
unsigned short sem_num; /* 指明操作的是信号量集合中的第几个信号量 */
short sem_op; /* 是何种操作,可以为负数,0或正数 */
short sem_flg; /* IPC_NOWAIT,SEM_UNDO */
};
| sem_op的值 | 说明 |
|---|---|
| >0 | 表示资源要释放资源. 增加信号量sem_op的值 |
| <0 | 表示进程要占用资源. 减少信号量值, 若信号量不够减,则操作被阻塞 |
| 0 | 表示进程等待信号量变为0. 不为0则该操作被阻塞 |
| sem_flg的值 | 说明 |
|---|---|
| IPC_NOWAIT | 不阻塞,若操作无法完成,则直接返回EAGAIN |
| SEM_UNDO | 修改进程的 信号量调整值. 进程在退出时,内核会根据该调整值释放资源. 推荐使用 |
共享存储器运行多个进程共享同一块存储区,由于数据不需要在进程间复制,所以这是最快的一种IPC.
由于不同进程要对同一块存储区操作,因此通常会使用记录锁或信号量来实现对共享存储区访问的同步.
内核为每个共享存储段设置了一个shmid_ds结构
struct shmid_ds{
struct ipc_perm shm_perm; /* IPC权限说明 */
size_t shm_segsz; /* 共享存储的大小 */
pid_t shm_lpid; /* 最后执行shmop()的进程ID */
pid_t shm_cpid; /* 创建该共享存储的进程ID */
shmatt_t shm_nattch; /* 当前attach到该共享内存的数量 */
time_t shm_atime; /* 最后一次attach的时间 */
time_t shm_dtime; /* 最后一次detach的时间 */
time_t shm_ctime; /* 最后一次更改shmid_ds结构的时间 */
/* 其他成员 */
}
#include <sys/shm.h>
/* 成功返回共享存储ID,出错返回-1 */
int shmget(key_t key,size_t size,int flag);
#include <sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds* buf);
cmd参数说明:
| cmd参数 | 说明 |
|---|---|
| IPC_STAT | 去该段的shmid_ds结构,存放在buf所指的结构中 |
| IPC_SET | 根据buf中的值设置该共享存储的shm_perm.uid,shm_perm.gid和shm_perm.mode |
| IPC_RMID | 从系统中删除该存储段. 共享存储有一个计数器,只有当最后一个进程删除该共享存储后才回实际删除 |
| SHM_LOCK | 不让该共享存储交换到swap中 |
| SHM_UNLOCK | 允许共享存储交换到swap中 |
#include <sys/shm.h>
/* 成功则返回指向共享存储的指针,出错返回-1 */
void* shmat(int shmid,const void* addr,int flag);
#include <sys/shm.h>
int shmdt(void* addr);
