### 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-)