操作系統原理實驗:動態分區分配方式(魔改版first fit(首次適應)算法和best fit(最佳適應)算法)

實驗目的

瞭解動態分區分配方式中使用的數據結構和分配算法,進一步加深對動態分區存儲管理方式及其實現過程的理解。提高設計實驗、發現問題、分析問題和解決問題的能力,並學習撰寫規範的科學研究報告。

內容和要求

1.用C或其他語言分別實現動態分區分配過程和回收過程。
2.設置初始狀態,每次分配和回收後顯示出空閒內存分區鏈的情況。

實驗原理

(1)first fit(首次適應)算法 · 魔改。

現代計算機系統爲了確保安全性,操作系統均引入了ASLR(地址空間佈局隨機化),並與MMU的更復雜的地址翻譯機制配合,使得數據在內存中的分佈顯得隨機。
爲了模擬得更接近較新的操作系統的動態內存分配與回收機制,這裏不模擬通過first fit和best fit算法直接進行順序查找並分配內存,而是模擬用戶調用malloc、new等內存分配的函數或運算符之後,從線性地址已經轉換爲物理頁號(PPN)的時刻開始,直到返回隨機分配的物理頁號,這一段分配過程(因爲一旦隨機找到一片滿足要求的可用空間就立即分配,所以這裏其實借鑑了first fit的思想)。
本程序通過< random >中的隨機數生成引擎std::mt19937_64隨機生成物理頁號來簡單模擬MMU將線性地址轉換爲內存的物理地址並取得物理頁號的過程,並用兩個std::map——free_list和used_list模擬由操作系統內核維護的空閒列表。頁大小設爲4 KB。
本程序通過模擬對內存的按頁分配來演示內存分配與釋放的過程。內存空間一次至少分配一頁,並在成功分配後修改空閒列表free_list和已使用列表used_list。在回收已分配的內存頁時,這兩個表也要修改。物理內存的第0頁設爲不可訪問,當沒有足夠大的連續內存空間可供分配時,頁號就返回0,代表分配失敗。
如果隨機查找到頁數比需要的頁數大的內存空間,那麼在這段內存空間中隨機選取一段連續的頁進行分配。
每次釋放內存後,當空閒列表free_list上有兩個相鄰節點表示的物理內存空間連續時,爲了減少查找空閒列表的耗時,這兩個節點要合併。
這裏不用線性表或鏈表來構造空閒列表,是因爲需要頻繁插入、刪除、查找:在線性表上查找、插入、刪除元素的時間複雜度和在鏈表上查找的時間複雜度均達到O(n)。本實驗選用的std::map是用紅黑樹實現的。如果使用std::map來構造空閒列表,那麼隨機分配和釋放內存時,時間複雜度均減小到O(log n)。當內存不足時,由於分配內存需要遍歷該表,時間複雜度爲遍歷紅黑樹的均攤時間複雜度O(n);但回收已使用內存時,時間複雜度仍然爲O(log n)。
使用used_list是爲了模擬內存的釋放。used_list也用紅黑樹存儲,方便每次分配或回收時按低地址(0x0)到高地址的順序輸出內存空間的使用情況。模擬內存回收在模擬內存分配之後進行。全部的已使用空間的地址會通過std::shuffle打亂順序,然後按此順序釋放內存。
嘗試隨機分配最多10次(可以在代碼中修改嘗試次數max_attempt_count)。如果隨機查找空閒列表10次都未找到足夠大的連續內存空間來分配這些頁,就認爲可用的連續內存空間已經不足,此時將遍歷整個空閒列表嘗試分配。當第一次找到足夠的空間後,就分配內存(first fit);如果遍歷完畢後仍然找不到符合要求的空閒內存,則分配失敗。
每次分配和回收後,都顯示空閒內存和已用內存的情況。
由於篇幅所限,在本實驗中不模擬內存碎片的回收機制。

(2)best fit(最佳適應)算法 · 魔改。

這一部分的很多代碼可以直接套用(1)中的代碼,但也有地方需要修改。
首先free_list的數據結構採用std::multimap,且以頁數爲索引。也就是說第一項爲頁數,第二項爲這段連續的內存空間的起始地址。分配內存的時候,根據所需的頁大小可以以O(log n)的時間複雜度取得一段儘可能小的連續的內存空間,減少內存碎片。當這段內存空間的頁數更多時,同樣隨機選取其中一段連續的頁面進行分配。如果搜索不到長度達到所需頁數的連續內存空間,則直接返回分配失敗。
used_list的含義與(1)相同。
當釋放內存時,由於空閒列表不是按起始地址索引的,而是按頁數索引的,所以需要遍歷全表來判斷在將釋放的內存對應的節點重新寫入空閒列表後,是否有節點刻畫的空閒內存與這段剛釋放的空間相鄰。如果是,則合併節點。釋放內存的時間複雜度爲遍歷整個紅黑樹的均攤時間複雜度,即O(n)。
輸出內存情況的函數只需做少量的修改即可。

代碼

橫向長度較長,建議先將代碼複製到IDE中,然後將IDE的窗口最大化,再進行查看。
(1)first fit

#include<iostream>
#include<map>
#include<algorithm>
#include<vector>
#include<random>
#include<chrono>
#include<string>
#include<iomanip>
#include<cmath>
#include<cstdint>
using namespace std;

const size_t memory_capacity = 1ull << 33;
const size_t page_count = memory_capacity / 4096; //Page size = 4 KB
const size_t bound[] = { 1ull, 1ull << 8, 1ull << 18, 1ull << 28, 1ull << 38, 1ull << 48 };
const size_t page_to_byte = 1ull << 12;
const char* const unit[] = { " KB ", " MB ", " GB ", " TB ", " PB ", " EB " };
const size_t exp_count = 1024;

map<size_t, size_t> free_list, used_list; //The first is starting address, the second is page count.
const pair<size_t, size_t> entire_memory = { 1, page_count - 1 }; string output_str;

vector<size_t> free_seq;
uniform_int_distribution<size_t> us(1, page_count - 1), upe(2, UINT64_MAX); mt19937_64 re; //Random engine.

//Modify the node of the free list and the used list according to the random situation of allocation.
inline size_t split_node_n_mark_used(const map<size_t, size_t>::iterator& i, size_t _PageCount) {
	static pair<size_t, size_t> p, q, r;
	p.first = us(re) % (i->second - _PageCount) + i->first; p.second = _PageCount;
	if (p.first == i->first) {
		q = { p.first + p.second, i->second - _PageCount }; free_list.erase(i); free_list.emplace(q);
	}
	else if (p.first + p.second == i->first + i->second) {
		q = { i->first, i->second - _PageCount }; free_list.erase(i); free_list.emplace(q);
	}
	else {
		q = { i->first, p.first - i->first }; r = { p.first + p.second, i->second - q.second - _PageCount };
		free_list.erase(i); free_list.emplace(q); free_list.emplace(r);
	}
	used_list.emplace(p); return p.first;
}

//Allocate specific free memory.
inline size_t alloc(const map<size_t, size_t>::iterator& i, size_t _PageCount) {
	static size_t s; s = us(re);
	if (i->second == _PageCount) { s = i->first; used_list.emplace(*i); free_list.erase(i); return s; } //Allocation succeeded.
	else { s = i->first; return split_node_n_mark_used(i, _PageCount); } //Allocation succedded.
}

//Allocate a segment of free memory.
inline size_t random_alloc(size_t _PageCount) {
	static const size_t max_attempt_count = 10; static size_t s; static map<size_t, size_t>::iterator i;
	for (size_t h = 0; h < max_attempt_count; ++h) {
		s = us(re); i = free_list.lower_bound(s); if (i == free_list.end())--i;
		if (i->second == _PageCount) { s = i->first; used_list.emplace(*i); free_list.erase(i); return s; } //Allocation succeeded.
		else if (i->second > _PageCount) { return split_node_n_mark_used(i, _PageCount); } //Allocation succedded.
	}
	//Random allocation failed after several attempts due to the lack of memory (or too many small fragmentations).
	//Needs to traverse the free list to try to allocate a segment of free memory.
	for (map<size_t, size_t>::iterator i = free_list.begin(); i != free_list.end(); ++i) {
		if (i->second >= _PageCount) { return alloc(i, _PageCount); }
	}
	return 0; //No enough continuous free memory space. Using the first page frame (PPN = 0) is forbidden.
}

//Release a segment of used memory.
inline void release(size_t _PPN) {
	static map<size_t, size_t>::iterator i, j;
	static pair<map<size_t, size_t>::iterator, bool> r; static bool merged_left_node;
	i = used_list.find(_PPN); r = free_list.emplace(*i); merged_left_node = false;
	if (r.first != free_list.begin()) {
		j = r.first; --j;
		if (j->first + j->second == r.first->first) {
			j->second += r.first->second; r.first = free_list.erase(r.first); merged_left_node = true;
		}
	}
	if (merged_left_node) { --r.first; };
	if ((*r.first).first != (*free_list.rbegin()).first) {
		j = r.first; ++j;
		if (r.first->first + r.first->second == j->first) {
			r.first->second += j->second; free_list.erase(j);
		}
	}
	used_list.erase(i);
}

//Convert the physical page number to another notation.
inline void convert(size_t _PPN, string& _Dest, unsigned _AlignMode = 1) {
	_Dest.clear();
	while (_PPN >= (1 << 8)) {
		auto d = upper_bound(bound, bound + 5, _PPN) - bound - 1;
		_Dest.append(to_string(_PPN / bound[d])); _Dest.append(unit[d]);
		_PPN %= bound[d];
	}
	if (_PPN) { _Dest.append(to_string(_PPN * 4)); _Dest.append(" KB"); }
	else _Dest.pop_back();
	if (_AlignMode)_Dest.insert(0, 48 - _Dest.size(), ' ');
	else _Dest.resize(48, ' ');
}

inline void output_list(const map<size_t, size_t>& _List) {
	cout << "               Starting Position                 |                  Ending Position                 | Length (Dec)" << endl;
	for (map<size_t, size_t>::const_iterator i = _List.cbegin(); i != _List.cend(); ++i) {
		cout << "--------------------------------------------------------------------------------------------------------------------" << endl;
		cout << hex << setw(48) << i->first * page_to_byte << " | " << setw(48) << (i->first + i->second) * page_to_byte << " | " << dec << i->second * page_to_byte << endl;
		convert(i->first, output_str); cout << output_str << " | ";
		convert(i->first + i->second, output_str); cout << output_str << " | ";
		convert(i->second, output_str, 0); cout << output_str << endl;
	}
	cout << "--------------------------------------------------------------------------------------------------------------------\nList size = " << _List.size() << endl;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	re.seed(chrono::steady_clock::now().time_since_epoch().count());
	free_list.emplace(entire_memory);

	cout << "********************************SIMULATION STARTS********************************\n" << endl;
	cout << "------------------------------------------------ Free List ------------------------------------------------" << endl;
	output_list(free_list);
	cout << "------------------------------------------------ Free List Ends ------------------------------------------------\n" << endl;
	cout << "\nStart to allocate memory.\n" << endl;
	for (size_t i = 0; i < exp_count; ++i) {
		if (!random_alloc(log2(upe(re)) * (upe(re) % 128 + 1)))cout << "ERROR: Allocation failed." << endl;
		cout << "------------------------------------------------ Free List ------------------------------------------------" << endl;
		output_list(free_list);
		cout << "------------------------------------------------ Free List Ends ------------------------------------------------\n\n\n\n" << endl;
		cout << "------------------------------------------------ Used List ------------------------------------------------" << endl;
		output_list(used_list);
		cout << "------------------------------------------------ Used List Ends ------------------------------------------------\n\n\n\n" << endl;
	}
	for (map<size_t, size_t>::const_iterator i = used_list.cbegin(); i != used_list.cend(); ++i) { free_seq.emplace_back(i->first); }
	shuffle(free_seq.begin(), free_seq.end(), re);
	cout << "\n\n\n\n\n\nStart to free memory.\n" << endl;
	for (vector<size_t>::const_iterator i = free_seq.cbegin(); i != free_seq.cend(); ++i) {
		release(*i);
		cout << "------------------------------------------------ Free List ------------------------------------------------" << endl;
		output_list(free_list);
		cout << "------------------------------------------------ Free List Ends ------------------------------------------------\n\n\n\n" << endl;
		cout << "------------------------------------------------ Used List ------------------------------------------------" << endl;
		output_list(used_list);
		cout << "------------------------------------------------ Used List Ends ------------------------------------------------\n\n\n\n" << endl;
	}
	cout << "\n********************************SIMULATION ENDS********************************" << endl;

	return 0;
}

(2)best fit

#include<iostream>
#include<cstdint>
#include<vector>
#include<map>
#include<chrono>
#include<random>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<string>
using namespace std;

const size_t memory_capacity = 1ull << 33;
const size_t page_count = memory_capacity / 4096; //Page size = 4 KB
const size_t bound[] = { 1ull, 1ull << 8, 1ull << 18, 1ull << 28, 1ull << 38, 1ull << 48 };
const size_t page_to_byte = 1ull << 12;
const char* const unit[] = { " KB ", " MB ", " GB ", " TB ", " PB ", " EB " };
const size_t exp_count = 1024;

multimap<size_t, size_t> free_list; //The first is page count and the second is starting address.
map<size_t, size_t> used_list; //The first is starting address and the second is page count.
const pair<size_t, size_t> entire_memory = { page_count - 1, 1 }; string output_str;

vector<size_t> free_seq;
uniform_int_distribution<size_t> us(1, page_count - 1), upe(2, UINT64_MAX); mt19937_64 re; //Random engine.

//Modify the node of the free list and the used list according to the random situation of allocation.
inline size_t split_node_n_mark_used(const multimap<size_t, size_t>::iterator& i, size_t _PageCount) {
	static pair<size_t, size_t> p, q, r;
	p = { us(re) % (i->first - _PageCount) + i->second, _PageCount };
	if (p.first == i->second) {
		q = { i->first - _PageCount, i->second + _PageCount }; free_list.erase(i); free_list.emplace(q);
	}
	else if (p.first + p.second == i->first + i->second) {
		q = { i->first - _PageCount, i->second }; free_list.erase(i); free_list.emplace(q);
	}
	else {
		q = { p.first - i->second, i->second }; r = { i->first - _PageCount - q.first, p.first + p.second };
		free_list.erase(i); free_list.emplace(q); free_list.emplace(r);
	}
	used_list.emplace(p); return p.first;
}

//Allocate a segment of free memory.
inline size_t alloc(size_t _PageCount) {
	static multimap<size_t, size_t>::iterator i; static pair<size_t, size_t> p;
	i = free_list.lower_bound(_PageCount);
	if (i == free_list.end())return 0; //Allocation failed.
	if (i->first == _PageCount) {
		p.first = i->second; p.second = i->first; free_list.erase(i); used_list.emplace(p); return p.first;
	}
	else { return split_node_n_mark_used(i, _PageCount); } //i->first > _PageCount
}

//Release a segment of used memory.
inline void release(size_t _PPN) {
	static map<size_t, size_t>::iterator h; static multimap<size_t, size_t>::iterator i;
	static pair<size_t, size_t> p;
	h = used_list.find(_PPN); p = { h->second, h->first };
	for (i = free_list.begin(); i != free_list.end();) {
		//merge with the node which indicates the left piece of free space.
		if (i->first + i->second == h->first) { p.first += i->first; p.second = i->second; i = free_list.erase(i); }
		//merge with the node which indicates the right piece of free space.
		else if (h->first + h->second == i->second) { p.first += i->first; i = free_list.erase(i); }
		else ++i;
	}
	free_list.emplace(p); used_list.erase(h);
}

//Convert the physical page number to another notation.
inline void convert(size_t _PPN, string& _Dest, unsigned _AlignMode = 1) {
	_Dest.clear();
	while (_PPN >= (1 << 8)) {
		auto d = upper_bound(bound, bound + 5, _PPN) - bound - 1;
		_Dest.append(to_string(_PPN / bound[d])); _Dest.append(unit[d]);
		_PPN %= bound[d];
	}
	if (_PPN) { _Dest.append(to_string(_PPN * 4)); _Dest.append(" KB"); }
	else _Dest.pop_back();
	if (_AlignMode)_Dest.insert(0, 48 - _Dest.size(), ' ');
	else _Dest.resize(48, ' ');
}

inline void output_list(const multimap<size_t, size_t>& _List) {
	cout << "               Starting Position                 |                  Ending Position                 | Length (Dec)" << endl;
	for (multimap<size_t, size_t>::const_iterator i = _List.cbegin(); i != _List.cend(); ++i) {
		cout << "--------------------------------------------------------------------------------------------------------------------" << endl;
		cout << hex << setw(48) << i->second * page_to_byte << " | " << setw(48) << (i->first + i->second) * page_to_byte << " | " << dec << i->first * page_to_byte << endl;
		convert(i->second, output_str); cout << output_str << " | ";
		convert(i->first + i->second, output_str); cout << output_str << " | ";
		convert(i->first, output_str, 0); cout << output_str << endl;
	}
	cout << "--------------------------------------------------------------------------------------------------------------------\nList size = " << _List.size() << endl;
}

inline void output_list(const map<size_t, size_t>& _List) {
	cout << "               Starting Position                 |                  Ending Position                 | Length (Dec)" << endl;
	for (map<size_t, size_t>::const_iterator i = _List.cbegin(); i != _List.cend(); ++i) {
		cout << "--------------------------------------------------------------------------------------------------------------------" << endl;
		cout << hex << setw(48) << i->first * page_to_byte << " | " << setw(48) << (i->first + i->second) * page_to_byte << " | " << dec << i->second * page_to_byte << endl;
		convert(i->first, output_str); cout << output_str << " | ";
		convert(i->first + i->second, output_str); cout << output_str << " | ";
		convert(i->second, output_str, 0); cout << output_str << endl;
	}
	cout << "--------------------------------------------------------------------------------------------------------------------\nList size = " << _List.size() << endl;
}

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	re.seed(chrono::steady_clock::now().time_since_epoch().count());
	free_list.emplace(entire_memory);

	cout << "********************************SIMULATION STARTS********************************\n" << endl;
	cout << "------------------------------------------------ Free List ------------------------------------------------" << endl;
	output_list(free_list);
	cout << "------------------------------------------------ Free List Ends ------------------------------------------------\n" << endl;
	cout << "\nStart to allocate memory.\n" << endl;
	for (size_t i = 0; i < exp_count; ++i) {
		if (!alloc(log2(upe(re)) * (upe(re) % 128 + 1)))cout << "ERROR: Allocation failed." << endl;
		cout << "------------------------------------------------ Free List ------------------------------------------------" << endl;
		output_list(free_list);
		cout << "------------------------------------------------ Free List Ends ------------------------------------------------\n\n\n\n" << endl;
		cout << "------------------------------------------------ Used List ------------------------------------------------" << endl;
		output_list(used_list);
		cout << "------------------------------------------------ Used List Ends ------------------------------------------------\n\n\n\n" << endl;
	}
	for (map<size_t, size_t>::const_iterator i = used_list.cbegin(); i != used_list.cend(); ++i) { free_seq.emplace_back(i->first); }
	shuffle(free_seq.begin(), free_seq.end(), re);
	cout << "\n\n\n\n\n\nStart to free memory.\n" << endl;
	for (vector<size_t>::const_iterator i = free_seq.cbegin(); i != free_seq.cend(); ++i) {
		release(*i);
		cout << "------------------------------------------------ Free List ------------------------------------------------" << endl;
		output_list(free_list);
		cout << "------------------------------------------------ Free List Ends ------------------------------------------------\n\n\n\n" << endl;
		cout << "------------------------------------------------ Used List ------------------------------------------------" << endl;
		output_list(used_list);
		cout << "------------------------------------------------ Used List Ends ------------------------------------------------\n\n\n\n" << endl;
	}
	cout << "\n********************************SIMULATION ENDS********************************" << endl;

	return 0;
}

結果與分析

【1】first fit部分。

1、設在某嵌入式設備中,內存共16 MB。每次分配的頁數爲1到63,且請求分配更多頁數的空間具有更大的機率。嘗試分配次數1024次。
在這裏插入圖片描述
可見,程序能夠正常結束,並且在回收全部的已分配空間後,僅剩餘1段完整的空閒內存(頁0不可用,4 KB到16 MB均可用)。測試通過。
2、設在某PC上的虛擬Linux系統或雲服務器的虛擬化環境中,內存共8 GB。單次分配頁數最少爲1,最多爲第1個測試的128倍。且請求分配更多頁數的空間具有更大的機率。嘗試分配次數1024次。在這裏插入圖片描述
可見,程序能夠正常結束,並且在回收全部的已分配空間後,僅剩餘1段完整的空閒內存(頁0不可用,4 KB到8 GB均可用)。測試通過。在筆者的計算機上,累計運行時間約1小時15分鐘。

【2】best fit部分。

1、設在某嵌入式設備中,內存共16 MB。每次分配的頁數爲1到63,且請求分配更多頁數的空間具有更大的機率。嘗試分配次數1024次。
在這裏插入圖片描述
可見,程序能夠正常結束,並且在回收全部的已分配空間後,僅剩餘1段完整的空閒內存(頁0不可用,4 KB到16 MB均可用)。測試通過。
2、設在某PC上的虛擬Linux系統或雲服務器的虛擬化環境中,內存共8 GB。單次分配頁數最少爲1,最多爲第1個測試的128倍。且請求分配更多頁數的空間具有更大的機率。嘗試分配次數1024次。
在這裏插入圖片描述
可見,程序能夠正常結束,並且在回收全部的已分配空間後,僅剩餘1段完整的空閒內存(頁0不可用,4 KB到16 MB均可用)。測試通過。

總結:在上述全部測試中,程序運行正常,均能正確模擬內存的分配和釋放的過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章