在创建进程的时候会调用到alloc_pidmap来分配一个空闲的pid。
函数入参是pid_namespace结构体,介绍下该结构体的两个成员:
int last_pid 上一个进程所使用的pid;
struct pidmap pidmap[PIDMAP_ENTRIES] pid位图数组,pidmap有两个成员:nr_free,用来表征当前位图中的空闲pid个数;page指针,存储一页大小的内存的虚拟地址,也就是pid位图,一页有32768位,每一位代表一个pid,置1表示该位表征的pid已被使用。
来看具体的实现流程:
1 pid = last + 1; 2 if (pid >= pid_max) 3 pid = RESERVED_PIDS; 4 5 /* 32位体系结构上,pid位图是一个物理页,但是pid的值可以超过一页含有的位数32768, 6 获取新进程pid在页内偏移 7 */ 8 offset = pid & BITS_PER_PAGE_MASK; 9 /* pid/BITS_PER_PAGE 表征处于哪一页,每个页都有一个与之对应的pidmap结构体 */ 10 map = &pid_ns->pidmap[pid/BITS_PER_PAGE];
首先是last pid加1作为新进程的pid,如果pid大于等于pid_max,就把pid置为RESERVED_PIDS(300),从RESERVED_PIDS开始查找空闲pid,然后获取页内偏移和该pid所对应的pidmap结构体,假设pid_max为65536,那么pidmap[0]表征pid 0~32767,pidmap[1]表征pid 32768~65535。
1 max_scan = DIV_ROUND_UP(pid_max, BITS_PER_PAGE) - !offset;
接下来会通过一个for循环来遍历位图,max_scan表明遍历次数,如果offset为0,即从0开始查找空闲pid,max_scan为0,那么只需要遍历一次;如果offset不为0,max_scan为1,需要遍历二次。
1 /* max_scan表征的查找次数只适用于从offset开始查找位图没有找到空闲pid的情况, 2 因为一旦查找到了就直接返回pid了,不会再开始下一次查找; 3 4 主要是担心offset如果不为0,即使从offset开始直到位图末没有查找到空闲pid,但 5 是offset之前的位图中仍然可能有空闲的pid,于是将offset置为RESERVED_PIDS,重 6 新从最初的位图开始查找,或者置offset为0,从下一张开始查找 7 8 如果offset为0,max_scan就为1,从头开始查找,如果没有找到合适的pid,就说明的确 9 是没有空闲的pid,就不必再继续遍历,max_scan为0即可 10 */ 11 for (i = 0; i <= max_scan; ++i) 12 { 13 /* 分配新页作为pid位图 14 SOLVE_ME:为什么不用alloc_pages 15 */ 16 if (unlikely(!map->page)) 17 { 18 void *page = kzalloc(PAGE_SIZE, GFP_KERNEL); 19 /* 20 * Free the page if someone raced with us 21 * installing it: 22 */ 23 spin_lock_irq(&pidmap_lock); 24 if (!map->page) 25 { 26 map->page = page; 27 page = NULL; 28 } 29 spin_unlock_irq(&pidmap_lock); 30 /* 如果map->page是NULL,page赋值为NULL,接下来free(NULL); 31 如果map->page不是NULL,就不要再分配新页,把新分配的页free掉 32 */ 33 kfree(page); 34 if (unlikely(!map->page)) 35 break; 36 } 37 /* 如果该位图内还有未分配的pid */ 38 if (likely(atomic_read(&map->nr_free))) 39 { 40 do 41 { 42 /* 查看位图中由偏移offset指定的位是否置位,没有就把它置位,返回原 43 先的值,返回0说明找到了未被使用的pid 44 */ 45 if (!test_and_set_bit(offset, map->page)) 46 { 47 /* 空闲pid个数减1 */ 48 atomic_dec(&map->nr_free); 49 50 /* 将pid置为pid_ns->last_pid */ 51 set_last_pid(pid_ns, last, pid); 52 return pid; 53 } 54 /* 如果offset指定的位已被占用,就从offset开始在map->page中找到 55 第一个为0的位,就是下一个pid,返回值offset是距离地址map起第一个 56 为0的位,offset如果大于BITS_PER_PAGE说明已经不属于该pidmap了 57 */ 58 offset = find_next_offset(map, offset); 59 /* 根据offset得到实际pid值 */ 60 pid = mk_pid(pid_ns, map, offset); 61 62 } while (offset < BITS_PER_PAGE && pid < pid_max); 63 } 64 65 /* 66 在以上的代码中,比较理想的情况是找到了合适的pid,然后return,但是还有一 67 些其他的情况,比如从某个offset开始直到该页结束的位都被用光了,那么根据 68 find_next_offset查找到的offset就会超过BITS_PER_PAGE,此时假如还有多余的 69 位图,offset置0,map指向下一个pidmap,从下一页继续查找即可;假如pid已使用到 70 最后一张位图,就将map设为pidmap[0],从第一张位图开始继续查找,只不过要从 71 offset 300开始查找,前提是max_scan为1,那么max_scan是否有可能不为1呢? 72 73 如果最初查找空闲pid时获得的offset为0,只有一种情况,就是在内核启动过程中 74 刚刚开始创建进程,即从offset为0开始查找空闲pid是肯定能查找到的,因而如果 75 查找不到pid,最初的offset肯定不为0,即max_scan肯定为1,肯定还可以再执行一 76 个for循环查找一次 77 */ 78 if (map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) 79 { 80 ++map; 81 offset = 0; 82 } 83 else 84 { 85 map = &pid_ns->pidmap[0]; 86 offset = RESERVED_PIDS; 87 if (unlikely(last == offset)) 88 break; 89 } 90 pid = mk_pid(pid_ns, map, offset); 91 }
为pid位图分配一页大小的内存;通过nr_free判断将要查找的pidmap中是否还有空闲的pid,开始一个do...while循环来遍历该位图查找pid,调用test_and_set_bit判断offset所指定的位图中的位是否为0(test_and_set_bit返回0),如果是表明该位表征的pid空闲,即已经找到了空闲pid,nr_free减1,将该pid设置了last_pid之后返回。
如果offset指定的pid已被占用,就调用find_next_offset从offset开始找到第一个为0的位,返回值是该位距离位图首地址的偏移,如果找到的空闲位的位置没有超过BITS_PER_PAGE,表示仍属于该pidmap并且由空闲位得到的pid没有超过pid_max,那么该位就是所要找的pid,返回。
存在三种情况会跳出该do...while循环:
1. offset > BITS_PER_PAGE ,pid < pid_max
查找到的空闲位不属于该pidmap,但是没有超过pid_max,说明当前位图的pid已全部被占用,那么就从下一个位图的偏移0处开始继续查找;
2. offset < BITS_PER_PAGE ,pid > pid_max
查找到的空闲位属于该pidmap,但是超过pid_max,就从第一个pidmap[0]的RESERVED_PIDS开始查找,发生在pid_max不是BITS_PER_PAGE的整数倍的情况;
3. offset > BITS_PER_PAGE ,pid > pid_max
查找到的空闲位属于该pidmap,但是超过pid_max,就从第一个pidmap[0]的RESERVED_PIDS开始查找,发生在pid_max是BITS_PER_PAGE的整数倍的情况;
(其他的可以看函数注释)