### 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 範例

共享的 ``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
可以對照下方例子看


##### 例子:
假設 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

``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 所需要的大小

> 把 object 的大小設成 4096 Bytes
3. 最後用 ``mmap()`` function 來建立一個包含這個 shared memory object 的 memory-mapped file
##### Producer Code

``MAP_SHARED``: flag,代表如果有對 shared memory object 做更動,所有 sharing 這個 object 的 processes 都看得到
``sprintf()``: 寫入 shared memory object
- 每次 write 之後,寫多少 byte,ptr 就要加多少
##### Consumer Code

``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() 例子

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

如果有其中某個 process 對 shared object write 或 read,就會產生 page fault,然後這個 shared object 才會真的被 bring in to physical memory
- ``mmap()`` 的其中一個好處就是像這個例子中 process A, B 在 memory 裡只共用同一個 page,就算更多的 process 也可以像這樣共用,所以節省了很多 memory 空間

如果 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.

> 摘自 wiki
>

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

---
### 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 之間的溝通
> 
- 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-)