Linux内核使用位图为进程分配pid
来源:互联网 发布:非农历史数据一览表 编辑:程序博客网 时间:2024/04/30 03:51
Linux允许用户使用一个叫做进程标识符process ID(pid)的数来标识进程,PID存放在进程描述符的pid字段。pid的值有一个上限,举个例子:默认情况下,最大的PID号为32767。那么内核是如何管理这些pid的呢?能不能用数组?可以,不过效率不高,存在循环,比较耗CPU,而且需要空间较大;内核中使用位图来管理这些pid,由于使用内嵌的汇编语言扫描位图(跟循环有质的区别),所以效率较高,数据结构为pidmap-array,内核使用页框来存放位图,一个页框包含32768个位,所以32位体系结构中pidmap-array位图存放在一个单独的页中。然而,在64位体系结构中,可能需要为PID位图增加更多的页,pidmap及pidmap_array定义如下:
/* * nr_free为页框中空闲位的个数 * page存放页框的地址 */ typedef struct pidmap { atomic_t nr_free; void *page; } pidmap_t; /* * pidmap_array中存放的是所有pidmap位图 */ static pidmap_t pidmap_array[PIDMAP_ENTRIES] = { [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } }; /* * BITS_PER_PAGE正如字面意思,表示一个页框中位的个数 */ #define BITS_PER_PAGE (PAGE_SIZE*8) #define BITS_PER_PAGE_MASK (BITS_PER_PAGE-1)
内核使用alloc_pidmap()来为进程分配一个pid,代码如下:
int alloc_pidmap(void) { /* * last_pid为全局变量,表示上次分配的pid */ int i, offset, max_scan, pid, last = last_pid; pidmap_t *map; pid = last + 1; /* * RESERVED_PIDS为300,前300个PID是固定的,不可以分配 */ if (pid >= pid_max) pid = RESERVED_PIDS; /* * offset为pid在页框中的偏移量,因为pid可能大于一个页框所能容纳的最大位数,即BITS_PER_PAGE * 所以pid要对BITS_PER_PAGE取模,下面即是实现取模操作 */ offset = pid & BITS_PER_PAGE_MASK; /* * pid/BITS_PER_PAGE求得pid所在页面在pidmap_array中的下标 */ map = &pidmap_array[pid/BITS_PER_PAGE]; /* * max_scan为扫描次数,前面好理解一些,后面的- !offset是什么意思呢? * 扫描从pid所在页框开始,如果offset为0,则扫描该页框一次 * 如果offset非0,则需要扫描两次,第一次为offset之后的位,第二次为0 ~ offset的位 */ max_scan = (pid_max + BITS_PER_PAGE - 1)/BITS_PER_PAGE - !offset; /* for循环开始扫描 */ for (i = 0; i <= max_scan; ++i) { /* 如果页面不存在则分配页面 */ if (unlikely(!map->page)) { unsigned long page = get_zeroed_page(GFP_KERNEL); /* * Free the page if someone raced with us * installing it: */ spin_lock(&pidmap_lock); if (map->page) free_page(page); else map->page = (void *)page; spin_unlock(&pidmap_lock); if (unlikely(!map->page)) break; } /* 如果该页框空闲位数大于0 */ if (likely(atomic_read(&map->nr_free))) { do { /* 如果该offset位空闲,则返回该值 */ if (!test_and_set_bit(offset, map->page)) { atomic_dec(&map->nr_free); last_pid = pid; return pid; } /* *否则继续扫描该页框其余位,find_next_offset见下面分析 */ offset = find_next_offset(map, offset); /* * 根据页框下标和offset重新算出pid,比较容易理解 * #define mk_pid(map, off) (((map) - pidmap_array)*BITS_PER_PAGE + (off)) */ pid = mk_pid(map, offset); } while (offset < BITS_PER_PAGE && pid < pid_max && (i != max_scan || pid < last || !((last+1) & BITS_PER_PAGE_MASK))); } /* * 如果上述页框分配失败,则继续扫描剩余的页框 */ if (map < &pidmap_array[(pid_max-1)/BITS_PER_PAGE]) { ++map; offset = 0; } else { map = &pidmap_array[0]; offset = RESERVED_PIDS; if (unlikely(last == offset)) break; } pid = mk_pid(map, offset); } return -1; }
find_next_offset()函数如下:
#define find_next_offset(map, off) \ find_next_zero_bit((map)->page, BITS_PER_PAGE, off) int find_next_zero_bit(const unsigned long *addr, int size, int offset) { /* * 找出offset在页框中的地址,页框中用字来记录位,所以offset需要右移5位,即32 */ unsigned long * p = ((unsigned long *) addr) + (offset >> 5); int set = 0, bit = offset & 31, res; if (bit) { /* * 内嵌汇编语言,在p指向的字中找出第一个为0的位 */ __asm__( /* * %i为输出输入的变量,从输出开始算,这里的%0为输出set,%1为输入~(*p >> bit) * bsfl将第一个操作数中第一个为1的位数存放到第二个操作数中 */ "bsfl %1,%0\n\t" /* * 找得到则返回,找不到则把令set = 32 */ "jne 1f\n\t" "movl $32, %0\n" "1:" /* 输出部分 */ : "=r" (set) /* 输入部分,本来是要查找第一个为0的位,取反后利用bsfl找到为1的位 */ : "r" (~(*p >> bit))); /* * 如果在该字中找到空闲位,则返回,否则继续下一个字的扫描,由find_first_zero_bit进行、 */ if (set < (32 - bit)) return set + offset; set = 32 - bit; p++; } /* * find_first_zero_bit继续扫描剩下的位,也是用汇编实现,见下面分析 */ res = find_first_zero_bit (p, size - 32 * (p - (unsigned long *) addr)); return (offset + set + res); } /* * 该函数参考博文:http://blog.csdn.net/weifenghai/article/details/52794872 */ static inline int find_first_zero_bit(const unsigned long *addr, unsigned size) { int d0, d1, d2; int res; if (!size) return 0; __asm__ __volatile__( /* eax所有位置位 */ "movl $-1,%%eax\n\t" /* edx置为0 */ "xorl %%edx,%%edx\n\t" /* 在edi(addr)中搜索与eax不匹配的(即不全为1的) */ "repe; scasl\n\t" /* 没有找到则跳到1处 */ "je 1f\n\t" /* 将eax中edi为0的位置1,其余置0 */ "xorl -4(%%edi),%%eax\n\t" /* edi存放当前找到的位置 */ "subl $4,%%edi\n\t" /* bsfl指令见上文 */ "bsfl %%eax,%%edx\n" /* edi存放字节偏移位置 */ "1:\tsubl %%ebx,%%edi\n\t" /* edi左移3位,存放整体移位数 */ "shll $3,%%edi\n\t" /* 计算偏移位数 */ "addl %%edi,%%edx" /* 因为是输出,最后将edx值存赋给res,后面同一道理,c代表ecx,d为edi,a为eax */ :"=d" (res), "=&c" (d0), "=&D" (d1), "=&a" (d2) /* 输入部,"1"表示和%1相同,将((size + 31) >> 5)存放到ecx中,后同,将addr存放到edi和ebx */ :"1" ((size + 31) >> 5), "2" (addr), "b" (addr) : "memory"); return res; }
0 0
- Linux内核使用位图为进程分配pid
- linux内核-分配PID位图算法
- linux内核-分配PID位图算法
- linux 进程的pid分配策略——pid位图算法 [转]
- linux 进程的pid分配策略——pid位图算法
- linux 进程的pid分配策略——pid位图算法
- linux 进程的pid分配策略——pid位图算法
- fork 分配pid位图法
- Linux内核之pid为0和pid为1
- Linux内核编程遍历所有进程PID
- 进程创建时pid分配
- Linux内核中位图的使用
- Linux系统如何为进程分配内存
- linux内核PID管理
- linux内核PID管理
- linux内核PID管理
- linux内核pid哈希表
- Linux获取进程pid
- Linux搭建负载均衡集群,使用LVS的NAT模式
- JDBC与JDBC-2、开发
- 函数式编程
- slf4j-api、slf4j-log4j12以及log4j之间什么关系?
- 【爬虫二】爬取豆瓣音乐榜单
- Linux内核使用位图为进程分配pid
- Oulipo POJ - 3461
- C++ 并发编程的一种思维
- (转)海外基金销售新模式 机器人投顾与人工相结合
- jquery设置disabled属性的方法
- 关于Oracle数据库存储汉字所占字节数
- Cocos2d中JniHelper获取JNIENV及C++与Java间类型转换
- 最强势就业形势分析,助攻“金三银四”求职季
- 【C语言】LeetCode 18 4Sum