今天,带来Linux下的进程间通信讲解。文中不足错漏之处望请斧正!
进程间通信,Inter-Process Communication(IPC):
进程间通过访问同一块内存空间来进行数据的交流。
为什么要有IPC,它的作用是什么,场景是什么?
让要通信的进程们拥有公共资源。
*谁实现IPC,公共资源都得由OS来提供。
为什么?
进程具有独立性(数据结构独立,代码和数据也独立),任何由进程提供的资源都只能被自己看见(进程地址空间)。所以进程们能看到的同一份公共资源,绝对只能由OS提供。
不同的公共资源来源,对应出不同的通信方式。
主要有三种方式:
内核的一块缓冲区,在内存中。
虽然形式上是文件,但匿名或命名管道文件只是一种标识符,使得进程能通过这个标识符访问内存中的同一块缓冲区。
管道也是用文件作进程公共资源的IPC方式。
分类:
没有也不需要名字的管道文件。
通过血缘关系,打开同一个管道文件。
创建子进程,子进程会继承父进程的文件描述符表。有了同样的表,就可指向同一被打开文件。
fd_array
)int pipe(int fildes[2]);
fds[2]
:是输出型参数,内含两个fd,分别是以读和写打开pipe文件的
fd[0]
:读(0像嘴巴,读)fd[1]
:写(1像钢笔,写)int main()
{
int fds[2];
int n = pipe(fds);
assert(n == 0);
pid_t id = fork();
assert(id >= 0);
if(id == 0)
{
close(fds[0]);
int cnt = 1;
//循环写,但父进程读端关闭,所以写端进程也被OS发信号终止(异常退出)
while(1)
{
cout << "count:" << cnt << endl;
char buf[1024];
snprintf(buf, sizeof buf, "|child(%d): %d|", getpid(), cnt++);
write(fds[1], buf, strlen(buf));
sleep(1);
}
close(fds[1]);
cout << "子进程写端关闭!" << endl;
exit(0);
}
//读3次后关闭读端
close(fds[1]);
for(int i = 0; i < 3; ++i)
{
char buf[1024];
ssize_t read_ret = read(fds[0], buf, sizeof(buf) - 1);
if(read_ret > 0)
buf[read_ret] = '\0';
printf("parent(%d) got msg -- %s\n", getpid(), buf);
}
close(fds[0]);
cout << "父进程读端关闭!" << endl;
//父进程关闭读端后,子进程写端直接被OS终止
int status = 0;
n = waitpid(id, &status, 0);
assert(n == id);
cout << "wait success!" << endl;
return 0;
}
[bacon@VM-12-5-centos mypipe]$ ./mypipe
count:1
parent(26629) got msg -- |child(26630): 1|
count:2
parent(26629) got msg -- |child(26630): 2|
count:3
parent(26629) got msg -- |child(26630): 3|
父进程读端关闭!
count:4
wait success!
设计思路:
父进程通过管道向子进程发送任务码,子进程获取并执行任务。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef void(*ptask)();
#define PROCESS_NUM 5
#define PLANT_SEED() srand((unsigned int)time(nullptr) ^ rand() ^ rand())
//进程池:
//准备子进程并让他们等待执行任务 ==> 发送任务码给子进程 ==> 子进程执行 ==> 获取子进程信息
class subEP
{
public:
subEP(pid_t subId, int writeFd)
:_subId(subId), _writeFd(writeFd)
{
char numBuf[512];
snprintf(numBuf, sizeof(numBuf), "[process%d]: pid=%d | fd=%d", _nameNum++, subId, _writeFd);
_name = numBuf;
}
public:
std::string _name;
pid_t _subId;
int _writeFd; //父进程眼中管道的写端fd
static int _nameNum;
};
int subEP::_nameNum = 0;
void task1() {std::cout << getpid() << " 完成任务1\n" << std::endl; sleep(1);}
void task2() {std::cout << getpid() << " 完成任务2\n" << std::endl; sleep(1);}
void task3() {std::cout << getpid() << " 完成任务3\n" << std::endl; sleep(1);}
void loadTask(std::vector<ptask>& taskV)
{
taskV.push_back(task1);
taskV.push_back(task2);
taskV.push_back(task3);
}
//向父进程眼中管道的写端写入任务码
void sendTask(const subEP& proc, int taskCode)
{
int write_ret = write(proc._writeFd, &taskCode, sizeof(taskCode));
assert(write_ret == sizeof(taskCode));
std::cout << "TaskCode " << taskCode << " sent to " << proc._name << std::endl;
}
//从子进程眼中管道的读端读出任务码
int reciveTask(int readFd)
{
int taskCode = 0;
int read_ret = read(readFd, &taskCode, sizeof(taskCode));
//正常读到任务码
if(read_ret == sizeof(taskCode)) return taskCode;
//读到0,说明 写端关闭 && 该读的读完了
else if(read_ret == 0) return -1;
else return 8848; //不可能的情况
}
std::vector<subEP>& getSubProcessWaittingTask(std::vector<subEP>& subEPs, const std::vector<ptask>& taskV)
{
//创建子进程
//bug?
std::vector<int> fdToDelete;
for(int i = 0; i < PROCESS_NUM; ++i)
{
int fds[2];
int pipe_ret = pipe(fds);
assert(pipe_ret == 0);
pid_t fork_ret = fork();
//子进程等待任务
if(fork_ret == 0)
{
//关闭之前子进程的写端,免得影响别人自己的终止
for(int i = 0; i < fdToDelete.size(); ++i) close(fdToDelete[i]);
close(fds[1]);
while(true)
{
int taskCode = reciveTask(fds[0]);
if(taskCode >= 0 && taskCode < taskV.size()) taskV[taskCode]();
else if(taskCode == -1) break;
}
exit(0);
}
//父进程保存当前子端点,让父进程后续能均衡发送任务码
close(fds[0]);
subEPs.push_back(subEP(fork_ret, fds[1]));
//保存要删除的fd(下一个子进程要关闭的写端)
fdToDelete.push_back(fds[1]);
}
}
void balancedTaskSending(std::vector<subEP> subEPs, const std::vector<ptask>& taskV, int taskCount)
{
bool infinite = taskCount == 0 ? true : false;
while(infinite || taskCount)
{
//1. 选择一个子进程
int procIndex = rand() % subEPs.size();
//2. 选择一个任务
int taskCode = rand() % taskV.size();
//3. 将任务对应的任务码发送给子进程
sendTask(subEPs[procIndex], taskCode);
sleep(1);
--taskCount;
}
}
//1. 绕过bug
// void waitSubProcess(std::vector subEPs)
// {
// for(int i = 0; i < subEPs.size(); ++i) close(subEPs[i]._writeFd);
// for(int i = 0; i < subEPs.size(); ++i)
// {
// waitpid(subEPs[i]._subId, nullptr, 0);
// std::cout << "wait sub process success: " << subEPs[i]._name << std::endl;
// }
// }
//2. 硬钢bug
void waitSubProcess(std::vector<subEP> subEPs)
{
for(int i = 0; i < subEPs.size(); ++i)
{
close(subEPs[i]._writeFd);
waitpid(subEPs[i]._subId, nullptr, 0);
std::cout << "wait sub process success: " << subEPs[i]._name << std::endl;
}
}
int main()
{
//0.准备任务,设置随机数
std::vector<ptask> taskV;
loadTask(taskV);
PLANT_SEED();
//1.获取正在等待执行任务的子进程
std::vector<subEP> subEPs; //等待执行任务的子进程
getSubProcessWaittingTask(subEPs, taskV);
//2.向subEPs发送任务
int taskCount = 5;
// int taskCount = 0; //无限个任务
balancedTaskSending(subEPs, taskV, taskCount);
//3.正在等待任务的子进程获取到任务并执行
//4.任务执行完毕,子进程退出
//5.获取子进程退出信息
waitSubProcess(subEPs);
return 0;
}
逻辑梳理:
原因:
关闭父进程中关于第一个的写端,我们希望OS检测到写端关闭从而终止第一个子进程。
但第一个子进程的写端被后续的进程也拿了一份,就导致关了一个写端还有其他写端,该关的时候反而关不掉。总结一句话,某个子进程的写端被别的子进程拿了,该退退不掉。
为什么刚刚的代码没问题?
最后一个子进程的写端没有被别人拿,所以关了写端就会终止读端进程。
顺序把全部子进程的写端关闭,实质上先终止的是最后一个子进程,倒数第二个的写端变为0个,才关闭;倒数第三个的写端变为0,才关闭……
解决:
//关闭之前子进程的写端,免得耦合
for(int i = 0; i < fdToDelete.size(); ++i) close(fdToDelete[i]);
fdToDelete.push_back(fds[1]);
是通过mkpipe
创建的有名管道文件。
不同文件通过路径打开同一个命名管道文件。
int mkfifo(const char *pathname, mode_t mode);
int unlink(const char *path);
comm.hpp
#define PIPE_PATH "/tmp/myPipe"
bool createFifo(const std::string& path)
{
umask(0);
int mkdfifoRet = mkfifo(path.c_str(), 0666);
if(mkdfifoRet == 0) return true;
else
{
std::cout << "err: " << strerror(errno) << std::endl;
exit(errno);
}
}
void removeFifo(const std::string& path)
{
int unlinkRet = unlink(path.c_str());
assert(unlinkRet == 0);
(void)unlinkRet; //象征性用一下,以防unused variable
}
server.cc
#include "comm.hpp"
int main()
{
createFifo(PIPE_PATH);
std::cout << "server will open soon" << std::endl;
int rfd = open(PIPE_PATH, O_RDONLY);
if(rfd < 0) exit(-1);
std::cout << "server open done" << std::endl;
//read
char buf[1024];
while(true)
{
int read_ret = read(rfd, buf, sizeof(buf));
if(read_ret > 0) std::cout << "server got msg: " << buf;
else if(read_ret == 0) //读到0说明写端不写了,就可以退出
{
std::cout << "communication complete!" << std::endl;
break;
}
else
{
std::cout << "err: " << strerror(errno) << std::endl;
}
}
close(rfd);
removeFifo(PIPE_PATH);
return 0;
}
client
#include "comm.hpp"
int main()
{
std::cout << "client will open soon" << std::endl;
int wfd = open(PIPE_PATH, O_WRONLY);
if(wfd < 0) exit(-1);
std::cout << "client open done" << std::endl;
//write
char buf[1024];
while(true)
{
std::cout << "please input msg:> ";
fgets(buf, sizeof(buf), stdin);
int write_ret = write(wfd, buf, strlen(buf));
assert(write_ret == strlen(buf));
}
close(wfd);
return 0;
}
IPC之管道:管道是一种利用文件进行通信的IPC方式,通常是父子进程使用这种方式,因为可以轻易看到同一份文件。
OS给我们提供的一种本地IPC方案。
*System V的方式用得并不多,原因是有二:
方式:
共享内存,shared memory,一块能同时被不同进程看到的内存。是System V IPC的一种。
不同进程关联同一块内存空间。
同样的,只要是IPC,免不了的前提:进程间能有一份公共资源。
不免有个问题:多条通信同时进行,每一组通信的进程都会有自己的共享内存,OS是如何标识不同内存,如何区分不同shm?
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
底层通过key_t key
来标识共享内存。
特点:生命周期随OS
优点:是所有IPC中最快的方式(一般没有不必要拷贝)
缺点:没有同步与互斥机制,对数据没有保护
int shmget(key_t key, size_t size, int shmflg);
0XXX
:对内存的读写权限shmid
,是某个内核数据结构(数组)的下标说得挺好,key是共享内存的唯一标识,但不同进程怎么用同一个key创建/获取一块共享内存?
看一个接口:
key_t ftok(const char *pathname, int proj_id);
怎么理解?key的值到底是多少根本不重要,只要不同进程能获取到同一个key就行——只需要传同一个pathname和proj_id就能得到同一个key。只要得到同一个key,也就能看到同一份内存。
key和shmid都是shm的“唯一”标识?
是的:
很像fd、inode和file的关系:
上层→下层:fd → inode → file
上层→下层:shmid → key → memBlock
为什么要搞俩,统一用一个不行吗?
分开:
工号改了,不影响我在学校由学号标识,在社会由身份证号标识。
不分开:
身份证号改了,影响我在学校由学号标识,在社会由身份证号标识。
分开 = 不互相影响,不分开 = 互相影响。前者其实就是不耦合,后者就是耦合。
如果不分开,唯一的key/shmid变了,其他地方全部受影响。
说简单点:shmid是用户管理共享内存用的,key是OS管理内存用的。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:要关联的shm
shmaddr:要关联到进程空间的哪个地址?(一般传nullptr就行)
If shmaddr is a null pointer, the segment is attached at the first available address as selected by the system.
shmflg:以什么方式(读/写)关联(0代表读写)
int shmdt(const void *shmaddr);
ipcs -m
:查看shm属性ipcrm -m
:将共享内存的链接数-1(为0时共享内存会被释放)comm.h
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#define PATH_NAME "."
#define PROJ_ID 0x8848
#define SHM_SIZE 4096
key_t getKey()
{
key_t key = ftok(PATH_NAME, PROJ_ID);
if(key == -1)
{
std::cerr << "err: " << strerror(errno) << std::endl;
exit(1);
}
return key;
}
int shmHelper(key_t key, int shmFlag)
{
//创建共享内存时传入的key,会被填入共享内存的一个字段key_t k
//k : shm = 1 : 1
int shmId = shmget(key, SHM_SIZE, shmFlag);
if(shmId < 0)
{
std::cerr << "err:" << strerror(errno) << std::endl;
exit(2);
}
return shmId;
}
int getShm(key_t key)
{
return shmHelper(key, IPC_CREAT);
}
int createShm(key_t key)
{
//创建shm并填入key
return shmHelper(key, IPC_CREAT | IPC_EXCL | 0666); //对shm读写执行的权限
}
void* attachShm(int shmId)
{
void* mem = shmat(shmId, nullptr, 0);
// if((int)mem == -1) //64位系统,指针64bits ==> 32bits == err
if((long long)mem == -1L)
{
std::cerr << "err: " << strerror(errno) << std::endl;
exit(3);
}
return mem;
}
void detachShm(void* start)
{
if(shmdt(start) == -1)
{
std::cerr << "err: " << strerror(errno) << std::endl;
exit(4);
}
}
void removeShm(int shmId)
{
if(-1 == shmctl(shmId, IPC_RMID, nullptr))
{
std::cerr << "err: " << strerror(errno) << std::endl;
exit(-1);
}
}
shm_server.cc
#include "comm.hpp"
int main()
{
printf("sercer pid = %d", getpid());
//获取内核级shm标识:key
key_t key = getKey();
printf("server key = 0x%x\n", key);
//创建shm
int shmId = createShm(key);
// printf("server shmId = 0x%x\n", shmId);
//关联shm和进程
char* start = (char*)attachShm(shmId);
printf("server attatch shm success, address start: %p\n", start);
//通信
while(true)
{
printf("got msg: %s\n", start);
struct shmid_ds ds;
shmctl(shmId, IPC_STAT, &ds);
printf("server stat: |memSegSz=%d| |creator=%d| |key=0x%x|\n", ds.shm_segsz, ds.shm_cpid, ds.shm_perm.__key);
sleep(1);
}
//去关联shm和进程
detachShm(start);
//删除shm
removeShm(shmId);
return 0;
}
#include "comm.hpp"
int main()
{
//获取内核级shm标识:key
key_t key = getKey();
// printf("client key = 0x%x\n", key);
int shmId = getShm(key);
// printf("client shmId = 0x%x\n", shmId);
//关联shm和进程
char* start = (char*)attachShm(shmId);
printf("server attatch shm success, address start: %p\n", start);
//通信
int cnt = 1;
while(true)
{
snprintf(start, SHM_SIZE, "hello server, I'm [%d] | cnt=%d", getpid(), cnt++);
sleep(1);
}
//去关联shm和进程
detachShm(start);
return 0;
}
逻辑梳理:
主要是公共资源用得不一样:
IPC之共享内存:不同进程通过关联同一块内存空间来看到公共资源,进而完成通信。
Linux的内核级队列。
消息队列中的结点主要的字段有type和buf,type是数据块的类型,buf是数据块内的数据。可以理解为:消息队列中存放类型为type的数据块buf。
进程可以根据type来区分数据块,来读写自己对应的数据块。
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
有了抽象,管理就是水到渠成的事了。
int msgget(key_t key, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:消息队列的id
msgp:要发送的消息(是一个结构体struct msgbuf)
struct msgbuf {
long mtype; /* message type, must be > 0 **/
char mtext[1]; /** message data */
};
msgsz:消息的大小
msgflg:给0就好
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msqid:消息队列的id
msgp:输出型参数,用来接收消息(是一个结构体struct msgbuf)
struct msgbuf {
long mtype; /* message type, must be > 0 **/
char mtext[1]; /** message data */
};
msgsz:消息的大小
msgflg:给0就好
具体的例子就不写了,了解个大概即可。
ipcs -q
IPC之消息队列:以系统维护的内核级队列——消息队列作为公共资源,不同通过系统调用读写消息队列来完成通信。
一个衡量共享资源使用情况的计数器,提供了一种“预定机制”。
伪代码:
信号量 sem = 20; //有20块共享资源
//进程1预定一块资源(申请信号量)
sem--; //衡量共享资源使用情况的计数器sem就需要对应地自减
//进程2预定一块资源(申请信号量)
sem--;
//访问...
//进程1用完了,回收资源
sem++; //衡量共享资源使用情况的计数器sem就需要对应地自增
//进程2用完了,回收资源
sem++;
其中,
sem--;
就称为 P操作sem++;
就称为 V操作怎么理解“预定机制”?
:P操作是对共享资源的预定,V操作是对共享资源的释放。
可以管理、保护好资源。
怎么说?
我们可以通过共享资源的使用来理解。
临界资源:受保护的共享资源
临界区:访问临界资源的代码片段
互斥:进程对公共资源的访问是相互排斥的(同一共享资源,同一时刻只能有一个进程访问)
原子性:独立不可分割的操作
可理解为只有两态(转账:要么转账成功,要么转账不成功,没有其他状态)
使用方式有两种:
初始值为1的信号量能实现互斥(我申请了,信号量从1减到0,别人就不能申请),这种信号量叫二元信号量。
进程访问共享资源需要先申请信号量,就像看电影,需要先“买票”。
进程想访问共享资源,得先申请信号量,这需要所有进程都得能看到同一个信号量,所以信号量本身就是共享资源。
这样的话,信号量又是怎么管理和保护自己的呢?
只需要令P操作和V操作(对共享资源的申请和释放)都是原子,要么申请成功,要么申请失败;要么释放成功,要么释放失败。
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};
struct ipc_perm {
key_t __key; /* Key supplied to semget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
int semop(int semid, struct sembuf *sops, unsigned nsops);
sops:sem 的options
struct sembuf
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
nspos:n个sops
具体怎么用,后面多进程时演示。
ipcs -s
先看共享内存、消息队列和信号量的抽象:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};
struct ipc_perm {
key_t __key; /* Key supplied to semget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
不仅接口相似度高(尤其是获取和删除),而且采用了类似的抽象方式。这叫什么?
这就叫 “标准”,所谓SystemV标准下的IPC方案,就是这个意思。
我们还发现,它们都有一个结构:
struct ipc_perm {
key_t __key; /* Key supplied to XXX(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
而且每个ipc_perm对象,都是共享内存这些结构的第一个字段,这有什么用?
通过类型转换实现一地址多用,就像多态一样。
今天的分享就到这里了,感谢您能看到这里。
这里是培根的blog,期待与你共同进步!