linux内核中的位图

    位图(bitmap)是一种非常有用的数据结构,在处理系统中的进程数管理、磁盘中的磁盘块管理、以及内存中的内存页的使用情况管理时非常有用。

    同时在内核中对位图进行各种操作,现在总结一些常用的操作,以便在以后用到时方便回顾。

    几个常用的宏定义:

    #define BIT_PER_TYPE 8 

    #define __WORDSIZE   32

    #define BITS_PER_LONG  __WORDSIZE

    #define DIV_ROUND_UP(nr, d)  \

            ( (nr) + (d) - 1) / (d)

    这个DIV_ROUND_UP(nr, d)宏的作用是:如果定义了一个: unsinged long name[128]用作位图的话,则 d = 32bits, 在假设 nr = 128*32 - 1,则n在数组中的下标可以通过 nr / 32 = 127来计算出来,而实际的数组name中有128个元素,所有 DIV_ROUND_UP()函数的作用是将比特位与数组的大小关联起来,可以通过 nr 比特位来求出数组的长度。

   

    #define BITS_TO_LONGS(bits)  DIV_ROUND_UP(bits, sizeof(long) * BTT_PER_BYTE)

    #define DEFINE_BITMAP(name, bits) \

           unsigend long int name[BITS_TO_LONGS(bits)]

    DEFINE_BITMAP(name, bits)定义了一个比特位图,name为比特位图的名字,bits为比特位图的大小。

    #define BIT(nr)   1UL << (nr)  //将1左移 nr位

    #define BIT_MASK(nr)  1UL << ( (nr) % BITS_PER_LONG ) //将第nr位置一;

    #define BIT_WORD(nr)  (nr / BITS_PER_LONG )  // 用于nr位于那个数组元素之中;

    #define BITMAP_LAST_WORD_MASK(bits)  \

            ( bits % BITS_PER_LONG ) ?  (1 << (bits % BITS_PER_LONG) ) - 1 : ~0UL

    BITMAP_LAST_WORD_MASK()主要用于保存第bits位所在的元素中的前(bits % BITS_PER_LONG -1)个位中的值。

 2.位图中的函数操作:

   

   1.判断一个位图是否没有还没有别使用:

     // bitmap : 位图的起始地址;

     // bits   : 位图的大小;

     int __bitmap_empty(unsigned long *bitmap, int bits)

     {

         unsigned long *addr= bitmap;

         unsigned int k, limit;

         limit = bits / BITS_PER_LONG ; //数组下标值;

         // 用于看看数组下标为0 - limit-1的数组元素是否都为0;

         for(k = 0; k < limit ; k++)

         {

            if(addr[k])

               return 0;

         }

         //用于看下标为 bits / BITS_PER_LONG 的元素中的 0 - bits%BITS_PER_LONG-1位是否也全都为零

         if( bits % BITS_PER_LONG )

         {

            if( addr[k] & BITMAP_LAST_WORD_MASK(bits) )

               return 0;

         }

         return 1;

     }

    如果位图中的所有位都为0,则 int __bitmap_empty()返回 1;否则返回 0;

    2.判断一个位图中的所有位是否置为 1:

      int __bitmap_full(unsigned long *bitmap, int bits)

      {

          unsigned long *addr = bitmap;

          int k, limit;

          limit = bits / BITS_PER_LONG;

          for(k = 0; k < limit; k++)

          {

             // 如果addr[k]中的每个位都为1的话,那么~addr[k]必然为0;

             // 否则如果addr[k]中有的位为1,有的位为0的话,那么~addr[k]中的每一个位必然也是有的为零,有的为1;

              if(~addr[k])

               return 0;

          }

          if(bits % BITS_PER_LONG)

          {

              if( ~addr[k] & BITMAP_LAST_WORD_MASK(bits) )

               return 0;

          }

          return 1;

      }

      3.判断两个位图是否相等,前提时这两个位图的长度必须要相等。

        int __bitmap_equal(unsigned long *bitmap1, unsigned long *bitmap2, int bits)

        {

            int k , limit;

            limit = bits / BITS_PER_LONG;

            for(k = 0; k < limit; k++)

            {

               // ^ 是异或操作运算符, 如果 a == b 则 a ^ b == 0

                                       如果 a != b 则 a ^ b != 0

               // 所以判断两个数是否相等,可以将这两个数进行异或操作,结果为0,表示相等

                                                                      结果不为0,表示不想等

               if(bitmap1[k] ^ bitmap[k])

                  return 0;

            }

            if(bits % BITS_PER_LONG)

            {

               if( (bitmap1[k] ^ bitmap[k]) & BITMAP_LAST_WORD_MASK(bits) )

                   return 0;

            }

            return 1;

        }

    

     4. 对一个位图中的所有位进行取反操作,然后将结果存入到另一个位图中:

        void __bitmap_complement(unsigned long *dest, unsigned long *src, int bits)

        {

             int k, limit;

             limit = bits / BITS_PER_LONG ;

             for(k = 0 ; k < limit ; k++)

                 dest[k] = ~src[k];

             if(bits % BITS_PER_LONG )

                 dest[k] = ~src[k] & BITMAP_LAST_WORD_MASK(bits);

        }

      5.判断一个位图是否是另一个位图的子集:

       此处用到的数学集合的思想: 如果 A 属于 B 话,即A 是 B 的子集的,那么 A 必然不属于 ~B了;   A & ~B其结果必然为0;

       int __bitmap_subset(unsigned long *bitamp1, unsigned long *bitmap2, int bits)

       {

            int k, limit;

            limit = bits / BITS_PER_LONG ;

            for(k = 0; k < limit ; k++)

            {

                // 用于bitmap1[]中的每个元素的二进制形式是否是bitmap2[]中的每个元素二进制的

                //子集。如果是的话,则 bitmap1[k] & ~bitmap2[k]为零;否则非零。

                if(bitmap1[k] & ~bitmap2[k])

                  return 0;

            }

            if(bits % BITS_PER_LONG)

              if((bitmap1[k] & ~bitmap2[k]) & BITMAP_LAST_WORD_MASK(bits) )

                return 0;

            return 1;

       }

       6.用于求出位图中已经置为1的位的个数:

         int __bitmap_weight(unsigned long *bitmap, int bits)

         {

              int count, k, limit;

              count = 0;

              limit = bits / BITS_PER_LONG ;

              for(k = 0; k < limit ; k++)

                  count += hweight_long(bitmap[k]);

              if( bits % BITS_PER_LONG )

                count += hweight_long(bitmap[k] & BITMAP_LAST_WORD_MASK(bits))

              return count;

         }

         hweight_long() 用于计算一个8,16,32位的二进制数,1的个数;

       下面的这几个函数要用到这个宏定义,所以先定义一下:

       #define small_const_nbits(nbits) \

          ( __builtin_const_p(nbits) && (nbits) <= BITS_PER_LONG )

       __builtin_const_p(n) 是gcc内嵌的用于判断一个变量的内容是不是一个常数;

       所以 small_const_nbits()这个宏的主要作用是来判断一个变量的内容是否是一个常数,以及这两个变量的值是否大于32;

 

        7.对位图进行初始化:

         void bitmap_zero(unsigned long *bimap, int nbits)

         {

              //如果位图的长度小于32的话

              if(small_const_nbits(nbits))

                  *bitmap = 0UL;

               else

               {          

                  int length = BITS_TO_LONGS(nbits) * sizeof(long);

                  memset(bitmap, 0, length);

               }

         }

        8.对位图进行填充:

         void bitmap_fill(unsigned long *bitmap, int nbits)

         {

             if( small_const_nbits(nbits) )

                 *bitmap = BITS_TO_LONGS(nbits);

             else

               {

                   //最后的一个long数组元素可能并没有用完,所以只对用到的进行填充;

                   int length = BITS_TO_LONGS(nbits);

                   int len = (length - 1) * sizeof(long);

                   memset(bitmap, 0xff, len);

                   bitmap[length - 1] = BITS_TO_LONGS(nbits);

               }

         }

         9.位图的拷贝:

           void bitmap_copy(unsigned long *dest, unsigned long *src, int nbits)

           {

                if( small_const_nbits(nbits) )

                    *dest = *src;

                else

                {

                    int length = BITS_TO_LONGS(nbits) * sizeof(long);

                    memcpy(dest, src, length);

                }

           }

 下面的这些函数只做说明,并不在介绍其是如何实现:

      set_bit(int n, unsigned long *addr) //将addr位图中的第n位设置为1;

      clear_bit(int n, unsigned long *addr) //将addr位图中的第n位置0;

      change_bit(int n, unsigned long *addr) //将addr位图中的第n位改变,即1变0,0变1;

      test_bit(int n, unsigned long *addr) //测试addr位图中的第n位是否为1;

      test_and_set_bit(int n, unsigned long *addr)//测试addr位图中的第n位是否为1,如果不是

                       的话,将其设置为1,其返回值是之前第n位的值;

      test_and_clear_bit(int n, unsigned long *addr);

      test_and_change_bit(int n, unsigned long *addr);

      unsigned int  find_first_zero_bit(unsigned long *addr, int size) //在位图addr中,查找第一个为零的比特位,并返回其位置值;

      find_first_bit(unsigned long *addr, int size) //在位图addr中,查找第一个为1的比特位,并返回其位置;

      find_next_zero_bit(unsigned long *addr, int size, int pos) //在位图addr中,从pos开始

查找下一个为零的比特位,并返回其位置值;

      fine_next_bit(unsigned long *addr, int size, int pos)

      对位图进行遍历操作:

      #define for_each_bit(bit, addr, size) \

          for( (bit) = find_firt_bit( (addr), (size) ); \

               (bit) < (size); \

               (bit) = find_next_bit( (addr), (size), (bit)+1 ) )  

在linux源代码中关于位图各种操作实现位于: /include/linux/bitmap.h  

                                         /include/linux/bitops.h

                                         /lib/bitmap.c

时间: 2024-08-08 14:53:42

linux内核中的位图的相关文章

Linux内核中namespace之PID namespace

前面看了LInux PCI设备初始化,看得有点晕,就转手整理下之前写的笔记,同时休息一下!!~(@^_^@)~ 这片文章是之前写的,其中参考了某些大牛们的博客!! PID框架的设计 一个框架的设计会考虑很多因素,相信分析过Linux内核的读者来说会发现,内核的大量数据结构被哈希表和链表链接起来,最最主要的目的就是在于查找.可想而知一个好的框架,应该要考虑到检索速度,还有考虑功能的划分.那么在PID框架中,需要考虑以下几个因素. 如何通过task_struct快速找到对应的pid 如何通过pid快

[翻译] Linux 内核中的位数组和位操作

Linux 内核里的数据结构 [TOC] 原文链接与说明 https://github.com/0xAX/linux-insides/blob/master/DataStructures/bitmap.md 本翻译文档原文选题自 Linux中国 ,翻译文档版权归属 Linux中国 所有 Linux 内核中的位数组和位操作 除了不同的基于链式和树的数据结构以外,Linux 内核也为位数组或位图提供了 API.位数组在 Linux 内核里被广泛使用,并且在以下的源代码文件中包含了与这样的结构搭配使用

Linux内核中常用String库函数实现

//只列举了部分常用的strcpy,strcmp,strcat,strchr,strstr,strpbrk...  char *strcpy(char *dest, const char *src) { char *tmp = dest; while ((*dest++ = *src++) != '\0') /* nothing */; return tmp; } char *strncpy(char *dest, const char *src, size_t count) { char *t

Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; /***second***/susecond_t tv_usec;/***microsecond***/}到底microsecond是毫秒还是微秒?? 1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000

route-显示并设置Linux内核中的网络路由表

route命令 网络配置 route命令用来显示并设置Linux内核中的网络路由表,route命令设置的路由主要是静态路由.要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现. 语法 route(选项)(参数) 选项 -A:设置地址类型: -C:打印将Linux核心的路由缓存: -v:详细信息模式: -n:不执行DNS反向查找,直接显示数字形式的IP地址: -e:netstat格式显示路由表: -net:到一个网络的路由表: -host:到一个主机的路

C语言在linux内核中do while(0)妙用之法

为什么说do while(0) 妙?因为它的确就是妙,而且在linux内核中实现是相当的妙,我们来看看内核中的相关代码: #define db_error(fmt, ...) do { fprintf(stderr, "(error): "); fprintf(stderr, fmt, ##__VA_ARGS__); } while (0) 这只是个普通的调试信息的输出,有人便会认为,你这不是多此一举吗?去掉do while(0)不一样也实现了吗?其实不然,我们看看例子就清楚了,尽管很

Linux内核中的中断栈与内核栈的补充说明【转】

转自:http://blog.chinaunix.net/uid-12461657-id-3487463.html 原文地址:Linux内核中的中断栈与内核栈的补充说明 作者:MagicBoy2010 中断栈与内核栈的话题更多地属于内核的范畴,所以在<深入Linux设备驱动程序内核机制>第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断嵌套情形下可能的溢出问题. 本贴在这个基础上对内核栈与中断栈的话题做些补充,讨论基于x86 32位系统,因为64位系

向linux内核中添加外部中断驱动模块

本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内核中添加外部中断驱动模块.7.完整驱动程序代码.linux的内核版本为linux2.6.32.2. 一.linux模块的框架以及混杂设备相关知识 1.内核模块的框架如下图所示,其中module_init()(图中有误,不是modules_init)只有在使用insmod命令手动加载模块时才会被调用,

Linux内核中的软中断、tasklet和工作队列详解

[TOC] 本文基于Linux2.6.32内核版本. 引言 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的"下半部"(bottom half)演变而来.下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者. 介绍这三种下半部实现之前,有必要说一下上半部与下半部的区别. 上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务.举个例子:在网络传输中,网卡接收到数据包这