进程之间具有独立性,如果需要进行通信,就必须打破进程间的独立性。进程通信需要提供一块公共的能够进行信息存储和取出的空间。文件系统提供的我们称为管道,操作系统提供的System V。
进程间通信的本质就是让不同的进程看到同一份资源。
实际上就是构建一个公共区域,供不同进程进行写入或读取数据。
实际情况中,有时候我们需要多进程协作完成某种业务。
其实我们早在之前命令中的学习就已经见过了管道|
,我们也经常使用管道命令。
例如,查看服务器连接人数:
[---@VM-8-4-centos day04]$ who | wc -l
匿名管道主要用于父子间的通信。它本质上就是让父子进程看到同一个被打开的文件,然后让父子进程进行写入或读取数据,从而实现父子间的通信。
这里的文件是由操作系统提供的,所以在父进程或子进程写入数据时,并不会发生写时拷贝。
int pipe(int pipefd[2]);
#include
实际上,我们需要对一对父子进程关闭相反的两个端口来使用匿名管道进行通信。
例如,父进程关闭写端,子进程关闭读端。
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int fd[2] = {0};
int n = pipe(fd);
if (n != 0)
perror("pipe fail");
pid_t id = fork();
int cnt = 0;
const char *str = "I am a child process. MYPID->";
if (id > 0)
{
close(fd[1]); // 父进程关闭写端
while (1)
{
char buffer[1024];
ssize_t r = read(fd[0], buffer, sizeof(buffer) - 1);
if (r > 0)
{
buffer[r] = '\0';
cout << "parent process get message-> " << buffer << endl;
}
else if (r == 0)
{
// 读完了
cout << endl;
cout << "数据读取完毕!!!" << endl;
break;
}
else
{
perror("read fail");
return -1;
}
}
int wp = waitpid(-1, nullptr, 0);
close(fd[0]);
return 0;
}
else if (id == 0)
{
close(fd[0]); // 子进程关闭读端
while (1)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer), "Message: %s%d, count: %d", str, getpid(), cnt++);
write(fd[1], buffer, strlen(buffer));
sleep(1); // 每隔1秒写入一次
if (cnt == 6)
break;
}
close(fd[1]);
exit(0);
}
else
{
perror("fork fail");
return -1;
}
return 0;
}
SIGPIPE
,从而让写端关闭对于管道来说,同步就是指这两个进程不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作;互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作。
ps:管道的最大容量一般是65536字。
我们也可以通过以下命令查看:
[---@VM-8-4-centos day04]$ ulimit -a
匿名管道通常只能用于父子进程间的通信,为了让不具有亲缘关系的进程相互通信,由此有了命名管道。
通过创建一个特殊的文件,让两个进程看到同一份资源,从而实现通信。
匿名管道和命名管道都是内存文件,但是命名管道在磁盘上有一个特殊的映像。(大小为0,因为命名管道和匿名管道都不会刷新到磁盘上)
[---@VM-8-4-centos day04]$ mkfifo named_pipe
我们可以从第一个p看到出,这个文件类型是管道文件,管道文件大小默认是0。
此时我们已经可以进行通信了,我们使用两个不同的命令行进行通信测试:
如果我们不进行读取,写端就会阻塞等待读端读取。
int mkfifo(const char *pathname, mode_t mode);
pathname:命名管道创建路径,若给出文件名,则创建在当前路径下;若给出路径,按路径创建
mode:管道文件默认权限
返回值:创建成功,返回0;创建失败,返回-1。
int main()
{
umask(0);
int n = mkfifo("named_pipe", 0666);
if(n < 0) perror("mkfifo fail");
//创建成功
cout << "mkfifo success..." << endl;
return 0;
}
int unlink(const char *path)
int main()
{
int n = unlink("./named_pipe");
if(n == -1)
{
perror("unlink fail");
return -1;
}
cout << "unlink success ......." << endl;
return 0;
}
com.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
using namespace std;
bool create_named_pipe()
{
umask(0);
int n = mkfifo("./named_pipe", 0600); // 创建管道文件
if (n != 0)
{
perror("mkfifo fail");
return false;
}
cout << "mkfifo success ......" << endl;
return true;
}
void unlink_named_pipe()
{
int n = unlink("./named_pipe");
if (n != 0)
perror("unlink fail");
cout << "unlink success ......" << endl;
}
serve.cc
#include "com.hpp"
int main()
{
bool flag = create_named_pipe();
if (flag == false)
{
perror("create_named_pipe fail");
exit(-1);
}
int fd = open("./named_pipe", O_RDONLY);
if (fd < 0)
perror("open fail");
char buffer[1024];
while (1)
{
ssize_t r = read(fd, buffer, sizeof(buffer) - 1);
if (r > 0)
{
buffer[r] = '\0';
cout << "serve get message -> " << buffer << endl;
}
else if (r == 0)
{
cout << "read end ......" << endl;
break;
}
else
{
perror("read fail");
return -1;
}
}
close(fd);
unlink_named_pipe();
return 0;
}
client.cc
#include "com.hpp"
int main()
{
int fd = open("./named_pipe", O_WRONLY);
if (fd < 0)
perror("open fail");
char buffer[1024];
while(1)
{
cout << "client Enter # ";
fgets(buffer, sizeof(buffer), stdin);//fgets剩一个空间会被系统填充'\0',不用-1
ssize_t w = write(fd, buffer, strlen(buffer));
if(w != strlen(buffer))
{
perror("write fail");
exit(-1);
}
}
close(fd);
return 0;
}
另外一提,命令行中的管道|
是匿名管道。
之前我们提到过System V通信方式有:System V共享内存、System V消息队列、System V信号量,下面我们就着重说说System V共享内存。
用户使用操作系统提供的接口在物理内存中申请一块资源,通过页表将这段物理空间映射至进程地址空间,进程将这段虚拟地址的起始地址返回给用户。
操作系统中的进程都可以通过共享内存进行通信,一个操作系统可以有多个共享内存。
共享内存不止一个,操作系统必然对这些共享内存也要进行管理,系统也为他维护了一个数据结构:
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 by DIPC */
void *shm_unused3; /* unused */
};
第一个成员shm_perm
的类型ipc_perm结构是这样的。(key也存在shm_perm中)
struct ipc_perm{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
我们使用以下命名,可以查看系统中的IPC资源:
[---@VM-8-4-centos day05]$ ipcs
ipcs
这个命令还有3个选项,分别来查看共享内存、消息队列和信号量。
-q
:仅显示消息队列的信息-m
:仅显示共享内存的信息-a
:仅显示信号量的信息key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, size_t size, int shmflg);
ftok
获取)IPC_CREAT | IPC_EXCL | 0666
)#include
#include
#include
#include
#include
using namespace std;
int main()
{
key_t key = ftok("./makefile", 0x6666);
if(key < 0)
{
perror("ftok fail");
return -1;
}
int shm = shmget(key, 4096, IPC_CREAT | IPC_EXCL);
if(shm < 0)
{
perror("shmget fail");
return -2;
}
cout << key << endl << shm << endl;
return 0;
}
我们使用ipcs
命令来看一下,我们是否创建成功:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
int main()
{
key_t key = ftok("./makefile", 0x6666);
if (key < 0)
{
perror("ftok fail");
return -1;
}
int shm = shmget(key, 4096, IPC_CREAT | IPC_EXCL);
if (shm < 0)
{
perror("shmget fail");
return -2;
}
cout << key << endl << shm << endl;
cout << "create success..." << endl;
sleep(3);
int ctl = shmctl(shm, IPC_RMID, nullptr);
if(ctl < 0)
{
perror("shmctl fail");
return -3;
}
cout << "delete success..." <<endl;
return 0;
}
我们创建一块共享内存,让程序休眠3秒。休眠后,删除这块共享内存。(我们可以看见其中有3行打印了这块共享内存,第4行就没有了,这也证明了我们代码的逻辑是正确的)
右边命令行使用以下的监控脚本:
[wsj@VM-8-4-centos day05]$ while :; do ipcs -m;echo "###################################";sleep 1;done
我们也可以使用命令行来删除共享内存:
[wsj@VM-8-4-centos day05]$ ipcrm -m 6(数字代表自己共享内存的shm)
void *shmat(int shmid, const void *shmaddr, int shmflg);
nullptr
让内核自己选择。int shmdt(const void *shmaddr);
shmaddr:待去关联的共享内存,使用shmat得到的地址。
返回值,调用成功,返回0;调用失败,返回-1。
接下来,我们使用一段代码加深一下对这些接口调用的理解:
int main()
{
key_t key = ftok("./makefile", 0x6666);
if (key < 0)
{
perror("ftok fail");
return -1;
}
int shm = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
if (shm < 0)
{
perror("shmget fail");
return -2;
}
cout << "create success..." << endl;
cout << "-----------------------" << endl;
void* mem = shmat(shm, nullptr, 0);
if(mem == (void*)-1)
{
perror("shmat fail");
return -3;
}
cout << "attach success..." << endl;
cout << "-----------------------" << endl;
int dt = shmdt(mem);
if(dt < 0)
{
perror("shmdt fail");
return -4;
}
cout << "detach success ..." << endl;
cout << "-----------------------" << endl;
int ctl = shmctl(shm, IPC_RMID, nullptr);
if(ctl < 0)
{
perror("shmctl fail");
return -5;
}
cout << "delete success..." << endl;
return 0;
}
右边的命令行,任然使用上面的监控脚本。
我们从共享内存,从无到有;从关联数,从0到1,再到0。证明我们代码的逻辑是正确的。(创建共享内存->关联该共享内存->去关联该共享内存->删除共享内存)
ps:创建共享内存时需要设置权限,不然就无法正常关联。
com.hpp
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define PATH "./makefile"
#define PROJ_ID 0X888
#define MAX_SIZE 4096
key_t getKey() // 获取key值
{
key_t key = ftok(PATH, PROJ_ID);
if (key == -1)
{
perror("ftok fail");
exit(-1);
}
return key;
}
int getShm(key_t key, int flag) // 创建共享内存,为下面两个函数服务
{
int shm = shmget(key, MAX_SIZE, flag);
if (shm < 0)
{
perror("shmget fail");
exit(-2);
}
return shm;
}
int shmHelper(key_t key) // 获取共享内存(已创建的前提)
{
return getShm(key, IPC_CREAT);
}
int createShm(key_t key) // 创建共享内存
{
return getShm(key, IPC_CREAT | IPC_EXCL | 0666);
}
void *attachShm(int shm) // 关联
{
void *mem = shmat(shm, nullptr, 0);
if (mem == (void *)-1)
{
perror("shmat fail");
exit(-3);
}
return mem;
}
void detachShm(void *mem) // 去关联
{
if (shmdt(mem) < 0)
{
perror("shmdt fail");
exit(-4);
}
}
int deleteShm(int shm) // 删除共享内存
{
if (shmctl(shm, IPC_RMID, nullptr) < 0)
{
perror("shmctl fail");
exit(-5);
}
}
serve.cc
#include "com.hpp"
int main()
{
int key = getKey();
int shm = createShm(key);
cout << shm << endl;
void *mem = attachShm(shm);
cout << mem << endl;
int cnt = 0;
while (cnt++ < 10)
{
printf("client # %s\n", mem);
struct shmid_ds ds;
shmctl(shm,IPC_STAT,&ds);
cout << "PID->" << getpid() << ", creator->" << ds.shm_cpid << ", key->" << ds.shm_perm.__key << endl;
sleep(1);
}
detachShm(mem);
deleteShm(shm);
return 0;
}
client.cc
#include "com.hpp"
int main()
{
int key = getKey();
int shm = shmHelper(key);
cout << shm << endl;
void *mem = attachShm(shm);
cout << mem << endl;
int cnt = 0;
char *message = "Hello, I`m client";
while (1)
{
snprintf((char *)mem, MAX_SIZE, "PID->%d : %s, count : %d\n", getpid(), message, ++cnt);
sleep(1);
}
detachShm(mem);
return 0;
}
共享内存是所有通信中最快的通信方式,因为它没有缓冲区,能大大减少通信数据的拷贝次数。