/* * Get physical address of first (actually last :-) free page, and mark it * used. If no free pages left, return 0. * process steps : (mainly meanings , Refrence: * https://topic.alibabacloud.com/tc/a/linux-011-kernel-memory-management-get_free_page--function-analysis_1_16_30220847.html) * 0. mem map = memory map * 1. find where are free pages * 2. set it's mem map to 1 , and make it empty ,and return it . * */ unsignedlongget_free_page(void) { registerunsignedlong __res asm("ax");
/* * Free a page of memory at physical address 'addr'. Used by * 'free_page_tables()' */ voidfree_page(unsignedlong addr) 、、 { if (addr < LOW_MEM) return; if (addr >= HIGH_MEMORY) panic("trying to free nonexistent page"); addr -= LOW_MEM; addr >>= 12; // addr = addr >> 12 . means divide 2^12 = 4K for calculating // the number of page . if (mem_map[addr]--) return; // if (a--) first condite a then -- ; // bug if the mem_map bigger than 1 (e.g == 3 ) ,is it meaningful ? // mem_map[addr]>=2 : page is shared // mem_map[addr]>=1 : page is used mem_map[addr]=0; panic("trying to free free page"); //error }
/* * This function frees a continuos block of page tables, as needed * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks. * 函数功能: 释放一块连续的物理内存 * process: 工作过程 * 1. begin addr + size (only aligned to 4M ,so begin address could be 0 4M 8M 12M ) * 2. confirm dir : need dir location ; * which page table : need page table location . * if LINEAR ADDRESS , only need it's DIR offset address. * 3. free each FRAME PAGE that relative to page table entries . * from : 线性地址 * size : 长度(页表个数) * * 总结: 从一个入参线性地址from 确定对应的页目录项, 页表 . 根据size大小确定要释放的范围. * 从页目录到页表 逐级遍历释放掉PAGE FRAME , 最后释放掉页表 * 再刷新缓存区 * 完毕. */ intfree_page_tables(unsignedlong from,unsignedlong size) { unsignedlong *pg_table; unsignedlong * dir, nr;
if (from & 0x3fffff) /* 3fffff = 4M . 计算过程: 3fffff/ffffff = 1/4 ; 0xffffff = 2^24 = 16M ; 0xffffff * 1/4 = 4M & 位与; && 逻辑与 只有当from 是4M 或4M的倍数的时候,条件才为假 . 学习表达式. if (from & 0xff) 表示判断某个数 from 是否在 (0xff+0x1)的边界上,如果条件为否,表示在边界上.是说明不在边界上. linus 编程也太牛了.*/ /* The expression from & 0x3fffff checks if the least significant 22 bits of the from variable are non-zero. If the result is non-zero, the if condition evaluates to true. Therefore, the if statement will be false when from has a value that has all 22 least significant bits set to zero. In other words, if from is a multiple of 4M (0x400000). */ panic("free_page_tables called with wrong alignment"); if (!from) // 判断是否为地址零,条件否,from非零;条件是,from为零,死机 panic("Trying to free up swapper memory space"); size = (size + 0x3fffff) >> 22;/* 计算size是有多少个页表,避免最后结果为零,+4M(0x3fffff) 左移多少位代表什么含义?表示除2^22的大小 . 2^22 = 4M . 除4M . 计算得到多少页表个数. 一个页表能包含的额内存范围是4M 所以在 linux 0.11 中 ,主物理内存大小是16M ,所以用4个页表表示足够了. */ dir = (unsignedlong *) ((from>>20) & 0xffc); /* _pg_ dir = 0 ; dir指的是目录项, 计算的应该是从哪个页目录项*/ /* 一个线性地址32bit; DIR(31-22)|PAGE(21-12)|OFFSET(11-0)
回忆一下页目录项的地址: 分别是0x0000 ; 0x0004 ; 0x0008 ;0x0012
from 从上面的条件判断只能是4M或4M的倍数. 所以得到的值会是4;8;12
0xffc:The purpose of this operation is to mask the lower bits and ensure that the resulting value is aligned to a 4KB boundary (the size of a page directory entry). 注意这里的mask. 0xffc = 1111 1111 1100 . 所以这里是为了mask掉最后 2bits.
/* * Well, here is one of the most complicated functions in mm. It * copies a range of linerar addresses by copying only the pages. * Let's hope this is bug-free, 'cause this one I don't want to debug :-) * * Note! We don't copy just any chunks of memory - addresses have to * be divisible by 4Mb (one page-directory entry), as this makes the * function easier. It's used only by fork anyway. * * NOTE 2!! When from==0 we are copying kernel space for the first * fork(). Then we DONT want to copy a full page-directory entry, as * that would lead to some serious memory waste - we just copy the * first 160 pages - 640kB. Even that is more than we need, but it * doesn't take any more memory - we don't copy-on-write in the low * 1 Mb-range, so the pages can be shared with the kernel. Thus the * special case for nr=xxxx. * 这里注意: 最后是将from_page_table复制给to_page_table ,并且两者指向的是同一块pageframe,复制的不是page frame . 函数名称也是copy_page_tables 不是copy_pages。
/* * This routine handles present pages, when users try to write * to a shared page. It is done by copying the page to a new address * and decrementing the shared-page counter for the old page. * * If it's in code space we exit with a segment error. */
// The page_fault() call this function // 这个函数还是取消写保护. 因为page fault调用的时候,是因为程序想要写入共享页,但是共享页只读. voiddo_wp_page(unsignedlong error_code,unsignedlong address) { #if 0 /* we cannot do this yet: the estdio library writes to code space */ /* stupid, stupid. I really want the libc.a from GNU */ if (CODE_SPACE(address)) do_exit(SIGSEGV); #endif un_wp_page((unsignedlong *) (((address>>10) & 0xffc) + (0xfffff000 & //((address>>10) & 0xffc) 保留 DIR|PAGE . 表示某个页表项的偏移地址 *((unsignedlong *) ((address>>20) &0xffc)))));/* S1: ((address>>20) &0xffc) 保留 DIR . S2: *DIR 取地址以后 就是某个page table . S3: 0xfffff000 & page table获得page table所存的地址,这个地址指向某个PAGE FRAME
/* * share_page() tries to find a process that could share a page with * the current one. Address is the address of the wanted page relative * to the current data space. * * We first check if it is at all feasible by checking executable->i_count. * It should be >1 if there are other tasks sharing this inode. */ staticintshare_page(unsignedlong address) { structtask_struct ** p;
if (!current->executable) return0; if (current->executable->i_count < 2) // ? return0; for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { if (!*p) continue; if (current == *p) continue; if ((*p)->executable != current->executable) continue; if (try_to_share(address,*p)) // address 期望共享的页面地址;p某一个进程 return1; } return0; }
/* * try_to_share() checks the page at address "address" in the task "p", * to see if it exists, and if it is clean. If so, share it with the current * task. * * NOTE! This assumes we have checked that p != current, and that they * share the same executable. */ staticinttry_to_share(unsignedlong address, struct task_struct * p) { unsignedlong from; unsignedlong to; unsignedlong from_page; unsignedlong to_page; unsignedlong phys_addr;
from_page = to_page = ((address>>20) & 0xffc); // DIR from_page += ((p->start_code>>20) & 0xffc); // 加上相对p进程的DIR to_page += ((current->start_code>>20) & 0xffc);// 加上相对current进程的DIR /* is there a page-directory at from? */ from = *(unsignedlong *) from_page; //from(page table entry) if (!(from & 1)) return0; from &= 0xfffff000; // 去掉属性 from_page = from + ((address>>10) & 0xffc); //得到相对进程p的page_table_entry phys_addr = *(unsignedlong *) from_page; /* is the page clean and present? */ if ((phys_addr & 0x41) != 0x01) //0x41 对应表项中的Dirty和Present标志 return0; phys_addr &= 0xfffff000; if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM) return0; to = *(unsignedlong *) to_page; if (!(to & 1)) { if ((to = get_free_page())) *(unsignedlong *) to_page = to | 7; else oom(); } to &= 0xfffff000; to_page = to + ((address>>10) & 0xffc);//得到相对进程current的page_table_entry if (1 & *(unsignedlong *) to_page) panic("try_to_share: to_page already exists"); /* share them: write-protect */ *(unsignedlong *) from_page &= ~2; // 设置写保护 // 共享,to_page 和from_page指向相同的物理页地址 *(unsignedlong *) to_page = *(unsignedlong *) from_page; invalidate(); phys_addr -= LOW_MEM; phys_addr >>= 12; mem_map[phys_addr]++; // 共享标记 return1; }
address &= 0xfffff000; tmp = address - current->start_code; // 进程线性地址空间对应偏移地址 . 从进程的start_code开始计算 if (!current->executable || tmp >= current->end_data) { get_empty_page(address); // no_page的处理方式1 , 获取一个free page 并且与address 建立映射. return; } if (share_page(tmp)) // no_page 的处理方式2 return; if (!(page = get_free_page())) oom(); /* remember that 1 block is used for header */ block = 1 + tmp/BLOCK_SIZE; for (i=0 ; i<4 ; block++,i++) nr[i] = bmap(current->executable,block); bread_page(page,current->executable->i_dev,nr); i = tmp + 4096 - current->end_data; tmp = page + 4096; while (i-- > 0) { tmp--; *(char *)tmp = 0; } if (put_page(share_page,address)) return; free_page(page); oom(); }
get_empty_page & put_page
get_empty_page
此函数让一个线性地址与空闲页建立映射,这个线性地址存在一个空页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/// @brief get free page and map to address // the difference between get_empty_page and get_free_page ? // get_free_page : not relative to linear addres // get_empty_page : map free page and linear address. this page called empty page. /// @param address : if successed , param address map to empty page . voidget_empty_page(unsignedlong address) { unsignedlong tmp;
if (!(tmp=get_free_page()) || !put_page(tmp,address)) { free_page(tmp); /* 0 is ok - ignored */ oom(); } }
/* * This function puts a page in memory at the wanted address. * It returns the physical address of the page gotten, 0 if * out of memory (either when trying to access page-table or * page.) * * 入参: page(4K)-- > PAGE FRAME * 入参: address linear address * put page to linear address * * 总结:实际上这个函数表达的意思是, 用入参address得到的page基地址 映射到 入参page基地址 * 也就是变更了线性地址中的某个页表所指向的PAGEFRAME * * * 有几个问题 1. page_table 是数组吗? 如果不是 怎么可以page_table[] 进行操作 ,如果是,unsigned long page_tables 不是定义数组 * 答 : 数组的本质是指针 , 所以指针可以用数组表示,他们可以互相表示. * 2. page_table是局部变量 ,这样的映射是否有效,在函数之外? * 答 : 其实实际上操作的是内存中的值, 所以在函数之外是有效的. 对dir;page_table;pageframe操作,这些都是在内存中. * */ unsignedlongput_page(unsignedlong page,unsignedlong address) { unsignedlong tmp, *page_table;
/* NOTE !!! This uses the fact that _pg_dir=0 */
if (page < LOW_MEM || page >= HIGH_MEMORY) // page variable is page table or page entry ? page pointer ? page printk("Trying to put page %p at %p\n",page,address); if (mem_map[(page-LOW_MEM)>>12] != 1) printk("mem_map disagrees with %p at %p\n",page,address); page_table = (unsignedlong *) ((address>>20) & 0xffc); // 目录项偏移地址, 前面变量名应该为dir才对 // (address>>20) & 0xffc: extract DIR(10bits) of LINER ADDRESS // 0xffc = 1111 1111 1100 . 所以这里是为了mask掉最后 2bits. if ((*page_table)&1) //指向pagetable的DIR 是否有效 page_table = (unsignedlong *) (0xfffff000 & *page_table); // 接上面 (unsigned long *) (0xfffff000 & *dir) // *dir 等价于page_table // 0xfffff000 & page_table 只保留page table 中的frame page address,也就是指向某个page frame else { // 如果无效 if (!(tmp=get_free_page())) return0; *page_table = tmp|7; page_table = (unsignedlong *) tmp; } page_table[(address>>12) & 0x3ff] = page | 7; /* address >> 12 保留DIR|PAGE| ; OFFSET 被移出 0x3ff = 0011 1111 1111 (address>>12) & 0x3ff : 保留10bits , 其他mask , get PAGE(10bits) page_table[(address>>12) & 0x3ff] 等价于 page_table[PAGE] , 等价于page_table的基地址+偏移地址PAGE 可以计算得到一个page frame的起始地址 总结:实际上这个函数表达的意思是, 用入参address得到的page基地址 映射到 入参page基地址 也就是变更了线性地址中的某个页表所指向的PAGEFRAME */ /* no need for invalidate */ return page; }