进程可以将同一段共享内存连接到它们自己的地址空间,所有进程都可以访问共享内存中的地址,如果某个进程向共享内存内写入数据,所做的改动将立即影响到可以访问该共享内存的其他所有进程。
相关接口
创建共享内存:int shmget(key_t key, int size, int flag);
成功时返回一个和key相关的共享内存标识符,失败范湖范围-1。
key:为共享内存段命名,多个共享同一片内存的进程使用同一个key。
size:共享内存容量。
flag:权限标志位,和open的mode参数一样。
连接到共享内存地址空间:void shmat(int shmid, void addr, int flag);
返回值即共享内存实际地址。
shmid:shmget()返回的标识。
addr:决定以什么方式连接地址。
flag:访问模式。
从共享内存分离:int shmdt(const void *shmaddr);
调用成功返回0,失败返回-1。
shmaddr:是shmat()返回的地址指针。
其他补充
共享内存的方式像极了多线程中线程对全局变量的访问,大家都对等地有权去修改这块内存的值,这就导致在多进程并发下,最终结果是不可预期的。所以对这块临界区的访问需要通过信号量来进行进程同步。
但共享内存的优势也很明显,首先可以通过共享内存进行通信的进程不需要像无名管道一样需要通信的进程间有亲缘关系。其次内存共享的速度也比较快,不存在读取文件、消息传递等过程,只需要到相应映射到的内存地址直接读写数据即可。
信号量
在提到共享内存方式时也提到,进程共享内存和多线程共享全局变量非常相似。所以在使用内存共享的方式是也需要通过信号量来完成进程间同步。多线程同步的信号量是POSIX信号量,
而在进程里使用SYSTEM V信号量。
相关接口
创建信号量:int semget(key_t key, int nsems, int semflag);
创建成功返回信号量标识符,失败返回-1。
key:进程pid。
nsems:创建信号量的个数。
semflag:指定信号量读写权限。
改变信号量值:int semop(int semid, struct sembuf *sops, unsigned nsops);
我们所需要做的主要工作就是串讲sembuf变量并设置其值,然后调用semop,把设置好的sembuf变量传递进去。
struct sembuf结构体定义如下:
C++ struct sembuf{
short sem_num;
short sem_op;
short sem_flg; };
成功返回信号量标识符,失败返回-1。
semid:信号量集标识符,由semget()函数返回。
sops:指向struct sembuf结构的指针,先设置好sembuf值再通过指针传递。
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作。
直接控制信号量信息:int semctl(int semid, int semnum, int cmd, union semun arg);
semid:信号量集标识符。
semnum:信号量集数组上的下标,表示某一个信号量。
arg:union semun类型。
辅助命令
ipcs命令用于报告共享内存、信号量和消息队列信息。
ipcs -a:列出共享内存、信号量和消息队列信息。
ipcs -l:列出系统限额。
ipcs -u:列出当前使用情况。
套接字
详见socket交互流程
同步互斥机制待补充
网络I/O模型在描述这块内容的诸多书籍中,很多都只说笼统的概念,我们将问题具体化,暂时只考虑服务器端的网络I/O情形。我们假定目前的情形是服务器已经在监听用户请求,建立连接后服务器调用read()函数等待读取用户发送过来的数据流,之后将接收到的数据打印出来。
所以服务器端简单是这样的流程:建立连接 -> 监听请求 -> 等待用户数据 -> 打印数据。我们总结网络通信中的等待:
建立连接时等待对方的ACK包(TCP)。
等待客户端请求(HTTP)。
输入等待:服务器用户数据到达内核缓冲区(read函数等待)。
输出等待:用户端等待缓冲区有足够空间可以输入(write函数等待)。
另外为了能够解释清楚网络I/O模型,还需要了解一些基础。对服务器而言,打印出用户输入的字符串(printf函数)和从网络中获取数据(read函数)需要单独来看。服务器首先accept用户连接请求后首先调用read函数等待数据,这里的read函数是系统调用,运行于内核态,使用的也是内核地址空间,并且从网络中取得的数据需要先写入到内核缓冲区。当read系统调用获取到数据后将这些数据再复制到用户地址空间的用户缓冲区中,之后返回到用户态执行printf函数打印字符串。我们需要明确两点:
read执行在内核态且数据流先读入内核缓冲区;printf运行于用户态,打印的数据会先从内核缓冲区复制到进程的用户缓冲区,之后打印出来。