首页 > 系统相关 >【Linux内核】解锁Linux性能:位图数据结构背后的故事

【Linux内核】解锁Linux性能:位图数据结构背后的故事

时间:2024-12-22 14:44:10浏览次数:4  
标签:数据结构 解锁 unsigned long bitmap 接口 Linux bit 位图

在日常使用 Linux 系统的过程中,你是否遇到过系统资源紧张、运行速度缓慢的情况?
面对这些问题,我们往往会寻找各种方法来提升性能。而今天要介绍的位图数据结构,就是 Linux 系统中解决这类问题的一把利器。
它以一种简洁而高效的方式,帮助 Linux 系统更好地管理资源、优化数据存储和处理,从而实现性能的大幅提升。
那么,位图数据结构到底是如何做到的呢?
它背后又有哪些值得我们探究的故事呢?
接下来,就让我们一探究竟。

一、位图概述

在驱动开发以及 Linux 内核源码中,位图都是一种常用的管理资源的方式。比如说,在对效率要求并非极其严苛的场景下,采用位图去管理资源会是个不错的选择,它简单且易于理解。
像 cpumask 就是内核源码里使用位图的典型例子,其定义为 typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;。

位图管理资源的原理其实很简单,就是用 0 来表示资源处于可用的状态,而 1 则代表对应的资源已经被占用了,通过这样简单的 0 和 1 的设置,就能清晰地知晓资源的使用情况。更方便的是,Linux 内核已经贴心地为我们提供了众多位图操作的 API,我们在使用时,只需像 “拿来主义” 那样直接运用就好,这些 API 涵盖了位图的各种常见操作,为我们处理位图相关需求提供了极大的便利。

位图(Bitmap)在 Linux 内核中使用非常广泛,比如用来标识中断是否已安装处理程序(used_vectors)、处理器是否在线(cpumask)等等。内核中,位图相关的接口及实现主要在以下几个文件中:

include/linux/bitmap.h
lib/bitmap.c
lib/find_next_bit.c
include/linux/bitops.h
arch/x86/include/asm/bitops.h

其中头文件 arch/x86/include/asm/bitops.h 中,保存的是特定于 x86-64 架构的位图操作。

二、位图的定义

2.1 简单定义方式

在 Linux 内核中,一种简单的位图声明形式就是采用 unsigned long 数组。
例如,我们可以像这样声明一个位图:unsigned long my_bitmap[8]; 。这里创建了一个包含 8 个 unsigned long 类型元素的数组来作为位图使用。

每个 unsigned long 类型在不同的系统架构下所占的比特位数有所不同,在 32 位系统里,unsigned long 通常占 32 位(即 4 个字节),在 64 位系统中则占 64 位(8 个字节)。用这种数组形式来表示位图时,我们可以通过对数组元素里的每一位(bit)进行操作,来体现相应资源的占用情况,比如用 0 表示资源处于可用状态,1 代表对应的资源已经被占用了。

这种定义方式比较直观、易于理解,不过在实际应用中,需要我们自己去处理诸如位运算等相关操作来实现对位图的各种管理需求,比如设置某一位、清除某一位等操作,都要手动编写代码去完成相应的位运算逻辑。

2.2 利用宏定义

除了上述简单的定义方式外,Linux 内核还提供了 DECLARE_BITMAP 宏来定义位图。其定义形式如下:

#define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]

这个宏有两个参数,name 代表的是定义出的位图变量名,也就是所生成的 unsigned long 类型数组的名字,同时它也代表了这个位空间的起始地址;而 bits 参数则表示位图中总共拥有的比特(bit)数目。

通过 DECLARE_BITMAP 宏定义出来的其实就是一个 unsigned long 类型的数组,其元素个数是由 BITS_TO_LONGS(bits) 来确定的。BITS_TO_LONGS 这个宏又涉及到其他相关的宏定义:

#define BITS_PER_BYTE 8
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

它的作用是将给定的比特位数 bits 转换为需要多少个 long 类型来表示,也就是计算出最终 unsigned long 数组的元素个数。例如,如果我们要定义一个位图 DECLARE_BITMAP(my_bitmap, 64); ,经过计算(按照 32 位系统,sizeof(long) 为 4 字节,也就是 32 位来算):

unsigned long my_bitmap[BITS_TO_LONGS(64)];
unsigned long my_bitmap[DIV_ROUND_UP(64, 8 * sizeof(long))];
unsigned long my_bitmap[DIV_ROUND_UP(64, 32)];
unsigned long my_bitmap[(((64) + (32) - 1) / (32))];
unsigned long my_bitmap[1];

可以看到最终展开就是定义了一个包含 1 个 unsigned long 元素的数组(因为在 32 位系统下,1 个 unsigned long 就有 32 位,足够表示 64 个比特位了)。如果要表示的比特数更多,超出了一个 unsigned long 所能承载的范围,那相应的数组元素个数就会增多,以此类推来满足对位图比特数的需求。这样通过宏定义的方式,为我们在定义位图时提供了一种更为规范和便捷的途径,并且内核后续也基于这样定义出的位图提供了一系列配套的操作函数来方便我们使用。

三、位图相关汇编指令

在Linux内核中,位图(bitmap)是一种用于高效管理二进制状态的数据结构。
与位图相关的汇编指令主要涉及对单个位或一组位的操作。
以下是一些常见的汇编指令,可以用于实现位图的功能。

设置位:SET 或 OR 指令可以用来设置特定位置为1。例如,使用 OR 操作将某个比特位置1。

; 假设 EAX 存储的是位图地址,EBX 存储的是要设置的比特位置
mov eax, [bitmap_address]
or byte ptr [eax], 1 << ebx ; 设置第 ebx 位

清除位:CLEAR 或 AND 指令可以用来将特定位清零。例如,使用 AND 操作将某个比特位置0。

; 假设 EAX 存储的是位图地址,EBX 存储的是要清除的比特位置
mov eax, [bitmap_address]
and byte ptr [eax], ~(1 << ebx) ; 清除第 ebx 位

读取位:TEST 指令可用于检查某个位是否被设置。

; 假设 EAX 存储的是位图地址,EBX 存储的是要检查的比特位置
mov eax, [bitmap_address]
test byte ptr [eax], 1 << ebx ; 检查第 ebx 位是否为1
jz not_set ; 如果为0则跳转到 not_set 标签

翻转/切换位:XOR 指令可以用来切换某个位。

; 假设 EAX 存储的是位图地址,EBX 存储的是要翻转的比特位置
mov eax, [bitmap_address]
xor byte ptr [eax], 1 << ebx ; 翻转第 ebx 位

这些指令通常会在更复杂的函数和宏中使用,以提高代码可读性和效率。在Linux内核中,特别是在处理内存管理、任务调度等方面时,会频繁使用到这些低级操作。具体实现可能依赖于不同的平台和架构,因此具体细节可能会有所不同。

四、位图相关接口详解

4.1 接口 A 的功能与使用

在 Linux 内核数据结构位图众多接口中,我们先来了解一下 set_bit 这个接口(这里以 set_bit 作为接口 A 示例,实际可根据具体情况替换)。

set_bit 接口的主要功能是设置位图中指定位置的位为 1。它的函数原型一般类似这样:void set_bit(int nr, volatile unsigned long *addr); 。其中,参数 nr 代表的是要设置的位在位图中的偏移量,也就是具体是第几位,这个偏移量是从 0 开始计数的。而参数 *addr 则指向了存储位图数据的内存地址,通过这个指针,接口就能准确找到对应的位图去进行操作。

例如,我们假设有一个简单的位图变量 my_bitmap ,用来表示某些设备的使用状态(假设共 8 位,对应 8 个设备),初始时所有位都是 0,表示设备都处于空闲状态。现在我们要标记第 3 个设备正在被使用,代码示例如下:


#include <stdio.h>
#include <asm/types.h>

int main() {
    // 定义并初始化一个位图,这里简单用一个unsigned long类型来模拟,实际可能在内核中有更复杂的定义和初始化方式
    volatile unsigned long my_bitmap = 0;
    // 使用set_bit接口设置第3位(因为偏移量从0开始,所以实际是第3个位置)为1,表示对应的设备被使用
    set_bit(2, &my_bitmap);
    // 可以在这里添加代码输出位图的状态查看设置结果,比如简单打印出十六进制的值来查看
    printf("The bitmap after setting bit: 0x%lx\n", my_bitmap);
    return 0;
}

通过这样的使用,就能利用 set_bit 接口准确地在位图中设置相应位的状态,方便后续内核根据位图情况来判断设备的使用与否等操作。

4.2 接口 B 的特点及应用场景

接下来讲讲 test_bit 接口(这里以 test_bit 作为接口 B 示例),它有着独特的特点及广泛的应用场景。

test_bit 接口的特点在于它只是用于测试位图中指定位置的位是 0 还是 1,并不会改变该位原本的值。其函数原型大致为 int test_bit(int nr, const volatile unsigned long *addr); 。这里参数 nr 和 *addr 的含义与 set_bit 中的类似,分别表示位的偏移量和位图所在的内存地址。该接口返回值是一个整型,如果返回值为 0,则表示测试的位是 0;如果返回值非 0,则表示测试的位是 1。

在实际应用场景中,比如在进程调度时,内核维护着一个位图来表示各个进程是否具备某种特殊属性(如是否可抢占等)。当需要判断某个特定进程是否具备该属性时,就可以使用 test_bit 接口来进行检测,而且不用担心会误改这个位图中进程属性对应的位状态。

以下是一个简单示例代码展示其应用:

#include <stdio.h>
#include <asm/types.h>

int main() {
    // 定义并初始化一个位图,模拟进程属性位图,假设初始值为0x10(二进制为10000,这里假设第5位表示某个特定属性)
    volatile unsigned long process_bitmap = 0x10;
    // 使用test_bit接口测试第4位(偏移量为4)的属性状态
    int result = test_bit(4, &process_bitmap);
    if (result) {
        printf("The bit is set (value is 1).\n");
    } else {
        printf("The bit is not set (value is 0).\n");
    }
    return 0;
}

使用 test_bit 接口时需要注意,要确保传入的偏移量参数 nr 是在合理范围内,不能超出位图实际的位数范围,否则可能会导致不可预期的结果,比如读取到错误的内存地址内容等情况。

4.3 更多接口的简要梳理

除了上述介绍的 set_bit 和 test_bit 接口外,还有一些相对没那么常用但同样重要的位图相关接口。

比如 clear_bit 接口,它的主要作用是将位图中指定位置的位清零。在实际使用中,当某个设备使用完毕,要将其在位图中对应的使用状态标记清除时,就可以使用这个接口。其使用方式和参数含义与 set_bit 类似,只是功能是将位设为 0 而非 1。

还有 change_bit 接口,正如其名,它可以改变位图中指定位置的位的值,如果原来为 0 则变为 1,如果原来为 1 则变为 0。这在一些需要动态切换状态的位图应用场景中比较实用,例如在内核管理一些可切换工作模式的模块状态标记时,就能借助这个接口来方便地更新位图中的对应位状态。

另外,find_first_zero_bit 接口也不容忽视,它能够在位图中查找第一个值为 0 的位的偏移量。在资源分配场景下,比如要寻找第一个空闲的设备,通过这个接口就能快速定位到位图中对应的空闲位,进而进行后续的资源分配操作。

虽然这些接口可能在日常开发中使用频率稍低一些,但掌握它们能让我们在处理 Linux 内核中位图相关操作时更加游刃有余,对深入理解内核如何利用位图进行资源管理等机制有着重要意义。

4.4 位图相关接口的优化

从代码实现角度来看,要尽量减少不必要的接口调用次数。比如在循环中频繁地调用 test_bit 接口去检测位图中多个位的状态时,可以考虑先将位图的数据整体拷贝到本地变量(当然要确保操作的合法性和安全性),然后通过位运算的方式在本地进行批量检测,最后再根据检测结果去执行后续操作。这样能避免多次进入内核态去访问位图内存地址,减少系统开销。例如,在处理大量进程的某个属性位图检测场景中,以下这种优化方式就很实用:

#include <stdio.h>
#include <asm/types.h>

#define PROCESS_COUNT 100 // 假设共有100个进程

int main() {
    // 模拟进程属性位图
    volatile unsigned long process_bitmap = 0xFFFF; // 这里只是示例一个初始值
    unsigned long local_bitmap = process_bitmap; // 拷贝到位图到本地变量

    for (int i = 0; i < PROCESS_COUNT; i++) {
        int bit_value = (local_bitmap >> i) & 0x1; // 通过位运算检测每一位状态
        // 根据检测结果进行相应处理,这里简单打印示例
        if (bit_value) {
            printf("Process %d has the property.\n", i);
        }
    }
    return 0;
}

在资源利用方面,合理规划位图的大小至关重要。不要为了方便随意开辟过大的位图空间,而是要根据实际业务需求准确预估需要表示的元素数量,从而确定合适的位图位数。比如在设备管理中,如果只需要管理 10 台设备,那就只使用能够表示 10 个状态位的位图,避免浪费内存资源。同时,对于一些临时使用的位图,在使用完毕后要及时释放其占用的内存,防止内存泄漏导致系统性能下降。

另外,在多线程或者多进程环境下使用位图相关接口时,要注意添加合适的锁机制来保证数据的一致性和线程安全。比如使用互斥锁来保护正在被读写的位图,避免出现数据竞争的情况,确保接口操作能按照预期准确执行,进而保障整个系统的高效稳定运行。

五、位图Bitmap操作

5.1 bit 位操作

在 Linux 内核中,有不少常用的 bit 位操作函数,下面为大家详细介绍一下它们的功能、参数定义及作用。

首先是 __set_bit 函数,其函数定义为 static inline void __set_bit(int nr, volatile unsigned long *addr) ,它的作用是用于在 bitmap 中将相应的 bit 位置 1。这里的参数 nr 代表的是 bit 索引,其取值范围属于 [0,bits-1];而 addr 则是一个指向存储位状态的内存地址,通过这个函数调用,就能把指定位置的二进制位设置为 1。

__set_bit 相反的是 __clear_bit 函数,定义为 static inline void __clear_bit(int nr, volatile unsigned long *addr),它的功能是将相应位置的 bit 清除为 0,其参数定义与 __set_bit 是一致的。

还有 __change_bit 函数,即 static inline void __change_bit(int nr, volatile unsigned long *addr),该函数能够将相应位置的值取反,也就是把 0 变为 1,1 变为 0。

另外,还有三个带 test 的对应的函数,分别是 test_and_set_bittest_and_clear_bittest_and_change_bit,它们相较于前面几个函数多了返回旧值的功能。

除此之外,还有 test_bit 函数,定义为 static inline int test_bit(int nr, const volatile unsigned long *addr),它主要用于测试对应位置是否置位,通过相应的运算逻辑来返回测试的结果,返回值为 1 表示对应位已置位,返回 0 则表示对应位未置位。

这些 bit 位操作函数在内核开发等场景中使用频繁,开发者可以根据具体需求灵活选用,实现对 bitmap 中 bit 位状态的各种操作。

5.2 bit 位遍历

在 Linux 内核中,提供了多个用于 bit 位遍历的接口,接下来对它们进行逐一介绍。

find_first_zero_bit 函数,其原型为 unsigned long find_first_zero_bit(const unsigned long *addr, unsigned long size),它的功能是寻找 bitmap 中第一个为 0 的位序,并返回该位序。这里 addr 是一个指向无符号长整型数组的指针,表示要查找的位图;size 则是位图的大小,以位为单位。

find_next_zero_bit 函数,函数原型是 unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, unsigned long offset),它从 offset 这个起始位置开始,在位图中寻找下一个为 0 的位序,并返回找到的位序。其中 offset 表示从哪个位开始查找下一个为 0 的位,其最小值为 0,最大值为 sizeof(unsigned long)*8 - 1(在 32 位系统中就是 0 到 255)。

find_first_bit 函数,用于寻找 bitmap 中第一个为 1 的位序,并返回相应位序,其参数情况与前面寻找 0 位序的函数类似,都是通过传入位图地址和位图大小来确定查找范围。

find_next_bit 函数,函数形式为 unsigned long find_next_bit(const unsigned long *addr, unsigned long size, unsigned long offset),它从 offset 位置开始,在位图中查找下一个为 1 的位序,并返回该位序。

还有像 for_each_set_bit 这个宏定义,形式如下:

#define for_each_set_bit(bit, addr, size) \\
for ((bit) = find_first_bit((addr), (size));\\
(bit) < (size);\\
(bit) = find_next_bit((addr), (size), (bit) +1))

它的作用是从 0 位序开始遍历到 size - 1,返回每一个置位 bit 的位序。

for_each_set_bit_from 宏定义与 for_each_set_bit 基本相同,不过它多了一个起始位,是从特定位置开始查找并返回置位 bit 的位序,其定义形式为:


#define for_each_set_bit_from(bit, addr, size) \\
for ((bit) = find_next_bit((addr), (size), (bit));\\
(bit) < (size);\\
(bit) = find_next_bit((addr), (size), (bit) +1))
for_each_clear_bit 宏定义,定义如下:
#define for_each_clear_bit(bit, addr, size) \\
for ((bit) = find_first_zero_bit((addr), (size));\\
(bit) < (size);\\
(bit) = find_next_zero_bit((addr), (size), (bit) +1))
它是从 0 位序开始遍历,返回每一个为 0 bit 的位序。
for_each_clear_bit_from 宏定义同样类似,也是从特定位置开始查找并返回为 0 bit 的位序,定义形式为:
#define for_each_clear_bit_from(bit, addr, size) \\
for ((bit) = find_next_zero_bit((addr), (size), (bit));\\
(bit) < (size);\\
(bit) = find_next_zero_bit((addr), (size), (bit) +1))

这些 bit 位遍历的接口,在处理位图相关的操作时,能够帮助开发者便捷地按照不同需求去遍历位图中的每一位,从而进行相应的逻辑处理。

六、位图Bitmap的应用

位图在 Linux 内核中有诸多实用的应用场景,下面为大家详细介绍一些常见的应用情况以及相应的代码示例,帮助大家更好地理解其实际使用逻辑与效果。

6.1 分配唯一 PID

在 Linux 系统中,需要为每个进程分配唯一的进程标识符(PID),同时还要对已经分配好的 PID 进行跟踪管理。这时,内核就巧妙地运用了位图来解决这个问题。内核会创建一个大的位图,其中每个 PID 由一个比特来标识。例如,PID 的值可通过对应比特在位图中的位置计算得出。

具体来说,分配一个空闲的 PID,本质上等同于在位图中寻找第一个值为 0 的比特,找到后将该比特设置为 1,便完成了 PID 的分配操作。以下是一个简单的代码示意,展示如何去寻找空闲 PID 并分配(这里只是示例逻辑,实际内核代码更复杂且涉及更多细节处理):

#include <linux/bitmap.h>
#include <linux/bitops.h>

// 假设我们有一个足够大的位图来表示PID,这里简单定义一个长度示例
#define PID_BITMAP_LEN 1024
DECLARE_BITMAP(pid_bitmap, PID_BITMAP_LEN);

// 函数用于分配一个空闲的PID
int allocate_pid() {
    int index = find_first_zero_bit(pid_bitmap, PID_BITMAP_LEN);
    if (index < PID_BITMAP_LEN) {
        // 找到空闲位,将其置为1,表示PID已被分配
        __set_bit(index, pid_bitmap);
        return index;
    }
    return -1; // 表示没有找到空闲的PID
}
而当要释放一个 PID 时,操作则相反,通过将对应的比特从 1 切换为 0 来实现,代码如下:
// 函数用于释放指定的PID
void release_pid(int pid) {
    if (pid >= 0 && pid < PID_BITMAP_LEN) {
        __clear_bit(pid, pid_bitmap);
    }
}

6.2 内存管理的伙伴分配系统

在 Linux 内核的内存管理中,伙伴分配系统发挥着重要作用。伙伴系统主要用于管理系统中的物理内存页,它把两个物理地址相邻的内存页当作伙伴关系。而在伙伴分配系统的数据结构中有个关键的元素 —— 位图,其用于记录伙伴内存块的使用情况。

比如,在内存管理区数据结构中有个名为 free_area 类型为 free_area_t 的字段,它的作用就是用来管理内存管理区内的空闲物理内存页。free_area_t 结构中有个 map 字段,这个字段就是一个位图,每个位记录着一对伙伴内存块的使用情况。如果一对伙伴内存块中的某一个内存块在使用,那么对应的位就为 1,如果两个伙伴内存块都是空闲或者使用,那么对应的位就为 0。

以下是一段简单示意代码,展示在伙伴分配系统中利用位图判断伙伴内存块能否合并的逻辑(仅是示意,实际内核代码更复杂):

// 假设这里有个结构体表示内存管理区中的空闲区域相关信息
typedef struct free_area_struct {
    struct list_head free_list;
    unsigned int  *map;  // 这里就是记录伙伴内存块使用情况的位图指针
} free_area_t;

// 函数判断给定的一对伙伴内存块(这里假设通过内存块索引index来表示)能否合并
int can_merge_buddies(free_area_t *area, int index) {
    // 通过位运算等操作来获取对应位的状态,这里简化示意获取逻辑
    int bit_status = test_bit(index, area->map);
    if (bit_status == 0) {
        return 1;  // 对应位为0,说明两个伙伴内存块都空闲,可以合并
    }
    return 0;  // 对应位为1,说明有一个内存块在使用,不能合并
}

通过这样的位图记录,当释放内存块时,如果对应的位是 1 的话,那么说明另外一个伙伴内存块是空闲状态的,所以释放当前内存块可以跟其伙伴内存块合并成一个更大的内存块,从而高效地管理物理内存的分配与回收。

6.3 调试场景

位图还可以用于调试相关的操作,方便开发人员查看资源的使用情况等信息。比如下面这段代码定义了一个长度为 128 的位图,代表 128 份的资源,1 表示不可用,0 表示可用,并且利用 sysfs 添加了位图的调试接口,方便查看资源使用以及进行相关设置操作:

#include <linux/bitmap.h>
#include <linux/bitops.h>
typedef   unsigned short uint16;
typedef   unsigned int   uint32;
#define BITMAP_LEN 128
DECLARE_BITMAP(Test, BITMAP_LEN);

// 获取可用资源索引的函数,用于调试查看
static ssize_t bitmap_debug_get_usable_index(struct device *dev,
                                              struct device_attribute *attr,
                                              char *buf) {
    uint16 index = 0;
    index = find_first_zero_bit(Test, 128);
    printk("\n   index:%d\r\n", index);
    return 0;
}

// 设置资源为已使用的函数,通过传入范围来设置相应位为1
static ssize_t bitmap_debug_set_used_index(struct device *dev,
                                            struct device_attribute *attr,
                                            const char *buf, size_t count) {
    uint32 start = 0;
    uint32 end   = 0;
    uint32 index = 0;
    uint32 old   = 0;
    sscanf(buf, "%u%u", &start, &end);
    if ( start > end) printk("index start input error\n\r");
    for (index = start; index <= end; index++)
        old = test_and_set_bit(index, Test);
    return count;
}

// 创建用于获取索引的设备属性
static  DEVICE_ATTR(get_index, S_IRUSR, bitmap_debug_get_usable_index,
                    NULL);
// 创建用于设置索引的设备属性
static  DEVICE_ATTR(set_index, S_IWUSR, NULL,
                    bitmap_debug_set_used_index);

struct attribute *bitmap_attrs[] = {
    &dev_attr_get_index.attr,
    &dev_attr_set_index.attr,
    NULL,
};

struct attribute_group bitmap_group = {
   .name  = "bitmap",
   .attrs  = bitmap_attrs,
};

从上述这些应用场景可以看出,位图在内核中的应用十分广泛,它以简洁高效的方式帮助内核完成了众多资源。

七、位图接口在实际项目中的案例分析

7.1 案例一:设备管理系统中的应用

在大型数据中心的设备管理系统里,有着众多的服务器、存储设备等硬件资源需要进行管理和调度。假设有一个数据中心拥有 1000 台服务器,使用位图来标记这些服务器的使用状态,每一位对应一台服务器,0 表示空闲,1 表示正在被使用。

通过 set_bit 接口,当某台服务器被分配任务开始工作时,就可以将对应位设置为 1;任务结束后,利用 clear_bit 接口将该位清零,标记为空闲状态。例如,当第 300 台服务器接收到任务开始运行,代码可以这样写:

#include <stdio.h>
#include <asm/types.h>

#define SERVER_COUNT 1000  // 总共1000台服务器
int main() {
    volatile unsigned long server_bitmap[SERVER_COUNT / (sizeof(unsigned long) * 8)] = {0};  // 初始化位图
    // 假设第300台服务器开始使用,设置对应位为1(注意要换算成正确的偏移量)
    set_bit(299, server_bitmap);
    // 这里可以添加其他相关操作代码,比如记录使用日志等
    return 0;
}

而在需要寻找空闲服务器进行新任务分配时,借助 find_first_zero_bit 接口,就能快速定位到第一个值为 0 的位,也就是找到第一台空闲的服务器,从而高效地进行资源分配。

这样做带来的效益是显著的,相比于传统的用数组或者链表等方式来记录服务器状态,位图极大地节省了内存空间。原本记录 1000 台服务器状态,如果用布尔值数组来表示,需要 1000 个字节的空间,而采用位图,只需要 1000 / (8 * sizeof (unsigned long)) 个 unsigned long 类型的空间,大大减少了内存占用。同时,通过接口操作位图来判断和更新设备状态,速度也很快,能快速响应对设备资源的调度需求,提升了整个数据中心设备管理的效率。

7.2 案例二:网络通信中的端口状态监控

在网络服务器的开发中,需要对大量的网络端口状态进行监控和管理。比如一台服务器要同时处理数千个网络连接请求,对应着数千个端口。

可以利用位图,让每一位代表一个端口,通过 set_bit 接口在端口建立连接时设置对应位为 1,表示端口处于使用状态;定期使用 test_bit 接口来检测端口对应的位,判断连接是否正常,如果检测到某端口对应的位为 0 了(原本是 1),说明连接可能出现异常中断等情况,便于及时进行相应处理;当连接关闭后,再用 clear_bit 接口清除该端口对应的位状态。以下是一个简单示例代码片段:

#include <stdio.h>
#include <asm/types.h>

#define PORT_COUNT 10000  // 假设最多监控10000个端口
int main() {
    volatile unsigned long port_bitmap[PORT_COUNT / (sizeof(unsigned long) * 8)] = {0};  // 初始化端口状态位图
    // 模拟端口8080建立连接,设置对应位为1
    set_bit(8080, port_bitmap);
    // 检测端口8080连接状态
    int status = test_bit(8080, port_bitmap);
    if (status) {
        printf("Port 8080 is connected.\n");
    } else {
        printf("Port 8080 is disconnected or has some issues.\n");
    }
    // 模拟端口8080连接断开,清除对应位
    clear_bit(8080, port_bitmap);
    return 0;
}

通过这种方式,服务器可以高效地掌握各个端口的状态,及时发现并处理网络连接中的异常情况,而且在内存占用方面,相较于用结构体等复杂数据结构来记录每个端口的详细状态信息,位图的空间优势明显,能够在有限的内存资源下,实现对大量端口状态的有效管控,保障网络通信服务的稳定运行。

7.3 案例三:文件系统中的文件索引管理

在 Linux 文件系统中,以 Ext2 文件系统为例,其采用了位图来存储信息的结构有块位图(Block Bitmap)和 inode 位图(inode Bitmap)等。

对于块位图,每一位对应着磁盘上的一个数据块,当文件需要存储数据,分配磁盘块时,通过 find_first_zero_bit 接口在位图中找到第一个空闲的数据块对应的位(值为 0 的位),然后使用 set_bit 接口将其设置为 1,表示该数据块已被占用;当文件删除,对应的磁盘块被释放时,再利用 clear_bit 接口将相应位清零。

而 inode 位图中,每一位对应着一个 inode 节点,文件创建时,标记对应的 inode 位为 1,代表该 inode 被使用,文件删除后将其清零。例如在查找文件流程中,通过 inode 位图能快速判断哪些 inode 节点是空闲可用的,进而为新文件分配 inode 资源。

这种利用位图来管理文件系统中磁盘块和 inode 等资源的方式,使得文件系统在进行文件的存储、删除、查找等操作时,能够快速准确地定位和分配资源,保证了文件系统整体运行的高效性和稳定性,并且有效避免了资源浪费和冲突等问题,提升了整个文件系统对海量文件的管理能力。

原创 往事敬秋风 深度Linux

标签:数据结构,解锁,unsigned,long,bitmap,接口,Linux,bit,位图
From: https://www.cnblogs.com/o-O-oO/p/18622113

相关文章

  • Linux安装Redis
    1、下载安装包如果没安装wget,先安装一下wgetyuminstallwget-ywget获取网络资源wgethttp://download.redis.io/releases/redis-6.2.6.tar.gz2、解压到指定目录tar-zxvfredis-6.2.6.tar.gz-C/opt/3、安装gcc安装gcc、tclyuminstallgcc-c++tcl-y因为我......
  • 【数据结构与算法】深度优先搜索:树与图的路径探寻之道
    一、引言在计算机科学领域,树与图的路径搜索是一个基础且重要的问题,而深度优先搜索算法(DepthFirstSearch,简称DFS)则是解决此类问题的经典算法之一。深度优先搜索算法通过从起始节点开始,沿着一条路径尽可能深地探索,直到无法继续或达到目标节点,然后回溯到前一步,继续探索其......
  • 解锁 Postman 前置脚本的强大功能:实用案例全解析
    公众号:测试工程师成长之路一、Postman前置脚本在当今的API开发与测试领域,Postman已然成为一款广受欢迎的工具,而其中的前置脚本功能更是犹如一位得力助手,发挥着不可或缺的作用。前置脚本,简单来说,就是在发送API请求之前执行的一段JavaScript代码。它能够让我们灵......
  • 解锁 Git Log 更多实用技巧
    目前,在软件开发的协作中,Git无疑是版本控制的王者。而其中的gitlog命令,犹如一把强大的历史探寻之剑,能够帮助我们深入洞察项目的演进历程。本篇将为大家整理解读几个实用的gitLog技巧,让你的项目管理和代码审查工作如虎添翼。1.挖掘代码深处的历史变更gitlog具备按文件......
  • 解锁分布式系统的关键:Spring Boot 与 Redis 分布式锁实战
    解锁分布式系统的关键:SpringBoot与Redis分布式锁实战在当今分布式系统架构广泛应用的时代,如何确保多个实例或线程在访问共享资源时的一致性和正确性,成为了开发人员面临的关键挑战之一。分布式锁作为解决这类问题的核心工具,在众多场景中发挥着不可或缺的作用。本文将深......
  • 嵌入式Linux,proc文件系统讲解,介绍以及读取使用
    1.简介         proc文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口,用户和应用程序可以通过proc文件系统得到系统信息和进程相关信息,对proc文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的是,proc文......
  • RockyLinux9编译安装MySQL8
    原文链接:RockyLinux9编译安装MySQL8-LiuZijian’sBlog|刘子健的博客Linux版本:RockyLinuxrelease9.5(BlueOnyx)1.下载打开MySQL-Community-Server官方下载页面:https://downloads.mysql.com/archives/community/筛选出要下载的版本,ProductVersion选择8.0......
  • linux用iftop实时查看软件应用进程网络占用情况
    步骤1:查看网卡名称1.使用ifconfig命令查看网卡信息:ifconfig你会看到类似以下的输出:eth0Linkencap:EthernetHWaddr00:11:22:33:44:55inetaddr:192.168.31.1Bcast:192.168.31.255Mask:255.255.255.0UPBROADCASTRUNNINGMULTICA......
  • 高敏感者变身沟通达人,五招解锁高情商秘籍
    1高敏感一族难以与人交流的三个主要原因虽然都被称为HSP人群,但是在“敏感”这一点上,其实可以细分出许多不同的倾向。虽然感受到的负面情绪也因人而异,但是有一点是共同的,那就是HSP人群会格外惧怕站在面前与自己交流的人。在交流时,即使表面上看起来游刃有余,但是内心却会不由自......
  • 嵌入式系统 第三讲 嵌入式Linux操作系统
    自己整理的笔记自用,抄录老师给的课件,只是看没有印象,所以我就敲出来了,不算原创也不算翻译,考试复习用的,有需要的伙伴可以看看,个人觉得还是有逻辑的。嵌入式系统对操作系统(OS)的要求:(1)高度简练(2)质量可靠(3)界面友善(4)易开发(5)多任务(6)价格低•3.1嵌入式Linux简介3.1.1μCLinux-......