目录
1. 命名管道
管道应⽤的⼀个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使⽤FIFO⽂件来做这项⼯作,它经常被称为命名管道。
命名管道是⼀种特殊类型的⽂件
创建⼀个命名管道
命名管道可以从命令⾏上创建,命令⾏⽅法是使⽤下⾯这个命令:
$ mkfifo filename
命名管道也可以从程序⾥创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);
创建命名管道:
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
匿名管道与命名管道的区别
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,打开⽤open
FIFO(命名管道)与pipe(匿名管道)之间唯⼀的区别在它们创建与打开的⽅式不同,⼀但这些⼯作完成之后,它们具有相同的语义。
命名管道的打开规则
如果当前打开操作是为读⽽打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写⽽打开该FIFO
O_NONBLOCK enable:⽴刻返回成功
如果当前打开操作是为写⽽打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读⽽打开该FIFO
O_NONBLOCK enable:⽴刻返回失败,错误码为ENXIO
实例1. ⽤命名管道实现⽂件拷⻉
读取⽂件,写⼊命名管道
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char* argv[])
{
mkfifo("tp", 0644);
int infd;
infd = open("abc", O_RDONLY);
if (infd == -1) ERR_EXIT("open");
int outfd;
outfd = open("tp", O_WRONLY);
if (outfd == -1) ERR_EXIT("open");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
return 0;
}
读取管道,写⼊⽬标⽂件:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char* argv[])
{
int outfd;
outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1) ERR_EXIT("open");
int infd;
infd = open("tp", O_RDONLY);
if (outfd == -1)
ERR_EXIT("open");
char buf[1024];
int n;
while ((n = read(infd, buf, 1024)) > 0)
{
write(outfd, buf, n);
}
close(infd);
close(outfd);
unlink("tp");
return 0;
}
实例2. ⽤命名管道实现server&client通信
# ll
total 12
-rw-r--r--. 1 root root 46 Sep 18 22:37 clientPipe.c
-rw-r--r--. 1 root root 164 Sep 18 22:37 Makefile
-rw-r--r--. 1 root root 46 Sep 18 22:38 serverPipe.c
# cat Makefile
.PHONY:all
all:clientPipe serverPipe
clientPipe:clientPipe.c
gcc -o $@ $^
serverPipe:serverPipe.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f clientPipe serverPipe
-- serverPipe.c --
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main()
{
umask(0);
if (mkfifo("mypipe", 0644) < 0) {
ERR_EXIT("mkfifo");
}
int rfd = open("mypipe", O_RDONLY);
if (rfd < 0) {
ERR_EXIT("open");
}
char buf[1024];
while (1) {
buf[0] = 0;
printf("Please wait...\n");
ssize_t s = read(rfd, buf, sizeof(buf) - 1);
if (s > 0) {
buf[s - 1] = 0;
printf("client say# %s\n", buf);
}
else if (s == 0) {
printf("client quit, exit now!\n");
exit(EXIT_SUCCESS);
}
else {
ERR_EXIT("read");
}
}
close(rfd);
return 0;
}
-- clientPipe.c --
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \
do{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main()
{
int wfd = open("mypipe", O_WRONLY);
if (wfd < 0) {
ERR_EXIT("open");
}
char buf[1024];
while (1) {
buf[0] = 0;
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf) - 1);
if (s > 0) {
buf[s] = 0;
write(wfd, buf, strlen(buf));
}
else if (s <= 0) {
ERR_EXIT("read");
}
}
close(wfd);
return 0;
}

2. system V共享内存
共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递彼此的数据
共享内存⽰意图

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment(bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void shm_unused2; /* ditto - used byDIPC */
void shm_unused3; /* unused*/
};
共享内存函数
shmget函数
功能:⽤来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存⼤⼩
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
说明:
shmaddr为NULL,核⼼⾃动选择⼀个地址
shmaddr 不为 NULL 且 shmflg ⽆ SHM_RND 标记,则以 shmaddr 为连接地址。shmaddr 不为 NULL 且 shmflg 设置了 SHM_RND 标记,则连接的地址会⾃动向下调整 SHMLBA 的整数倍。公式: shmaddr - (shmaddr % SHMLBA)shmflg=SHM_RDONLY ,表⽰连接操作⽤来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

实例1. 共享内存实现通信
测试代码结构
# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
-- comm.h --
#ifndef _COMM_H_
#define _COMM_H_
# include <stdio.h>
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# define PATHNAME "."
# define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
# endif
-- comm.c --
#include "comm.h"
static int commShm(int size, int flags)
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key < 0) {
perror("ftok");
return -1;
}
int shmid = 0;
if ((shmid = shmget(key, size, flags)) < 0) {
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if (shmctl(shmid, IPC_RMID, NULL) < 0) {
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
-- server.c --
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char* addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while (i++ < 26) {
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
-- client.c --
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char* addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while (i < 26) {
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}

ctrl+c终⽌进程,再次重启
# ./server
shmget: File exists
# ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66026a25 688145 root 666 4096 0
# ipcrm -m 688145 #删除shm ipc资源,注意,不是必须通过⼿动来删除,这⾥只为演⽰相关指令,删除IPC资源是进程该做的事情
bytes 和 nattch 有时间可以研究⼀下
注意:共享内存没有进⾏同步与互斥! 共享内存缺乏访问控制!会带来并发问题。
实例2. 借助管道实现访问控制版的共享内存
-- Comm.hpp --
#pragma once
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <cstdio>
#include <ctime>
#include <cstring>
#include <iostream>
using namespace std;
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string msg[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream& Log(std::string message, int level)
{
std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << "
| " << message;
return std::cout;
}
#define PATH_NAME "/home/hyb"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 // 共享内存的⼤⼩,最好是⻚(PAGE: 4096)的整数倍
#define FIFO_NAME "./fifo"
class Init {
public:
Init() {
umask(0);
int n = mkfifo(FIFO_NAME, 0666);
assert(n == 0);
(void)n;
Log("create fifo success", Notice) << "\n";
}
~Init() {
unlink(FIFO_NAME);
Log("remove fifo success", Notice) << "\n";
}
};
#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenFIFO(std::string pathname, int flags) {
int fd = open(pathname.c_str(), flags);
assert(fd >= 0);
return fd;
}
void CloseFifo(int fd) {
close(fd);
}
void Wait(int fd) {
Log("等待中....", Notice) << "\n";
uint32_t temp = 0;
ssize_t s = read(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
}
void Signal(int fd) {
uint32_t temp = 1;
ssize_t s = write(fd, &temp, sizeof(uint32_t));
assert(s == sizeof(uint32_t));
(void)s;
Log("唤醒中....", Notice) << "\n";
}
string TransToHex(key_t k) {
char buffer[32];
snprintf(buffer, sizeof buffer, "0x%x", k);
return buffer;
}
-- ShmServer.cc --
#include "Comm.hpp"
Init init;
int main() {
// 1. 创建公共的Key值
key_t k = ftok(PATH_NAME, PROJ_ID);
assert(k != -1);
Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;
// 2. 创建共享内存 -- 建议要创建⼀个全新的共享内存 -- 通信的发起者
int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
Log("create shm done", Debug) << " shmid : " << shmid << endl;
// 3. 将指定的共享内存,挂接到⾃⼰的地址空间
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
Log("attach shm done", Debug) << " shmid : " << shmid << endl;
// 4. 访问控制
int fd = OpenFIFO(FIFO_NAME, O_RDONLY);
while (true) {
// 阻塞
Wait(fd);
// 临界区
printf("%s\n", shmaddr);
if (strcmp(shmaddr, "quit") == 0)
break;
}
CloseFifo(fd);
// 5. 将指定的共享内存,从⾃⼰的地址空间中去关联
int n = shmdt(shmaddr);
assert(n != -1);
(void)n;
Log("detach shm done", Debug) << " shmid : " << shmid << endl;
// 6. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
n = shmctl(shmid, IPC_RMID, nullptr);
assert(n != -1);
(void)n;
Log("delete shm done", Debug) << " shmid : " << shmid << endl;
return 0;
}
-- ShmClient.cc --
#include "Comm.hpp"
int main() {
// 1. 创建公共的Key值
key_t k = ftok(PATH_NAME, PROJ_ID);
if (k < 0) {
Log("create key failed", Error) << " client key : " << TransToHex(k)<< endl;
exit(1);
}
Log("create key done", Debug) << " client key : " << TransToHex(k) << endl;
// 2. 获取共享内存
int shmid = shmget(k, SHM_SIZE, 0);
if (shmid < 0) {
Log("create shm failed", Error) << " client key : " << TransToHex(k)
<< endl;
exit(2);
}
Log("create shm success", Error) << " client key : " << TransToHex(k) <<endl;
// 3. 挂接共享内存
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
if (shmaddr == nullptr) {
Log("attach shm failed", Error) << " client key : " << TransToHex(k)<< endl;
exit(3);
}
Log("attach shm success", Error) << " client key : " << TransToHex(k) <<endl;
// 4. 写
int fd = OpenFIFO(FIFO_NAME, O_WRONLY);
while (true) {
ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
if (s > 0) {
shmaddr[s - 1] = 0;
Signal(fd);
if (strcmp(shmaddr, "quit") == 0)
break;
}
}
CloseFifo(fd);
// 5. 去关联
int n = shmdt(shmaddr);
assert(n != -1);
Log("detach shm success", Error) << " client key : " << TransToHex(k) <<endl;
return 0;
}
3. 内核是如何组织管理IPC资源的
这一看一些图







3857



被折叠的 条评论
为什么被折叠?



