一,关于PV操作
p操作:当一个进程对对信号量mutex执行p操作时,执行两个动作:
mutex.valu–; //申请一个资源
if (mutex.value<0) //申请失败 sleep(); //本进程进入该信号量等待队列睡眠
v操作:当一个进程对对信号量mutex执行v操作时,执行两个动作:
mutex.value++; //释放一个资源
if (mutex.value>=0) //如果有进程在等待使用本进程 wakeup(); //从该信号量的等待队列中唤醒一个进程当信号量mutex.value小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目.mutex.value大于0时表示可用的临界资源数。
PV操作实现的功能:
实现进程之间的互斥,实现进程之间的同步
区别:互斥是为了保证临界区一次只能由一个进程使用;而同步是为了实现进程通信,即传递资源当前的态是否适合一个进程使用。实例:
1. 互斥:进出教室问题:有一个变量count,初值为0,一个学生进入教室则count++,出教室则count–。
过程:一个学生进入教室执行IN,p操作,mutex.value = 0;假设在进行count++之前遇到了中断,而中断之后跳回来时正好这个学生又在出教室,那么这时候就会执行OUT,mutex.value = -1,该OUT进程进入睡眠,返回IN进程,count = 1,v操作,mutex.value = 0(说明有等待使用count的进程);唤醒OUT进程,count = 0,v操作,mutex.value = 1。
注意上面划线部分的假设。PV操作在这就是为了保证这种竞争情况的发生。
mutex = 1;count = 0;IN() { p(mutex); count++; v(mutex);}OUT() { p(mutex); count--; v(mutex);}
2. 同步:桌上有一空盘,允许存放一只水果。爸爸可向盘中放苹果,也可向盘中放桔子,儿子专等吃盘中的桔子,女儿专等吃盘中的苹果。规定当盘空时一次只能放一只水果供吃者取用,请用P、V原语实现爸爸、儿子、女儿三个并发进程的同步。
分析:在本题中,爸爸、儿子、女儿共用一个盘子,盘中一次只能放一个水果。当盘子为空时,爸爸可将一个水果放入果盘中。若放入果盘中的是桔子,则允许儿子吃,女儿必须等待;若放入果盘中的是苹果,则允许女儿吃,儿子必须等待。本题实际上是生产者-消费者问题的一种变形。这里,生产者放入缓冲区的产品有两类,消费者也有两类,每类消费者只消费其中固定的一类产品。
解:在本题中,应设置三个信号量S、So、Sa,信号量S表示盘子是否为空,其初值为l;信号量So表示盘中是否有桔子,其初值为0;信号量Sa表示盘中是否有苹果,其初值为0。
int S=1;int Sa=0;int So=0;main() { father(); /*父亲进程*/ son(); /*儿子进程*/ daughter(); /*女儿进程*/}father() { P(S); 将水果放入盘中; if(放入的是桔子)V(So); else V(Sa);}son() { P(So); 从盘中取出桔子; V(S); 吃桔子;}daughter() { P(Sa); 从盘中取出苹果; V(S); 吃苹果;}
#include#include #include using namespace std;#define MAXN 20struct ThreadInfo { int num; bool type; double delay, time;} thread_info[MAXN];HANDLE Mutex;HANDLE thread[MAXN];int readcount;double totaltime;void W_P(int Process_num) { //临界区 printf("Thread %d 开始写入\n", Process_num); Sleep((DWORD) thread_info[Process_num].time * 1000); printf("Thread %d 写入完成\n", Process_num);}void R_P(int Process_num) { //临界区 printf("Thread %d 开始读入\n", Process_num); Sleep((DWORD) thread_info[Process_num].time * 1000); printf("Thread %d 读入完成\n", Process_num);}DWORD WINAPI reader(LPVOID lpVoid) { int Process_num = *(int*) lpVoid; //用来检测reader事件的信号状态 Sleep((DWORD) thread_info[Process_num].delay * 1000); printf("Thread %d 请求读\n", Process_num); readcount++; if (readcount == 1) //之前没有读者,P操作占用资源 WaitForSingleObject(Mutex, INFINITE); R_P(Process_num); //之前有读者,不必P操作 readcount--; if (readcount == 0) //读者全部执行结束,V操作释放资源 ReleaseSemaphore(Mutex, 1, NULL); //读者优先,只有没有读者的时候才可以写 return Process_num;}DWORD WINAPI writer(LPVOID lpVoid) { int Process_num = *(int*) lpVoid; Sleep((DWORD) thread_info[Process_num].delay * 1000); printf("Thread %d 请求写\n", Process_num); WaitForSingleObject(Mutex, INFINITE); W_P(Process_num); ReleaseSemaphore(Mutex, 1, NULL); return Process_num;}int main() { freopen("data_tmp.txt", "r", stdin); Mutex = CreateSemaphore(NULL, 1, 1, NULL); //增加一个信号量,初始值为1,最大值为1 readcount = 0; totaltime = 0; char type[10]; int num = -1; while (~scanf("%s", type)) { num++; thread_info[num].num = num; if (strcmp(type, "write") == 0) thread_info[num].type = 0; else thread_info[num].type = 1; scanf("%lf%lf", &thread_info[num].delay, &thread_info[num].time); totaltime += thread_info[num].time; if (thread_info[num].type == 0) { printf("创建写进程 %d\n", thread_info[num].num); thread[num] = CreateThread(NULL, 0, writer, &thread_info[num].num, 0, 0); //创建线程执行写者函数,会与程序并发执行 //多个线程之间如果公用了一些资源的话,我们并不能保证这些资源都能正确地被利用,因为这个时候资源并不是独占的 //需要PV操作進行約束 } else { printf("创建读进程 %d\n", thread_info[num].num); thread[num] = CreateThread(NULL, 0, reader, &thread_info[num].num, 0, 0); } } Sleep((DWORD) 20 * 1000); return 1;}/*write 1 8read 0 4read 3 5write 3 5 *//*创建写进程 0创建读进程 1创建读进程 2创建写进程 3Thread 1 请求读Thread 1 开始读入Thread 0 请求写Thread 3 请求写Thread 2 请求读Thread 2 开始读入Thread 1 读入完成Thread 2 读入完成Thread 0 开始写入Thread 0 写入完成Thread 3 开始写入 */
二,磁盘空间管理
内存池实现:
mem_pool -> mem_block -> mem_node
空白盘区链:通过维护free链表实现对内存碎片的整理
通过mem_block实现空白盘区目录功能
#include#include #include #include using namespace std;#define BUF_SIZE 100#define BASE_COUNT 10000#define STEP_COUNT 1000typedef union _mem_node { union _mem_node *next; char buf[BUF_SIZE];} mem_node_t, *pmem_node_t;typedef struct _mem_block { mem_node_t *node_head; mem_node_t *node_tail; int node_cnt; /* node count */ struct _mem_block *next;} mem_block_t, *pmem_block_t;typedef struct _mem_pool { mem_block_t *block_head; mem_block_t *block_tail; mem_node_t *free_head; int block_cnt; /* block count */ int free_cnt; /* free node count; */ int base; int step;} mem_pool_t, *pmem_pool_t;static mem_pool_t mem_pool;/*************************************/static int mem_block_init(int cnt, mem_block_t *block) { int size; mem_node_t *p; if (block == NULL) { return 0; } size = cnt * sizeof(mem_node_t); if ((p = (mem_node_t *) malloc(size)) == NULL) { fprintf(stderr, "mem_pool_init::malloc error/n"); return 0; } memset(p, 0, size); memset(block, 0, sizeof(mem_block_t)); block->node_cnt = cnt; block->node_head = p; block->node_tail = p + cnt - 1; for (p = block->node_head; p < block->node_tail; p++) p->next = (p + 1); p->next = NULL; return 1;}static int add_mem_block(int cnt) { mem_block_t *block; if ((block = (mem_block_t *) malloc(sizeof(mem_block_t))) == NULL) { fprintf(stderr, "add_mem_block::malloc error/n"); return 0; } memset(block, 0, sizeof(mem_block_t)); if (!mem_block_init(cnt, block)) { fprintf(stderr, "mem_pool_init::mem_block_init error/n"); return 0; } block->next = mem_pool.block_head; mem_pool.block_head = block; if (mem_pool.block_tail == NULL) { mem_pool.block_tail = block; } block->node_tail->next = mem_pool.free_head; mem_pool.free_head = block->node_head; mem_pool.free_cnt += cnt; mem_pool.block_cnt++; return 1;}int mem_pool_init() { memset(&mem_pool, 0, sizeof(mem_pool)); mem_pool.base = BASE_COUNT; mem_pool.step = STEP_COUNT; if (!add_mem_block(BASE_COUNT)) { fprintf(stderr, "mem_pool_init::add_mem_block error/n"); return 0; } return 1;}void mem_pool_destroy(void) { mem_block_t *prev, *cur; prev = NULL; cur = mem_pool.block_head; while (prev != NULL) { prev = cur; cur = cur->next; free(cur->node_head); free(prev); } memset(&mem_pool, 0, sizeof(mem_pool_t));}/*************************************/mem_node_t* mem_alloc() { //从free删除一个内存,返回这个分配地址 mem_node_t *p; if (mem_pool.free_head == NULL) { if (!add_mem_block(mem_pool.step)) { return NULL; } } p = mem_pool.free_head; mem_pool.free_head = p->next; mem_pool.free_cnt--; return p;}void mem_free(void *ptr) { //将ptr移到free表 if (ptr == NULL) { return; } ((mem_node_t *) ptr)->next = mem_pool.free_head; mem_pool.free_head = (mem_node_t *) ptr; mem_pool.free_cnt++;}#define ALLOC_COUNT 10void alloc_test(char *ptr[]) { int i, j; for (i = 0; i < ALLOC_COUNT; i++) { if ((ptr[i] = (char*) mem_alloc()) == NULL) { fprintf(stderr, "mem_alloc error/n"); return; } for (j = 0; j < ALLOC_COUNT; j++) { ptr[i][j] = 'a' + j; } } for (i = 0; i < ALLOC_COUNT; i++) { for (j = 0; j < ALLOC_COUNT; j++) { printf("ptr[%d][%d]=%c ", i, j, ptr[i][j]); } }}int main() { char *ptr[ALLOC_COUNT]; if (!mem_pool_init()) { fprintf(stderr, "mem_pool_init error/n"); return 1; } alloc_test(ptr); mem_free(ptr[5]); mem_pool_destroy(); return 0;}
三,windows下文件操作
输出路径下所有文件目录树,递归创建文件目录
#include#include #include #include
四,window内存管理
内存管理的优劣会直接影响到系统本身的性能,所以操作系统管理内存的方法一般要根据处理器的硬件情况来决定。Windows的主要硬件体系结构就是Intel的架构,所以在内存管理上就受很多的x86架构的影响。另外为了能让多个进程之间不互相干扰破坏,并与操作系统隔离,Windows在处理器寻址的基础上,又应用了大量的内存管理技术来满足各种要求。在所有进程对于内存的要求已经超过了实际物理内存的时候,Windows还必须要平衡各种需求,借助如硬盘的外部存储,使计算机能够正常运行。由此可见,内存管理是所有操作系统非常重要的一部分。
首先,在x86体系中,内存地址有三类:
1 物理地址(直接对应物理内存的地址,一般为32位或36位的无符号整数)
2 虚拟地址(也称作线性地址,处理器使用前需要转换为物理地址,为32位无符号整数,因此地址空间范围为4GB)
3 逻辑地址(学过8086汇编的人都知道,逻辑地址分为两个部分:段地址和偏移地址,这里也是一样的)
然后再介绍一下两种比较主流的内存管理方式:页式内存管理,段式内存管理。
1 页式内存管理
如果直接让进程使用物理地址来访问内存将会使进程的动态内存分配难以有效实施,因为内存单元和进程紧密的联系在了一起,从而使内存的回收和再分配受限于特定的进程和物理地址。一种简单的思路就是让进城使用虚拟地址,在虚拟地址和物理地址之间建立一个映射表来完成转译。
页式内存管理中,虚拟地址空间是按照页来管理的,对应的物理内存同样是按照页来管理,二者的页大小是相同的。而因为这样的一个转译关系存在,在虚拟地址中连续的页面在物理地址中可以不连续。通过维护这样的一个转译关系,物理页面可以被动态的分配给虚拟页面,这样可以做到当虚拟页面真正被使用的时候才为其分配物理页面,这样的好处就是能够节省物理内存,使用效率更高。
另外需要注意的是,在一个系统中,物理地址空间只有一个,而虚拟内存空间可以有多个。而且每个虚拟内存空间都必须要维护一个映射关系,这样每个进程的地址空间都是独立的。进程A地址为0x00FF0000对应的物理地址和进程B的0x00FF0000地址对应的物理地址就不相同。另外,每个虚拟地址空间实际映射的物理页面很少。而物理页面一般只被映射到一个虚拟地址空间中。当然存在例外,比如DLL在被多个进程使用的时候,其函数所在的物理页面必然被映射到了多个进程的虚拟地址空间中。
在页面划分机制下,32位的虚拟地址被分为了两个部分:页索引+页内偏移。Intel x86下的页面大小标准为4KB,也就是2^12。因此,虚拟地址中的低12位可以用于页内偏移。前20位可以用于页索引部分,用于找到一个实际的物理页面。这样的页面映射表大小就是1M。若用线性关系表示的话,那么就需要用4M的内存,因为一个地址的大小是4Byte。不过这种方式浪费比较严重,毕竟Windows默认线程栈的大小也不过才1M。
另外在这1M的地址空间中,还有很大一部分的表项并未使用,浪费很严重。
所以Intel x86的虚拟地址解析过程如下:
32位的虚拟地址被分为了3个部分:31 - 22 页目录索引 21 - 12 页表索引 11- 0 页内偏移
1 对于一个虚拟地址,首先解析页目录索引,在也目录中查找。页目录的大小就是4KB。页目录索引的首地址位于CR3寄存器中。
2 在目录中找到后,再根据页表索引来找到页对应的物理地址。
3 最后加上页内偏移,就定位到了最终的物理地址。
可以知道,这样下来需要使用的空间达到了4KB+4MB的大小,但是这4MB的页表索引在对应页表不使用的时候完全可以不构造出来,从而节省大量的内存。
同时,因为二级页表的查找也需要付出一定性能上的代价,毕竟多了一次查找。对于这个问题,intel x86处理器缓存了地址转译信息,也就是虚拟地址到物理地址的映射关系。这样当处理器重复访问一个虚拟地址时,就不用再次进行转译了。此缓存被称为TLB,中文意思就是:地址转译快查缓冲区。
TLB是硬件级的地址查找支持电路,专门用来做这个,效率非常高。也因此,这一块是软件所无法触及的,知道优惠这么个东西就OK了。因为TLB的存在,二级查找引发的性能下降就不是很明显了。
另外当CR3寄存器的值改变的时候,TLB中的数据就全部无效了,因为这意味着进程的切换,之前的映射关系自然就不再成立了。
另外Intel x86 Pentium Pro还提供了成为PAE物理地址扩展的内存映射模式,支持36位物理地址,但虚拟地址仍旧是32位。而且PAE采用的是3级页表机制。但是基本原理一样。
段式内存管理还看得不是很清楚,看明白了再把整理篇博文发出来。
五,死锁检测
对资源的矩阵分析得到是否会发生死锁。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。死锁检测的原理:首先对每个资源每个进程的需求量由小到大排序,保证了资源会最快分配给进程。每次对各个资源进行一次分配,直到一次所有资源都没法分配给任何一个进程,如果是所有程序都得到了资源就没有发生死锁。
采用一个简单的规则,任何进程都必须在执行期间按照编号递增的顺序请求所需的资源。这样模拟看是否能会找到一个安全序列,找不到说明有可能死锁。
#include#include using namespace std;#define MAXPROCESS 50#define MAXN 100int available[MAXN];int allocation[MAXPROCESS][MAXN];int need[MAXPROCESS][MAXN];bool done[MAXPROCESS];int m, n; //m个进程,n个资源bool check() { vector path; int i, j, k; int avail_tmp[MAXN]; for (i = 0; i < n; i++) avail_tmp[i] = available[i]; memset(done, 0, sizeof(done)); while (1) { bool flag = 0; for (i = 0; i < m; i++) { if (done[i] == 1) continue; for (j = 0; j < n; j++) if (need[i][j] > avail_tmp[j]) break; if (j == n) { path.push_back(i); done[i] = 1; flag = 1; for (k = 0; k < n; k++) { avail_tmp[k] += allocation[i][k]; } } if ((int) path.size() == m) { printf("可以顺利执行"); for (i = 0; i < (int) path.size(); i++) { if (i > 0) printf(" -> "); printf("%d", path[i] + 1); } puts(""); return 1; } } if (!flag) break; } puts("简单的规则下发生死锁"); return 0;}void execute() { if (!check()) return; int i, id; int request[MAXN]; puts("输入申请资源的进程号"); scanf("%d", &id); puts("输入进程所请求的各资源数量"); for (i = 0; i < n; i++) { scanf("%d", &request[i]); if (request[i] > need[id][i]) request[i] = need[id][i]; } for (i = 0; i < n; i++) { available[i] -= request[i]; allocation[id][i] += request[i]; need[id][i] -= request[i]; } if (check()) { puts("已分配"); } else { puts("这个是可能会引起死锁的分配"); for (i = 0; i < n; i++) { available[i] += request[i]; allocation[i][id] -= request[i]; need[i][id] += request[i]; } }}int main() { freopen("data3.txt", "r", stdin); int i, j; puts("输入进程和资源的数目:"); scanf("%d%d", &m, &n); puts("输入需求资源矩阵"); for (i = 0; i < m; i++) for (j = 0; j < n; j++) scanf("%d", &need[i][j]); puts("输入已分配资源矩阵"); for (i = 0; i < m; i++) for (j = 0; j < n; j++) scanf("%d", &allocation[i][j]); puts("请输入各个资源现有的数目:"); for (i = 0; i < n; i++) { scanf("%d", &available[i]); } execute();}/*5 37 4 30 2 06 0 00 1 14 3 10 1 03 0 23 0 22 1 10 0 22 3 01 0 2 0 */
下面是当进程数很大时的检测做法,从每个资源出发检测死锁
#include#include #define INF 0x3fffffff#define MAXN 1003using namespace std;int alloc[MAXN][MAXN];int request[MAXN][MAXN];int avail[MAXN];int n, m;int order[MAXN][MAXN];int counter[MAXN];int ptr[MAXN];int cmp_id;bool cmp(int a, int b) { return request[cmp_id][a] < request[cmp_id][b];}void input(int table[MAXN][MAXN]) { for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) scanf("%d", &table[i][j]);}bool solve() { int i, j; memset(counter, 0, sizeof(counter)); memset(ptr, 0, sizeof(ptr)); while (1) { bool flag = 0; for (i = 0; i < m; i++) { while (ptr[i] < n) { int pid = order[i][ptr[i]]; if (request[i][pid] > avail[i]) break; counter[pid]++; ptr[i]++; flag = 1; if (counter[pid] == m) { //进程所有资源都已获取后释放 for (j = 0; j < m; j++) avail[j] += alloc[j][pid]; } } } if (!flag) //所有资源这一轮没有分配给任何进程 break; } for (i = 0; i < m && ptr[i] >= n; i++) //检测每个进程是否全部执行完毕 ; return (i < m) ? 0 : 1;}int main() {// freopen("data3.txt", "r", stdin); while (~scanf("%d%d", &n, &m)) { input(alloc); //输入已得资源矩阵 input(request); //输入需求资源矩阵 for (int i = 0; i < m; i++) scanf("%d", &avail[i]); //输入已有资源数量 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) order[i][j] = j; cmp_id = i; sort(order[i], order[i] + n, cmp); //按每个资源各进程的资源需求量由小到大排序 } if (solve()) puts("Yes"); else puts("No"); } return 0;}/* 3 9 46 80 95 9 63 89 27 82 41 43 58 79 10 31 71 44 4 76 93 52 75 84 86 49 78 0 80 71 8 6 68 89 31 71 69 21 68 62 26 2 43 44 21 43 99 36 92 62 8 65 73 4 72 94 74 */