计算机是模拟人的行为逻辑,而进程非常像人,从创建到最终的销毁。
以普遍性论述,人与人之间可以通过声音传递信息,一方通过声音发送信息,一方接受信息并通过声音进行反馈。而我们发现声音这种介质是不独属于某个人的,是所有人共享的(感觉声音还是有点抽象,哈哈哈~)。当我们将这种交流逻辑映到射进程中,进行2个或者多个进程之间的交互或者通信,我们需要确定一种不独属性2个进程的介质,而计算机中能满足这个要求的就是内存。因此那些进程间的通信介质:匿名管道,命名管道,共享内存,消息队列,信号量等本质上都是内存的不同存在形式。同时人与人之间的交流,也是遵守规则的,典型的就是:请说普通话。因此进程间的通信依据不同的通信介质必然要设置不同的规则,以保证通信的高效与安全。。最终,一个进程将信息放到这个介质里面,另外一个进程通过这个介质拿到信息,处理后返回到介质中,–这就是进程间的通信宏观上的行为逻辑。
进程是具有独立性的,因此共享的内存必然不是进程提供的,只能是OS提供的。因此进程间通信的本质:由OS提供公开资源用以进程间通信,并加以管理的行为。
共享的资源可以以文件、队列、原始内存形式出现。
如果把网络也看做是一种介质,它也必然有它的规则。
管道本质是一种特殊的文件,向文件写入内容时,不会立刻映射到磁盘上。
#include
int pipe(int fildes[2]);
filde是一个输出参数,默认fildes[0]为读端,fildes[1]写端
成功返回0,否则-1
#include
#include
int mkfifo(const char *pathname, mode_t mode);
nt main()
{
// int mkfifo(const char *pathname, mode_t mode);
//mode 是要受umask:mode&(~umask)
if(mkfifo(MY_FIFO,666)<0)
{
perror("mkfifo failed");
return -1;
}
return 0;
}
pathname:路径+命名管道
mode:初始权限,因为受umask影响,最终:mode&(~umask)
client
#include "./comm.h"
int main()
{
int fd = open("./fifo", O_WRONLY | O_TRUNC);
if (fd < 0)
{
perror("O_WRONLY failed");
exit(1);
}
while(1)
{
printf("请输入#:");
fflush(stdout);
char tmp[100]={0};
read(0,tmp,sizeof(tmp)-1);
write(fd, tmp, strlen(tmp));
}
close(fd);
return 0;
}
server
#include "./comm.h"
int main()
{
//创建命名管道
// int mkfifo(const char *pathname, mode_t mode);
// mode 是要受umask:mode&(~umask)
// if (mkfifo(MY_FIFO, 0666) < 0)
// {
// perror("mkfifo is failed");
// return -1;
// }
int fd = open("./fifo", O_RDONLY);
if (fd < 0)
{
perror("O_RDONLY failed");
exit(1);
}
while (1)
{
char data[100] = {0};
int ret = read(fd, data, sizeof(data) - 1);
if (ret > 0)
{
//去掉读取的\n
data[strlen(data) - 1] = '\0';
//进程控制
if (strcmp(data, "run") == 0)
{
if (fork() == 0)
{
execl("/usr/bin/sl", "sl", NULL);
exit(1);
}
waitpid(-1, NULL, 0);
}
else if (strcmp(data, "show") == 0)
{
if (fork() == 0)
{
execlp("ls", "ls", "-al", NULL);
exit(1);
}
waitpid(-1, NULL, 0);
}
else
{
printf("%s\n", data);
}
}
else if (ret == 0)
{
printf(" read end\n");
break;
}
else
{
break;
}
}
close(fd);
return 0;
}
comm.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MY_FIFO "./fifo"
管道是具有一定大小的,在linux是64Kb
除了管道这种基于文件进行的通信,还有很多通信,而这些通信是个人,组织OS设置的规则来帮助进程通信。在历史发展中,System V和POSIX标准成为了主流。
System V中有System V 共享内存(shm),System V 消息队列(msq),System V 信号量(sem) 。这里重点介绍共享内存shm
当OS开辟一块内存,进程A和进程B都可以访问,这块内存就可以成为进程通信的介质,即共享内存
一个系统中,必然可以有多个共享内存,而OS是不信任任何人的,因此OS必须亲自去管理这些共享内存,OS要想管理好这块共享内存,必然先用结构体描述这块内存,最后通过特定的数据结构去组织这些结构体,最终OS管理共享内存块就变成了对结构体的增删查改。
同时,不同的进程都要与这个共享内存交互,必然需要一个能唯一标识这块共享内存的标识符,方便定位这块内存,这和文件描述符非常像。
因此System V为了管理好共享内存,需要解决:生成共享内存,进程和共享内存挂接,进程和共享内存去关联,共享内存的释放4个问题,也就是4个系统接口。
以下只是用户层的结构体信息,内核会在其基础添加一些信息
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 */
};
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 */
};
#include
#include
int shmget(key_t key, size_t size, int shmflg);
创建成功返回共享内存的标识符
- 这个key是为了帮助创建标识共享内存id的一个种子,可以自己确定,但是为了防止污染OS内核的某些标识符,使用系统接口 ftok来获得key。本质是利用路径+文件指向文件的唯一性.
#include
#include key_t ftok(const char *pathname, int proj_id);
- 1
- 2
- 3
return
- key-t 为有符号整数int
- 成功返回key,失败返回-1
pathname
路径+文件名可以唯一标识一个文件,文件必须存在
proj_id
自己设置的一个int数
共享内存在内核中的申请基本单位为一页:4096bite。用户申请时,编译器总会向上取整。即用户:4097,OS:4096*2
- 一种标记位:IPC_CREAT,IPC_EXCL
如果单独使用IPC_CREAT或者flag=0:OS会创建一个共享内存,如果创建的共享内存已经存在,则直接返回已经存在的共享内存id。
IPC_EXCL单独使用没有意义,一般和IPC_CREAT搭配使用IPC_CREAT|IPC_EXCL:创建一个新的共享内存,如果和系统内核重复,就返回错误-1。
即如果创建是用IPC_CREAT|IPC_EXCL,使用时用IPC_CREAT
只需要了解会挂接就行,shmaddr和 shmflg不需要了解太多,如有需要请man指令
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
//进程挂接共享内存
char *retp = (char *)shmat(shmid, NULL, 0);
if (retp == NULL)
{
perror("shmat failed");
exit(1);
}
#include
#include
int shmdt(const void *shmaddr);
int ret = shmdt(retp);
if (ret < 0)
{
perror("shmdt failed");
exit(1);
}
共享内存的生命周期是随OS内核的,只能通过调用系统接口才能销毁共享内存,或者内核OS重启。
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
return
成功操作共享内存返回0,失败返回-1
shmid
共享内存的标识符
cmd
操作指令:IPC_STAT,IPC_SET,IPC_RMID.
buf
用于辅助完成cmd指令
显示已申请的共享内存的信息
- 只有有挂接数为0时,删除shmid对应的共享内存才能成功。
- 命令也会调用shmctl进行销毁
全局变量虽然可以被子进程继承,但是会发生“写时拷贝‘,而共享内存不会。
comm.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PATHNAME "./"
#define PROJ_ID 0x66
#define SIZE 4097
client.cpp
#include "./comm.h"
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key < 0)
{
perror("ftok failed");
exit(1);
}
printf("key =%d\n", key);
//挂接
int shmid = shmget(key, SIZE, IPC_CREAT);
if (shmid < 0)
{
perror("shmid failed");
exit(1);
}
//进程挂接共享内存
char *retp = (char *)shmat(shmid, NULL, 0);
if (retp == NULL)
{
perror("shmat failed");
exit(1);
}
//业务逻辑
char ch = 'A';
while (ch<'Z')
{
retp[ch - 'A'] = ch;
ch++;
retp[ch - 'A'] = 0; //放置\0
sleep(1);
}
//用户层不需要删除共享内存,只需要与共享内存失去挂接关系即可
int ret = shmdt(retp);
if (ret < 0)
{
perror("shmdt failed");
exit(1);
}
return 0;
}
server.cpp
#include "./comm.h"
int main()
{
key_t key = ftok(PATHNAME, PROJ_ID);
if (key < 0)
{
perror("ftok failed");
exit(1);
}
// printf("key =%d\n", key);
//umask(0);
// int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL|0666);//8进制表示权限
//创建全新的shm,如果和系统已经存在的ID冲突,就错误退出
int shmid = shmget(key, SIZE, IPC_CREAT);//8进制表示权限
if (shmid < 0)
{
perror("shmget failed");
exit(1);
}
printf("shmid=%d\n",shmid);
//进程挂接共享内存
char *retp = (char *)shmat(shmid, NULL, 0);
if (retp == NULL)
{
perror("shmat failed");
exit(1);
}
//业务逻辑
while(1)
{
printf("%s\n",retp);
sleep(1);
}
// 共享内存的删除
//sleep(5);
//删除
shmctl(shmid,IPC_RMID,NULL);
printf("shmid: %d delete success\n",shmid);
//sleep(5);
return 0;
}
每一个描述IPC资源的结构体的第一个成员都是ipc_perm,结构非常类似
OS采用ipc_perm指针数组 (ipc_perm*arr)
的方式,将每个IPC资源的首地址存储在这个数组中,当需要使用某个IPC资源时,通过强制转换的方式即
(shmid_ds *)arr[i]->shm的属性
来对IPC资源进行管理,这也就是为什么在用户层我们获得的shmid是0,1,2,3,4…
这样的设计,OS就能以统一的视角/方式去管理各种IPC资源
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
进程代码很多,用来访问临界资源的代码称为临界区。
在任意一个时刻,只能允许一个执行流进入临界资源,执行自己的临界区。