linux内核-分配PID位图算法:
在linux中(kernel/pid.c),提供了一种生成唯一PID的机制,用到一种位图算法。
什么是位图算法:它的特点是生成的是整数,而且是唯一的,限定在某个范围内。
内核既要分配唯一的pid还要对已经分好的pid进行跟踪,内核使用了一个大的位图,其中每个pid由一个比特标识。分配一个空闲的pid,本质上等同于找出位图中第一个值为0的比特,接下来将其置为1,释放时由1置为0.
eg:
假如最多有8个进程,我们用一个能表示8种情况的整数来标识。
0x03(00000011) 说明分配了的pid号是0,1号
0x13(00010011) 说明分配了的pid号是0,1,4号
一般应用中数值范围很大,我们考虑用一个比较大的数来表示,如果用unsigned long,4个字节,只能表示32个。如果还需要更多的进程号,我们可以申请一个连续的地址空间来表示。比如 :unsigned long bitmap[8],可以表示32*8个pid.
首先我们要知道申请的pid号在哪个范围,不同的范围用不同的地址中的数据来表示。求地址时相对于首地址的,所以要提供首地址,具体在哪个地址上的数据,要看pid号是什么。pid号对应于偏移号,即从首个bit开始算,看偏移了几位,偏移数值就是pid号。
第一个地址的偏移号时0-31,第二个地址的偏移号时32-63,第三个地址的偏移号时64-95,第四个地址的偏移号时96-127.
并且0~31 %32都得0,表示首地址,31~63 %32都得1,表示第二个地址,64~95 %32都得2,表示第三个地址,96-127 %32都得3,表示第四个地址。
得出结论:
偏移号/32所得结果时相对于首地址的偏移地址量,得到实际地址和pid了。
PID分配方法的DEMO
#include <stdio.h>
/* max pid, equal to 2^15=32768 */
#define PID_MAX_DEFAULT 0x8000
/* page size = 2^12 = 4K */
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define BITS_PER_BYTE 8
#define BITS_PER_PAGE (PAGE_SIZE * BITS_PER_BYTE)
#define BITS_PER_PAGE_MASK (BITS_PER_PAGE - 1)
typedef struct pidmap
{
unsigned int nr_free;
char page[PID_MAX_DEFAULT];
} pidmap_t;
static pidmap_t pidmap = { PID_MAX_DEFAULT, {'0'} };
static int last_pid = -1;
static int test_and_set_bit(int offset, void *addr)
{
unsigned long mask = 1UL << (offset & (sizeof(unsigned long) * BITS_PER_BYTE - 1));
unsigned long *p = ((unsigned long*)addr) + (offset >> (sizeof(unsigned long) + 1));
unsigned long old = *p;
*p = old | mask;
return (old & mask) != 0;
}
static void clear_bit(int offset, void *addr)
{
unsigned long mask = 1UL << (offset & (sizeof(unsigned long) * BITS_PER_BYTE - 1));
unsigned long *p = ((unsigned long*)addr) + (offset >> (sizeof(unsigned long) + 1));
unsigned long old = *p;
*p = old & ~mask;
}
static int find_next_zero_bit(void *addr, int size, int offset)
{
unsigned long *p;
unsigned long mask;
while (offset < size)
{
p = ((unsigned long*)addr) + (offset >> (sizeof(unsigned long) + 1));
mask = 1UL << (offset & (sizeof(unsigned long) * BITS_PER_BYTE - 1));
if ((~(*p) & mask))
{
break;
}
++offset;
}
return offset;
}
static int alloc_pidmap()
{
int pid = last_pid + 1;
int offset = pid & BITS_PER_PAGE_MASK;
if (!pidmap.nr_free)
{
return -1;
}
offset = find_next_zero_bit(&pidmap.page, BITS_PER_PAGE, offset);
if (BITS_PER_PAGE != offset && !test_and_set_bit(offset, &pidmap.page))
{
--pidmap.nr_free;
last_pid = offset;
return offset;
}
return -1;
}
static void free_pidmap(int pid)
{
int offset = pid & BITS_PER_PAGE_MASK;
pidmap.nr_free++;
clear_bit(offset, &pidmap.page);
}
int main()
{
int i;
for (i = 0; i < PID_MAX_DEFAULT + 100; ++i)
{
printf("pid = %d\n", alloc_pidmap());
if (!(i % 100))
{
// 到整百时释放一次pid,看回绕的时候是不是都是使用整百的pid
free_pidmap(i);
}
}
return 0;
}
在linux中(kernel/pid.c),提供了一种生成唯一PID的机制,用到一种位图算法。
什么是位图算法:它的特点是生成的是整数,而且是唯一的,限定在某个范围内。
内核既要分配唯一的pid还要对已经分好的pid进行跟踪,内核使用了一个大的位图,其中每个pid由一个比特标识。分配一个空闲的pid,本质上等同于找出位图中第一个值为0的比特,接下来将其置为1,释放时由1置为0.
eg:
假如最多有8个进程,我们用一个能表示8种情况的整数来标识。
0x03(00000011) 说明分配了的pid号是0,1号
0x13(00010011) 说明分配了的pid号是0,1,4号
一般应用中数值范围很大,我们考虑用一个比较大的数来表示,如果用unsigned long,4个字节,只能表示32个。如果还需要更多的进程号,我们可以申请一个连续的地址空间来表示。比如 :unsigned long bitmap[8],可以表示32*8个pid.
首先我们要知道申请的pid号在哪个范围,不同的范围用不同的地址中的数据来表示。求地址时相对于首地址的,所以要提供首地址,具体在哪个地址上的数据,要看pid号是什么。pid号对应于偏移号,即从首个bit开始算,看偏移了几位,偏移数值就是pid号。
第一个地址的偏移号时0-31,第二个地址的偏移号时32-63,第三个地址的偏移号时64-95,第四个地址的偏移号时96-127.
并且0~31 %32都得0,表示首地址,31~63 %32都得1,表示第二个地址,64~95 %32都得2,表示第三个地址,96-127 %32都得3,表示第四个地址。
得出结论:
偏移号/32所得结果时相对于首地址的偏移地址量,得到实际地址和pid了。
PID分配方法的DEMO
#include <stdio.h>
/* max pid, equal to 2^15=32768 */
#define PID_MAX_DEFAULT 0x8000
/* page size = 2^12 = 4K */
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define BITS_PER_BYTE 8
#define BITS_PER_PAGE (PAGE_SIZE * BITS_PER_BYTE)
#define BITS_PER_PAGE_MASK (BITS_PER_PAGE - 1)
typedef struct pidmap
{
unsigned int nr_free;
char page[PID_MAX_DEFAULT];
} pidmap_t;
static pidmap_t pidmap = { PID_MAX_DEFAULT, {'0'} };
static int last_pid = -1;
static int test_and_set_bit(int offset, void *addr)
{
unsigned long mask = 1UL << (offset & (sizeof(unsigned long) * BITS_PER_BYTE - 1));
unsigned long *p = ((unsigned long*)addr) + (offset >> (sizeof(unsigned long) + 1));
unsigned long old = *p;
*p = old | mask;
return (old & mask) != 0;
}
static void clear_bit(int offset, void *addr)
{
unsigned long mask = 1UL << (offset & (sizeof(unsigned long) * BITS_PER_BYTE - 1));
unsigned long *p = ((unsigned long*)addr) + (offset >> (sizeof(unsigned long) + 1));
unsigned long old = *p;
*p = old & ~mask;
}
static int find_next_zero_bit(void *addr, int size, int offset)
{
unsigned long *p;
unsigned long mask;
while (offset < size)
{
p = ((unsigned long*)addr) + (offset >> (sizeof(unsigned long) + 1));
mask = 1UL << (offset & (sizeof(unsigned long) * BITS_PER_BYTE - 1));
if ((~(*p) & mask))
{
break;
}
++offset;
}
return offset;
}
static int alloc_pidmap()
{
int pid = last_pid + 1;
int offset = pid & BITS_PER_PAGE_MASK;
if (!pidmap.nr_free)
{
return -1;
}
offset = find_next_zero_bit(&pidmap.page, BITS_PER_PAGE, offset);
if (BITS_PER_PAGE != offset && !test_and_set_bit(offset, &pidmap.page))
{
--pidmap.nr_free;
last_pid = offset;
return offset;
}
return -1;
}
static void free_pidmap(int pid)
{
int offset = pid & BITS_PER_PAGE_MASK;
pidmap.nr_free++;
clear_bit(offset, &pidmap.page);
}
int main()
{
int i;
for (i = 0; i < PID_MAX_DEFAULT + 100; ++i)
{
printf("pid = %d\n", alloc_pidmap());
if (!(i % 100))
{
// 到整百时释放一次pid,看回绕的时候是不是都是使用整百的pid
free_pidmap(i);
}
}
return 0;
}