Blog

list_entry宏

Content #

#define list_entry(ptr, type, member) \
container_of(ptr, type, member)

//container_of()宏的定义在kernel.h头文件中。
#define container_of(ptr, type, member) ({      \
const typeof( ((type *)0)->member ) *__mptr = (ptr);  \
(type *)( (char *)__mptr - offsetof(type,member) );})

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

其中offsetof()宏是通过把0地址转换为type类型的指针,然后去获取该结构体中member成员的指针,也就是获取了member在type结构体中的偏移量。最后用指针ptr减去offset,就得到type结构体的真实地址了。

下面是遍历链表的一个例子。

//<drivers/block/osdblk.c>
static LIST_HEAD(osdblkdev_list);
struct osdblk_device {
    int			id;		/* blkdev unique id */
    int			major;		/* blkdev assigned major */
    ...
    struct list_head	node;
    char			osd_path[0];	/* OSD device path */
};

static ssize_t class_osdblk_list(struct class *c,
    struct class_attribute *attr,
    char *data)
{
    int n = 0;
    struct list_head *tmp;
    list_for_each(tmp, &osdblkdev_list) {
        struct osdblk_device *osdev;
        osdev = list_entry(tmp, struct osdblk_device, node);
        n += sprintf(data+n, "%d %d %llu %llu %s\n",
            osdev->id,
            osdev->major,
            osdev->obj.partition,
            osdev->obj.id,
            osdev->osd_path);
    }
    return n;
}

From #

奔跑吧Linux内核——入门篇

...

链表头的初始化(list_head)

Content #

链表头的初始化有两种方法,一种是静态初始化,另一种动态初始化。把 next 和 prev指针都初始化并指向自己,这样便初始化了一个带头节点的空链表。

<include/linux/list.h>
  1. 静态初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
  1. 动态初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

From #

奔跑吧Linux内核——入门篇

sub:内核链表(list_head)

Content #

Linux内核链表使用struct list_head数据结构来描述。

//<include/linux/types.h>
struct list_head {
    struct list_head *next, *prev;
};

struct list_head数据结构不包含链表节点的数据区,通常是嵌入其他数据结构,如struct page数据结构中嵌入了一个lru链表节点,通常是把page数据结构挂入 LRU链表。

<include/linux/mm_types.h>
struct page {
    struct list_head lru;
    ...
}

链表头的初始化(list_head)

添加节点到一个链表中 #

list_add()是把一个节点添加到表头,list_add_tail()是插入表尾。

//<include/linux/list.h>
void list_add(struct list_head *new, struct list_head *head)
void list_add_tail(struct list_head *new, struct list_head *head)

遍历节点 #

#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)

这个宏只是遍历一个一个节点的当前位置,那么如何获取节点本身的数据结构呢?这里还需要使用 list_entry宏

From #

奔跑吧Linux内核——入门篇

UL后缀修饰

Content #

在Linux内核代码中,我们经常会看到一些数字的定义使用了UL后缀修饰。数字常量会被隐形定义为int类型,两个int类型相加的结果可能会发生溢出,因此使用UL强制把int类型数据转换为unsigned long类型,这是为了保证运算过程不会因为int的位数不同而导致溢出。

1 :表示有符号整型数字1 1UL:表示无符号长整型数字1

From #

奔跑吧Linux内核——入门篇

asmlinkage

Content #

在标准C语言中,函数的形参在实际传入参数时会涉及参数存放问题。对于x86结构,函数参数和局部变量被一起分配到函数的局部堆栈里。

<arch/x86/include/asm/linkage.h>
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

attribute((regparm(0))):告诉编译器该函数不需要通过任何寄存器来传递参数,只通过堆栈来传递。

对于ARM来说,函数参数的传递有一套ATPCS标准,即通过寄存器来传递。ARM中的R0~R4寄存器存放传入参数,当参数超过5个时,多余的参数被存放在局部堆栈中。所以,ARM平台没有定义asmlinkage。

<include/linux/linkage.h>
#define asmlinkage CPP_ASMLINKAGE

From #

奔跑吧Linux内核——入门篇

__builtin_prefetch(const void *addr, int rw, int locality)

Content #

__builtin_prefetch(const void *addr, int rw, int locality)

主动进行数据预取,在使用地址addr的值之前就把其值加载到cache中,减少读取的延迟,从而提高性能。

  • addr表示要预取数据的地址;
  • rw表示读写属性,1表示可写,0表示只读;
  • locality表示数据在cache中的时间局部性。其中0表示读取完addr的之后不用保留在cache中,而1~3表示时间局部性逐渐增强。

如下面的prefetch()和prefetchw()函数的实现。

//<include/linux/prefetch.h>
#define prefetch(x) __builtin_prefetch(x)
#define prefetchw(x) __builtin_prefetch(x,1)

下面是使用prefetch()函数进行优化的一个例子。

//<mm/page_alloc.c>
void __init __free_pages_bootmem(struct page *page, unsigned int order)
{
    unsigned int nr_pages = 1 << order;
    struct page *p = page;
    unsigned int loop;
    prefetchw(p);
    for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
        prefetchw(p + 1);
        __ClearPageReserved(p);
        set_page_count(p, 0);
    }
    
}

在处理struct page数据之前通过prefetchw()预取到cache中,从而提升性能。

From #

奔跑吧Linux内核——入门篇

__builtin_expect(exp, c)

Content #

__builtin_expect(exp, c) 这里的意思是exp==c的概率很大,用来引导GCC编译器进行条件分支预测。开发人员知道最可能执行哪个分支,并将最有可能执行的分支告诉编译器,让编译器优化指令序列,使指令尽可能地顺序执行,从而提高CPU预取指令的正确率。

#define LIKELY(x) __builtin_expect(!!(x), 1) //x很可能为真
#define UNLIKELY(x) __builtin_expect(!!(x), 0) //x很可能为假

From #

__builtin_constant_p(x)

Content #

判断x是否在编译时就可以被确定为常量。如果x为常量,该函数返回1,否则返回0。

#define __swab16(x)        \
(__builtin_constant_p((__u16)(x)) ?  \
___constant_swab16(x) :      \
__fswab16(x))

From #

奔跑吧Linux内核——入门篇

packed属性(GCC)

Content #

packed属性可以使变量或者结构体成员使用最小的对齐方式,对变量是以字节对齐,对域是以位对齐。

struct test
{
    char a;
    int x[2] __attribute__ ((packed));
};

x成员使用了packed属性,它会存储在变量a后面,所以这个结构体一共占用9字节。

From #

奔跑吧Linux内核——入门篇