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