# 2019q1 Homework7 (ringbuffer)
contributed by < `johnnylord` >
[第 11 週測驗題](https://hackmd.io/s/HypUB7HjV)
## `mmap` 用途
mmap 可以把 file 映射到 process 的 address space 中(virtual address space)。
![](https://i.imgur.com/9VKX2Cw.png)
下面是 mmap 的 interface
```clike
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
```
> `addr` 表示將檔案裝置映射到 process 中 virtual memory 的起始位址
> `length` 表示映射的大小
> `prot` 程式對於這塊映射的 memory 有何權限(rwx)
> `flags` 在對映射的 memory 處理時,對應的檔案裝置如何應對
> `fd` 映射的檔案裝置的 file descriptor
> `offset` 從檔案裝置的哪裡開始映射
:::info
`addr` 的選取必須 page aligned。且記憶體的映射是連續的。
:::
## Circular buffer 實做
以下是利用 `mmap`,在 process address space 中建立兩個大小相同的 memory mapping 映射到相同的檔案裝置區域,以此建立 circular buffer。
```clike=
static void create_buffer_mirror(cirbuf_t *cb)
{
char path[] = "/tmp/cirbuf-XXXXXX";
int fd = mkstemp(path);
unlink(path);
ftruncate(fd, cb->size);
/* create the array of data */
cb->data = mmap(NULL, cb->size << 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0);
void *address = mmap(cb->data, cb->size, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_SHARED, fd, 0);
address = mmap(cb->data + cb->size, cb->size, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_SHARED, fd, 0);
close(fd);
}
```
![](https://i.imgur.com/uTvTI2S.png)
由於 `mmap` 是映射檔案裝置,所以必須先建立一個暫時性的檔案供我們做 memory mapping。
```clike
char path[] = "/tmp/cirbuf-XXXXXX"; //
int fd = mkstemp(path);
unlink(path);
ftruncate(fd, cb->size);
```
`ftruncate` 會將 `fd` 指向的檔案,伸縮成指定的大小 `cb->size`。
:::info
為什麼檔案經過 `unlink` 過後還可以使用呢? 根據 man page。
> unlink() deletes a name from the filesystem. If that name was the last link to a file and no processes have the file open, the file is deleted and the space it was using is made available for reuse.
> ==If the name was the last link to a file but any processes still have the file open, the file will remain in existence until the last file descriptor referring to it is closed==.
:::
接著再 allocate 一段記憶體空間做 memory mapping。
```clike
cb->data = mmap(NULL, cb->size << 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
```
由於我們之後會有兩個一樣大小的 mapping 對應到相同的裝置,這邊在做 allocate 時,會要求 `cb->size << 1` 兩倍的大小,而且 mapping 的記憶體內容沒有由任何檔案提供(fd 參數為 -1)。
最後在做兩次 `mmap` 得到兩塊記憶體空間對應到相同的裝置上。
```clike
void *address = mmap(cb->data, cb->size, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_SHARED, fd, 0);
address = mmap(cb->data + cb->size, cb->size, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_SHARED, fd, 0);
```
這樣子的作法,可以簡化 circular buffer 在 edge case 時處理檔案讀寫的問題,允許我們平常的讀寫超出 buffer 的 boundary。但是超出的部份對應到檔案的起始點。
## 測驗 `1`
考慮到 cirbuf 這個 circular buffer 實作,嘗試透過 mmap 系統來化簡緩衝區邊界處理的議題。對照 cirbuf.h 和 test-cirbuf.c 以得知具體用法,請補完以下程式碼:
```cpp
static inline int cirbuf_offer(cirbuf_t *cb,
const unsigned char *data,
const int size)
{
/* prevent buffer from getting completely full or over commited */
if (cirbuf_unusedspace(cb) <= size)
return 0;
int written = cirbuf_unusedspace(cb);
written = size < written ? size : written;
memcpy(cb->data + cb->tail, data, written);
cb->tail += written;
MM1
return written;
}
static inline unsigned char *cirbuf_peek(const cirbuf_t *cb)
{
if (cirbuf_is_empty(cb))
return NULL;
MM2
}
```
這邊 `MM1` 應當為
```cpp
if (cb->size < cb->tail) cb->tail %= cb->size;
```
在做寫入時(`memcpy`) 我們不用考慮 `cb->tail`,和 `cb->head` 在 edgae case 時的操作,因為 `mmap` 的實做簡化了這項操作,但是讀寫完後,還是要更新 circular buffer 的 `head` 和 `tail`。
而 `MM2` 應當為
```cpp
returb (char*)cb->data + cb->head;
```
上面答案跟原本答案不一樣的地方是在 cast `cb->data` 成 `char *`,原本的型態是 `void *` 並不可以做 pointer 的運算。
###### tags: `sysprog2019`