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
原创粉丝点击