---
# System prepended metadata

title: IPC in shared memory
tags: [POSIX shared memory, shared memory, buffer, 研究所, 計系, mmap, producer consumer, pipe]

---

### Background


- shared memory 通常是放在 create 這個 shared memory 的 process 的 address space，其他想要透過這塊 shared memory 來溝通的 process，必須把這塊 shared memory attach 到他的 address space

> 但是 OS 會 prevent process 去 access 別的 process 的 memory，所以 shared memory 需要 processes 間同意移除這個限制。使用 shared memory 時，processes 間交換 data 的形式、位置要放哪，都是 processes 之間自己決定，不會受到 OS 的控制。

甚麼情況下會需要 shared memory？
舉例來說，processes 之間要合作，會有 producer - consumer problem：
> 例如：
>  compiler 產生 assembly code 後，會給 assembler 組譯
>  → compiler 是 producer process，assembler 是 consumer process

要如何讓 compiler 把 assembly code 給 assembler，其中一個方法就是 shared memory

為了讓 producer 和 consumer process 可以同時 run，我們需要一個 <font color = "snake">**buffer**</font> 可以讓 producer 把生產出來的東西丟進去、讓 consumer 把生產好的東西拿出來用，這個 buffer 就需要放在一塊 producer 和 consumer 共用的 memory 中，而且還需要考慮同步問題，避免 consumer 去 consume producer 根本還沒生產出來的東西。

### Buffer 種類

1. ==Unbounded Buffer==

不會限制 buffer 大小
consumer 可能會需要等 producer 把東西生產出來，但 producer 不用等 consumer 把東西消耗掉才能生產新的，可以無限制的生產。

2. ==Bounded Buffer==

有固定的 buffer 大小
consumer 可能會需要等 producer 把東西生產出來，同時 producer 也需要等 consumer 把東西消耗掉才能生產新的。

#### Bounded Buffer 範例

![image](https://hackmd.io/_uploads/BykywNFPa.png)

共享的 ``buffer`` 用 circular array implement
有兩個 ptr
1. ``in`` ： in 指向 buffer 下一個空著的位置
2. ``out``： out 指向 buffer 第一個有東西的位置

- buffer 空時： ``in == out`` 
- buffer 滿時： ``((in + 1)% buffer 大小) == out``

為什麼是由這樣的條件來判定 buffer 空、滿？
先看code：

#### Produce / Consume Code

可以對照下方例子看

![image](https://hackmd.io/_uploads/HJH7iEYv6.png)

![image](https://hackmd.io/_uploads/rJLVo4Fv6.png)

##### 例子：
假設 buffer 大小 = 3

1. ==init==


| index    | 0 | 1 | 2 |
| --- | -------- | -------- | -------- |
|   data  | -        | -    | -     |

<font color = "green">in = out = 0</font>
一開始確實 ``in == out`` 時 buffer 為空

2. ==假設 producer 產生 "a"== 
``buffer[in] = next_produced``
> buffer[0] = a

| index    | 0 | 1 | 2 |
| --- | -------- | -------- | -------- |
|   data  | a       | -    | -     |

``in = (in + 1) % buffer_size``
>  in = (0 + 1) % 3
>  → in = 1 

<font color = "green">in = 1, out = 0</font>

3. ==如果 consumer 馬上就要把 "a" 消耗掉==

``next_produced = buffer[out]``
> next_produced = buffer[0] = a

``out = (out + 1) % buffer_size``
>  out = (0 + 1) % 3
>  → out = 1


| index    | 0 | 1 | 2 |
| --- | -------- | -------- | -------- |
|   data  | -        | -    | -     |

此時 <font color = "green">in = out = 1</font>
確實 ``in == out`` 時 buffer 為空  


2*  ==假設重來，第一步後 producer 又接連要產生兩個 "b", "c"==

產生  "b"
>  in = (1 + 1) % 3
>  → in = 2 

產生  "c"
>  in = (2 + 1) % 3
>  → in = 0 = out
因為``while(((in + 1) % buffer_size) == out);`` 所以沒辦法加入 "c"，此時算是 buffer 滿

- <font color = "red">用這個方法 buffer 最多只能放 ``buffer_size - 1`` 個 element</font>

| index    | 0 | 1 | 2 |
| --- | -------- | -------- | -------- |
|   data  | a       | b    | -     |

<font color = "green">in = 2, out = 0</font>

3* ==假設接著 consumer 要消耗東西==

消耗 a
>  out = (0 + 1) % 3
>  → out = 1

| index    | 0 | 1 | 2 |
| --- | -------- | -------- | -------- |
|   data  | -        | b    | -     |

<font color = "green">in = 2, out = 1</font>

4* ==producer 又想再生產一個 "d"==

這次要產生  "d" 時
>  buffer[2] = d
>  in = (2 + 1) % 3 = 0  ≠  out = 1
>  → in = 0

| index    | 0 | 1 | 2 |
| --- | -------- | -------- | -------- |
|   data  | -        | b    | d     |

<font color = "green">in = 0, out = 1</font>

5* ==如果接著 producer 又想再生產一個 "e"==
> 同樣 while loop 先測試 (in + 1) % buffer_size)
>  因為 (0 + 1) % 3 = 1 = out
>  所以沒辦法加入 "e"

#### 會遇到的問題

這個方法沒有討論到如果 producer 和 consumer 同時都想要 access buffer 怎麼辦

> 解決方法 → CH6, CH7

---
### IPC System 例子
#### POSIX Shared memory

用 <font color = "snake">**memory-mapped files** </font>來做 Shared memory
→ 把 shared memory 的 region 和一個 file 連結

1. 首先，某個 process 需要透過``shm_open()`` 的 sys call 建立一個 shared memory object 

![image](https://hackmd.io/_uploads/BJl9hBtDp.png)

``name``: shared memory object 的名字
> 如果有 process 想 access 這個 shared memory 就要用到

``0_CREAT``: 如果這個 object 目前還不存在就 create
``0_RDWR``: 這個 object 是拿來 read / write 的
``0666``: file access 的 permission

- 如果 ``shm_open()`` 有成功，就會 return 一個 int 的 file descriptor ( 圖中的 ``fd``)

2. 當 object 被建立之後，我們會用一個 function 叫做 ``ftruncate()`` 來配置 object 所需要的大小

![image](https://hackmd.io/_uploads/HylmRHtPp.png)
> 把 object 的大小設成 4096 Bytes

3. 最後用 ``mmap()`` function 來建立一個包含這個 shared memory object 的 memory-mapped file

##### Producer Code

![image](https://hackmd.io/_uploads/S1DUlIYv6.png)

``MAP_SHARED``: flag，代表如果有對 shared memory object 做更動，所有 sharing 這個 object 的 processes 都看得到

``sprintf()``: 寫入 shared memory object

- 每次 write 之後，寫多少 byte，ptr 就要加多少


##### Consumer Code

![image](https://hackmd.io/_uploads/H1bKbUYPa.png)

``shm_unlink()``: consumer access 某部分 shared memory segment 後，就會把用掉的部分移除

---
### mmap() 補充

> 以下內容來自影片 *understanding mmap, the workhorse behind keeping memory access efficient in linux* 筆記，不一定完全正確，有興趣可參考 Reference 連結

``mmap()`` 的優點在於傳統做法上如果要 access 一個 file，不管是 ``open()``, ``read()``,  ``write()``, ``getline()``, ``printf()``... 每次都是在發 sys call，雖然現在 sys call 都已經 optimized，但是發如果只是要寫幾個 bytes 也要發 sys call ，在 user mode 和 kernel mode 間不斷切換，就還是會稍微慢一點（尤其是在做很多小小的 operation 的時候），此時 ``mmap()`` 就是提供了另一種取代一直 sys call ``read()``,  ``write()`` ...的替代方案

``mmap()`` 仍然會需要用到 ``open()`` sys call，因為需要獲得  file descriptor 來告訴 OS 這是我想要 operate 的 file，接著發 ``mmap()`` sys call 告訴 OS 說，我想要把這個 file 放到我的 virtual memory 的某個 address 範圍，不過在這之後，所有的 ``read()``,  ``write()`` 就變成 internal 的，不用再發 sys call 了

舉個應用的例子：
假設一個 parent process 用了 ``mmap()`` 之後，用 ``fork()`` create 很多 child process，``mmap()`` pulled in 的 region 就會保持 mapped between parent 和 child process，任何 parent 的 write 對 child 來說都是 visible 的，反之亦然。

- 用 ``mmap()`` 有點像是「佈告欄」的概念，如果有人在佈告欄上寫了東西，我不會收到通知，但是如果我去看佈告欄，我就能看到上面被寫了什麼。

#### mmap() 例子

![image](https://hackmd.io/_uploads/HJuo1PtPa.png)

假設一開始有兩個 Processes A, B 被 forked，且圖中紅色線框起來的區域為和 ``mmap()`` 要求的 map shared region，shared object 被 mapped 到兩個 processes 各自的 virtual memory address space

![image](https://hackmd.io/_uploads/By2EZwKvT.png)

如果有其中某個 process 對 shared object write 或 read，就會產生 page fault，然後這個 shared object 才會真的被 bring in to physical memory

- ``mmap()`` 的其中一個好處就是像這個例子中 process A, B 在 memory 裡只共用同一個 page，就算更多的 process 也可以像這樣共用，所以節省了很多 memory 空間

![image](https://hackmd.io/_uploads/S1GhZDtD6.png)

如果 Processes A 將 50 寫入 shared object， 50 馬上就會反映到 physical memory，且 process B 馬上就看得到


#### Solaris 對 mmap() 的使用

有些系統只有在用特別的 sys call 時才會做 memory mapping，其他的 file I/O 就還是用一般的 sys call；但是也有系統不管是不是用 ``mmap()``，都會 memory map file，像是Solaris

在 Solaris 中

- 如果有一個 file 是 memory mapped (用 ``mmap()`` sys call)， Solaris 就會把這個 file map 到這個 process 的 address space
- 如果有一個 file 是用一般的 sys call (``open()``, ``read()``,  ``write()``)， Solaris 還是會 memory map 這個 file，只是是 map 到 kernl 的 address space

不管 file 是如何被 open 的， Solaris 都將所有的 file I/O 視作 memory mapped，讓所有的 file access 透過較 efficient 的 memory subsystem，也能防止傳統 ``read()``,  ``write()`` 造成的 sys call overhead


##### 考古

> 98 交 7.
![image](https://hackmd.io/_uploads/rJBYf_fY6.jpg)

> 摘自 wiki
> 
![image](https://hackmd.io/_uploads/SkmRXOfta.png)

> 摘自 [stack overflow](https://stackoverflow.com/questions/258091/when-should-i-use-mmap-for-file-access)

![image](https://hackmd.io/_uploads/ByNcrdMt6.png)

---
### Pipe

pipe 是 UNIX 早期系統最初的 IPC mechanisms 之一

#### Ordinary pipes

- 允許兩個 processes communicate
- unidirectional
> 兩個 processes 中，其中一個作為 producer 從 pipe 的一端 write（此端稱作 <font color = "snake">write end</font>），而 consumer 從另一端 read（此端稱作 <font color = "snake">read end</font>）
- 在 UNIX 中，建立 pipe 用 ``pipe(int fd[])`` system call
- 建立 pipe 的 process 以外的 process 不能 access pipe
> 因為 pipe 是++一種特殊形式的 file++，而且 child 會繼承 parent 的 file，所以通常 ``pipe()`` 是拿來用在 parent ``fork()`` child 以後和 child 之間的溝通
> ![image](https://hackmd.io/_uploads/B1ljBniY6.png)

- Ordinary pipes 只有在兩個 processes 在溝通時才存在
- Ordinary pipes 在 Windows 系統裡面又稱作 <font color = "snake">anonymous pipes</font>，和 UNIX 的 Ordinary pipes 功能差不多
> Windows 系統中，要
> 
> 建立 pipe 可用 ``CreatePipe()``function
> 對 pipe read 可用 ``ReadFile()`` function
> 對 pipe write 可用 ``WriteFile()`` function


#### Named Pipes

- bidirectional
> 不一定要是 parent-child 關係
- 可以很多 processes 一起共用同個 named pipe 進行溝通
- 當 communication 結束時仍然存在
- 在 UNIX 系統中，Named Pipes 被稱作 <font color = "snake">FIFOs</font>，在 file system 中看起來就像一般的 file 
> ``mkfifo()`` sys call 用來建立 FIFOs
> 其他 operation 就用一般的 ``open()``, ``read()``, ``write()``, ``close()``



---
### Reference

- 恐龍 p.125-127, p.132-135, p.140-143, p.556
- [understanding mmap, the workhorse behind keeping memory access efficient in linux](https://youtu.be/8hVLcyBkSXY?si=1CmU1ZWEKJt1uzd-)