- 管道
- System V进程间通信
- POSIX进程间通信
- 匿名管道pipe
- 命名管道
- System V 消息队列
- System V 共享内存
- System V 信号量
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
- 数据传输
- 资源共享
- 通知事件
- 进程控制
2.2 匿名管道 2.2.1 pipe接口从一个进程连接到另一个进程的一个数据流称为一个“管道”。
int pipe(int pipefd[2]); // 作用:创建一个匿名管道(以读写方式打开同一个文件) // pipefd[0] --> 以读方式打开 pipefd[1] --> 以写方式打开 // 如果成功就返回0,错误就返回-1或者错误码2.2.2 画图演示 2.2.2 代码演示
#include2.2.3 关闭写端#include #include #include #include // for pid_t #include int main() { int pipefd[2] = {0}; int ret_pipe = pipe(pipefd); if (ret_pipe != 0) { perror("pipe"); return -1; } // 我们让子进程写,父进程读 pid_t pid = fork(); // 创建子进程 if (pid < 0) { perror("fork"); return -1; } if (pid == 0) { // child close(pipefd[0]); // 子进程关闭读端,留下写端 while (1) { const char* msg = "hello world"; write(pipefd[1], msg, strlen(msg)); sleep(1); } } // parent close(pipefd[1]); // 父进程进程关闭写端,留下读端 char msg[64] = ""; while (1) { int n = read(pipefd[0], msg, sizeof(msg) - 1); msg[n] = ' '; printf("child say: %sn", msg); } return 0; }
// 只关闭写端
int main()
{
int pipefd[2] = {0};
int ret_pipe = pipe(pipefd);
// 让子进程写,父进程读
pid_t pid = fork();
if (pid == 0)
{
// child
close(pipefd[0]);
while (1)
{
char ch = 'a';
write(pipefd[1], &ch, 1);
close(pipefd[1]); // 将写端关闭
break;
}
}
// parent
close(pipefd[1]);
char msg[1024*4 + 1] = "";
while (1)
{
sleep(1);
int n = read(pipefd[0], msg, sizeof(msg) - 1);
if (n < 0)
{
break;
}
if (n == 0) // n为0,说明读端已经关闭了
{
printf("file is empty!n");
break;
}
msg[n] = ' ';
printf("child say: %cn", msg[0]);
}
return 0;
}
2.2.4 关闭读端
// 关闭读端
int main()
{
int pipefd[2] = {0};
int ret_pipe = pipe(pipefd);
// 让子进程写,父进程读
pid_t pid = fork();
if (pid == 0)
{
// child
close(pipefd[0]);
while (1)
{
char ch = 'a';
write(pipefd[1], &ch, 1);
}
}
// parent
close(pipefd[1]);
char msg[1024*4 + 1] = "";
while (1)
{
sleep(1);
int n = read(pipefd[0], msg, sizeof(msg) - 1);
close(pipefd[0]); // 关闭读端
printf("child say: %cn", msg[0]);
break;
}
int status = 0;
wait(&status);
printf("exit_code: %d signal: %dn", (status >> 8) & 0xff, status & 0x7f); // 打印signal为13
return 0;
}
2.2.5 五个特点
- 管道是一个只能单向通信的通信信道
- 管道是面向字节流的
- 仅限于具有血缘关系的进程进行通信
- 管道自带同步机制,原子性写入
- 管道的生命周期是随进程的
2.3 命名管道 2.3.1 概念没有数据读时
写端不写或者写得慢,读端要等写端
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满时
读端不读或者读得慢,写端要等读端
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
写端对应的文件描述符被关闭时
读端read读完pipe的数据后会读到0,可以根据检测到0判断写端已经关闭了
读端对应的文件描述符被关闭时
写端收到SIGPIPE信号直接终止程序
原子性
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
2.3.2 创建命令和接口一个有名字的管道文件,可以像正常文件操作那样被打开,所以它可以用于不相关进程之间的通信。
# 1. 命令行上创建 mkfifo filename
// 2. C程序里面创建 // 成功返回0,失败返回-1 int mkfifo(const char* filename, mode_t mode);2.3.3 server和client案例代码
// server #include#include #include #include #include // for open #include // for open int main() { umask(0); int ret = mkfifo(FILE_NAME, 0666); int fd = open("fifo", O_RDONLY); while (1) { char buf[64] = {0}; ssize_t n = read(fd, buf, sizeof(buf)); printf("n -- %dn", n); buf[n-1] = ' '; if (strcmp(buf, "ls") == 0) { if (fork() == 0) { execl("/bin/ls", "ls", "-al", NULL); } } else if (strcmp(buf, "information") == 0) { if (fork() == 0) { execl("/usr/bin/information", "information", NULL); } } else if (strcmp(buf, "clear") == 0) { if (fork() == 0) { execl("/usr/bin/clear", "clear", NULL); } } else if (strcmp(buf, "close") == 0) { printf("OK, close the file!!!n"); break; } else { printf("client say: %sn", buf); } } close(fd); return 0; }
// client #include2.3.4 读写规则#include #include #include #include // for open #include // for open int main() { int fd = open("fifo", O_WRONLY); if (fd < 0) { perror("open"); } printf("successful open, fd is %dn", fd); while (1) { printf("请输入:"); fflush(stdout); char buf[64] = {0}; ssize_t n = read(0, buf, sizeof(buf)); buf[n] = ' '; n = write(fd, buf, n); if (strcmp(buf, "closen") == 0) { break; } } close(fd); return 0; }
2.4 匿名管道和命名管道的区别打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
三. System V共享内存
- 匿名管道由pipe函数创建并且打开
- 命名管道由mkfifo命令或者函数创建,程序里面打开用open
- 匿名管道只能在有血缘关系的进程之间进行通信,而命名管道可以在不相关的进程之间通信
3.1 简述3.2 函数接口 3.2.1 ftok如下图,在物理内存上申请一块空间,然后让不同进程的虚拟内存通过页表映射挂接到这个这块空间,这块空间就能实现被不同进程共享了。共享内存在建立连接以后,在通信的时候不需要调用系统接口了,没有IO的处理,速度上是比管道快很多的。
// 需要包含的头文件 #include3.2.2 shmget// for key_t #include // 函数声明 key_t ftok(const char* pathname, int proj_id); // 一. 功能:一般是给像shmget这种接口生成一个key // 二. pathname:自定义路径的名字 // 四. proj_id:自定义项目id // 五. 返回值:成功就返回一个对应key,失败就返回-1
// 需要包含的头文件 #include3.2.3 shmctl#include // 函数声明 int shmget(key_t key, size_t size, int shmflg); // 一. 功能: 向物理内存申请共享内存 // 二. key:也就是ftok函数接口生成的 // 三. size:是共享内存的大小,建议写4KB(4096Byte)的整数倍,因为内存分配操作是以页为单位的,一页的大小就是4KB // 四. shmflag:是一个位图映射的数据变量,主要的标志位有IPC_CREAT和IPC_EXCL // IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作 // IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误(单独使用没有意义) // 五. 返回值(成功返回共享内存的标识码,失败返回-1) // 1. 单独使用IPC_CREAT,如果key对应的共享内存不存在,就返回新申请共享内存的标识码,存在就返回返回其标识码 // 2. IPC_CREAT和IPC_EXCL一起使用,如果key对应的共享内存不存在,就返回新申请共享内存的标识码,存在就返回出错(意义:调用成功,保证返回的标识码的共享内存是新建的,而不是已经存在的)
// 需要包含的头文件 #include3.2.4 shmat#include // 函数声明 int shmctl(int shmid, int cmd, struct shmid_ds* buf); // 一. 功能: 控制共享内存 // 二. shmid:需要控制的共享内存的标识码 // 三. cmd:将要采取的动作(IPC_STAT, IPC_SET, IPC_RMID) // IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值 // IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值 // IPC_RMID:删除共享内存段(常用!!!) // 四. buf:指向一个保存着共享内存的模式状态和访问权限的数据结构的指针(删除共享内存时可以传NULL) // 五. 返回值:成功返回0,失败返回-1
// 需要包含的头文件 #include3.2.5 shmdt#include // 函数声明 void* shmat(int shmid, const void* shmaddr, int shmflg); // 一. 功能: 将进程的虚拟地址通过页表映射挂接到共享内存 // 二. shmid:需要控制的共享内存的标识码 // 三. shmaddr:指定虚拟地址,一般传NULL让系统自动找一块地址 // 四. shmflg:SHM_RDONLY,传这个过去为只读模式,否则为其他模式 // 五. 返回值:成功返回挂接上的虚拟地址,失败返回(void*)-1
// 需要包含的头文件 #include3.2.6 案例#include // 函数声明 int shmdt(const void* shmaddr) // 一. 功能: 解除虚拟内存与共享内存的页面映射关系 // 三. shmaddr:需要去关联的虚拟地址 // 四. 返回值:成功返回0,失败返回-1
// common.h文件 #include#include // for key_t #include #include // for shmget #include #include #include // for O_CREAT #define PATH_NAME "./" #define PROJ_ID 0x6666 #define SIZE 4096
// server.c文件
#include "common.h"
int main()
{
key_t key = ftok(PATH_NAME, PROJ_ID);
printf("key = %dn", key);
// 申请共享内存
int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
perror("shmget");
exit(1);
}
printf("client success! shmid is %dn", shmid);
// 让虚拟内存和共享内存关联起来
char* ptr = (char*)shmat(shmid, NULL, 0);
//sleep(10);
// 通信
char ch = 'A';
while(ch != 'Z')
{
*ptr = ch;
ptr++;
*ptr= ' ';
ch++;
sleep(1);
}
sleep(5);
// 去关联
shmdt(ptr);
// 销毁共享内存
int ret = shmctl(shmid, IPC_RMID, NULL);
if (ret == -1)
{
perror("shmctl");
}
printf("Destruction of successful!n");
return 0;
}
// client.c文件
#include "common.h"
int main()
{
key_t key = ftok(PATH_NAME, PROJ_ID);
// 获取共享内存的标识码
int shmid = shmget(key, SIZE, IPC_CREAT);
if (shmid == -1)
{
perror("shmget");
exit(1);
}
// 建立链接关系
char* ptr = (char*)shmat(shmid, NULL, 0);
// 通信
int times = 26;
while (times--)
{
sleep(1);
printf("%sn", ptr);
}
// 解除链接关系
shmdt(ptr);
return 0;
}
3.2.7 相关命令
# 1. 显示当前所有shmipc的信息 ipcs -m # 2. 删除shmipc资源 ipcrm -m shmid # shmid为共享内存的标识符



