1 任務(wù)一:實(shí)現(xiàn)固定內(nèi)存池
1.1 默認(rèn)內(nèi)存管理函數(shù)的不足
利用默認(rèn)的內(nèi)存管理函數(shù)
new/delete
或malloc/free
在堆上分配和釋放內(nèi)存會(huì)有一些額外的開銷吞加。
如果應(yīng)用程序頻繁地在堆上分配和釋放內(nèi)存,則會(huì)導(dǎo)致性能的損失。并且會(huì)使系統(tǒng)中出現(xiàn)大量的內(nèi)存碎片,降低內(nèi)存的利用率。
而內(nèi)存池是在真正分配內(nèi)存之前按灶,先申請分配一定數(shù)量检访,大小相等(固定內(nèi)存池的情況)的內(nèi)存塊留作備用始鱼。當(dāng)有新的內(nèi)存需求的時(shí)候,便從內(nèi)存池分出一部分內(nèi)存塊脆贵。這樣做就減少了在堆上分配和釋放內(nèi)存的次數(shù)医清,提高內(nèi)存的利用率。
1.2 需要實(shí)現(xiàn)的效果
每次申請就分配一塊固定大小的內(nèi)存塊卖氨,返回其首地址会烙。在這個(gè)內(nèi)存塊需要回收時(shí),將其由內(nèi)存池來管理筒捺。在內(nèi)存池已經(jīng)分配完后持搜,再次接受到申請請求時(shí),要新開辟一塊內(nèi)存池焙矛。
1.3 思路
假設(shè)內(nèi)存池的內(nèi)存塊個(gè)數(shù)為BLOCK_SIZE
,每一塊內(nèi)存塊大小為UNIT_SIZE
個(gè)字節(jié)残腌。
一開始先用malloc函數(shù)申請BLOCK_SIZE * UNIT_SIZE
大小的內(nèi)存村斟,再將這塊內(nèi)存構(gòu)建成一個(gè)記錄當(dāng)前可供分配內(nèi)存塊的單向鏈表,鏈表每個(gè)元素的大小為UNIT_SIZE
,如圖所示抛猫。
一開始的時(shí)候蟆盹,鏈表里的每個(gè)元素都是可供分配的。當(dāng)內(nèi)存申請時(shí)闺金,將鏈表的第一個(gè)分配出去逾滥,給申請對(duì)象返回該塊的首地址。在回收的時(shí)候败匹,將其添加回鏈表寨昙。當(dāng)分配滿了的時(shí)候,再申請一塊內(nèi)存來構(gòu)建單向鏈表掀亩。
因此整個(gè)過程就是構(gòu)建一個(gè)單向鏈表并維護(hù)該鏈表舔哪。
1.4 部分代碼
#define BLOCK_SIZE 5
#define UNIT_SIZE 31
// 數(shù)據(jù)結(jié)構(gòu)
typedef struct block {
struct block * next;
} T;
// 可分配內(nèi)存塊的鏈表頭指針
T * avail = NULL;
// 構(gòu)建單向鏈表
T * init(){
T * head = (T *)malloc(BLOCK_SIZE * UNIT_SIZE);
T * p = head;
for (int i = 0; i < BLOCK_SIZE - 1; i++){
// 這里將T類型指針轉(zhuǎn)成char類型指針是確保指針移動(dòng)了UNIT_SIZE個(gè)字節(jié)數(shù)
p->next = (T *)((char *)p + UNIT_SIZE);
p = p->next;
}
p->next = NULL;
return head;
}
// 申請一塊內(nèi)存塊
void * apply(){
T * p;
if (avail == NULL){
avail = init();
printf("generate new pool\n");
}
p = avail;
avail = avail->next;
// 返回?zé)o類型指針 void *
return (void *)p;
}
// 回收
void recycle(void * temp){
T * t = (T*)temp;
t->next = avail;
avail = t;
}
1.5 收獲
總體下來思路還是挺清晰的,實(shí)現(xiàn)過程出現(xiàn)的很多問題都是對(duì)C語言不熟悉特別是指針的移動(dòng)槽棍。出現(xiàn)的疑問有指針的加1操作捉蚤、結(jié)構(gòu)體大小計(jì)算以及字節(jié)對(duì)齊的原因等。
1.6 相關(guān)鏈接
2 任務(wù)二:使用存儲(chǔ)映射I/O(mmap函數(shù))修改文本文件
2.1 mmap和常規(guī)文件操作的區(qū)別
常規(guī)文件系統(tǒng)操作(調(diào)用read/fread等類函數(shù))中缆巧,函數(shù)的調(diào)用過程:
1、進(jìn)程發(fā)起讀文件請求豌拙。
2陕悬、內(nèi)核通過查找進(jìn)程文件符表,定位到內(nèi)核已打開文件集上的文件信息姆蘸,從而找到此文件的inode墩莫。
3芙委、inode在address_space上查找要請求的文件頁是否已經(jīng)緩存在頁緩存中。如果存在狂秦,則直接返回這片文件頁的內(nèi)容灌侣。
4、如果不存在裂问,則通過inode定位到文件磁盤地址侧啼,將數(shù)據(jù)從磁盤復(fù)制到頁緩存。之后再次發(fā)起讀頁面過程堪簿,進(jìn)而將頁緩存中的數(shù)據(jù)發(fā)給用戶進(jìn)程痊乾。
常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤,使用了頁緩存機(jī)制椭更。一個(gè)進(jìn)程讀文件需要兩次拷貝(文件頁到頁緩存哪审,頁緩存到進(jìn)程對(duì)應(yīng)的地址空間),寫操作也是一樣虑瀑。(?)
而使用mmap操作文件中湿滓,創(chuàng)建新的虛擬內(nèi)存區(qū)域和建立文件磁盤地址和虛擬內(nèi)存區(qū)域映射這兩步,沒有任何文件拷貝操作舌狗。
兩者的區(qū)別如圖
最直觀的感覺就是使用mmap映射叽奥,多個(gè)進(jìn)程共享了文件的同一個(gè)副本。
2.2 mmap內(nèi)存映射的實(shí)現(xiàn)過程
(一)進(jìn)程啟動(dòng)映射過程痛侍,并在該進(jìn)程的虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域
(二)調(diào)用內(nèi)核空間的系統(tǒng)調(diào)用函數(shù)mmap朝氓,實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址的一一映射關(guān)系
(三)進(jìn)程發(fā)起對(duì)這片映射空間的訪問,引發(fā)缺頁異常主届,實(shí)現(xiàn)文件內(nèi)容到物理內(nèi)存(主存)的拷貝
該引用的文章鏈接為 https://www.cnblogs.com/huxiao-tee/p/4660352.html
2.3 需要實(shí)現(xiàn)的效果
新建文本文件赵哲,使用mmap來添加文字并保存。
2.4 代碼
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int main(){
// 待添加的文本
char * src = "testtesttest";
int out= open("./a", O_RDWR | O_CREAT | O_TRUNC,FILE_MODE);
int input_size = strlen(src);
// 下面兩步是為了將文件大小設(shè)置成待添加文本的大小君丁。映射的時(shí)候才會(huì)做到一一映射
if(lseek(out, input_size - 1, SEEK_SET) == -1){//將光標(biāo)移到第input_size - 1的位置
printf("lseek error\n");
}
if (write(out, "", 1) != 1){//寫入字節(jié)為1的空字符
printf("write error\n");
}
void * dest = mmap(0, input_size, PROT_READ | PROT_WRITE,MAP_SHARED, out,0);
if(dest == MAP_FAILED){
printf("mmap error\n");
}
// 將fdin指向的區(qū)域拷貝到dst指向的區(qū)域誓竿,拷貝input_size個(gè)字節(jié)
memcpy(dest, src, input_size);
return 0;
}
2.5 收獲
這份代碼是按照書中例子敲的,但是自己不熟悉c語言的常用函數(shù)例如lseek
,write
等導(dǎo)致誤解了函數(shù)的作用谈截。所以說不懂的函數(shù)筷屡,參數(shù)就要多查,不要亂猜簸喂。把解決一個(gè)問題需要的前提條件和準(zhǔn)備工作構(gòu)思好毙死,然后去檢查案例代碼是否完成了這些工作!S黯扼倘!