linux内核——2.linux内核基础知识——摘录

本文介绍了Linux内核的基础知识,包括宏内核与微内核架构的优缺点,以及Linux内核如何利用预抓取技术优化性能。详细讲解了预抓取函数__builtin_prefetch的用法,并展示了预抓取的包装器函数示例。此外,还探讨了用于链表操作的宏,如offsetof、list_add系列函数,以及链表的合并与旋转操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

come from : 奔跑吧linux内核

宏内核架构的优点是设计简洁和性能比较好,而微内核架构的优势也很明显,比如稳定性和实时性等。

第二章 Linux内核基础知识

#define min(x, y) ({ \
       typeof(x) _min1 = (x); \
       typeof(y) _min2 = (y); \
        (void) (&_min1 == &_min2); \
        _min1 < _min2 ? _min1 : _min2; \ 
})

其中(void) (&_min1 == &_min2);有什么作用?

这句的作用是比较两个操作数的类型是否相同,防止不同类型的数据进行比较。
若不相同,则会在编译时给出警告:comparison of distinct pointer types lacks a cast。

两个指针类型不同是不能进行相互比较的。

struct line {
    int length;
    char contents[0];  // 变长数组
};
struct line *thisline = malloc(sizeof(struct line) + this_length);
thisline->length = this_length;

#include <linux/printk.h>
#define pr_debug(fmt, ...) \
    dynamic_pr_debug(fmt, ##__VA_ARGS__)
...可变参数, __VA_ARGS__ 是编译器保留字段 
预处理时把参数传递给宏,当宏调用展开时,实际参数就传递给dynamic_pr_debug函数了
int libcfs_debug_msg(struct libcfs_debug_msg_data *msgdata, const char *format1, ...)
__attribute__((format(printf, 2, 3));

__attribute__ format
该__attribute__属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。
format的语法格式为:
format (archetype, string-index, first-to-check)
          format属性告诉编译器,按照printf, scanf, 
strftime或strfmon的参数表格式规则对该函数的参数进行检查。“archetype”指定是哪种风格;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。
具体使用格式如下:
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中参数m与n的含义为:
m:第几个参数为格式化字符串(format string);
n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的呢,后面会提到;
在使用上,__attribute__((format(printf,m,n)))是常用的,而另一种却很少见到。下面举例说明,其中myprint为自己定义的一个带有可变参数的函数,其功能类似于printf:
//m=1;n=2
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l,const char *format,...) 
__attribute__((format(printf,2,3)));
需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,例如:
//m=3;n=4
extern void myprint(int l,const char *format,...) 
__attribute__((format(printf,3,4)));
其原因是,类成员函数的第一个参数实际上一个“隐身”的“this”指针。(有点C++基础的都知道点this指针,不知道你在这里还知道吗?)
这里给出测试用例:attribute.c,代码如下:
1:
2:extern void myprint(const char *format,...) 
__attribute__((format(printf,1,2)));
3:
4:void test()
5:{
6:     myprint("i=%d/n",6);
7:     myprint("i=%s/n",6);
8:     myprint("i=%s/n","abc");
9:     myprint("%s,%d,%d/n",1,2);
10:}

运行$gcc –Wall –c attribute.c attribute后,输出结果为:

attribute.c: In function `test':
attribute.c:7: warning: format argument is not a pointer (arg 2)
attribute.c:9: warning: format argument is not a pointer (arg 2)
attribute.c:9: warning: too few arguments for format

如果在attribute.c中的函数声明去掉__attribute__((format(printf,1,2))),再重新编译,既运行$gcc –Wall –c attribute.c attribute后,则并不会输出任何警告信息。
注意,默认情况下,编译器是能识别类似printf的“标准”库函数。
struct gib_user_info {
    __u32 spu_userversion;
    __u64 spu_base_info;
}__aligned(8); 
编译器以8字节对齐的方式来分配qib_user_info这个数据结构

struct test{
    char a;
    int x[2]  __attribute__((packed));
};
x成员使用了packed属性,它会存储在变量a后面,所以这个结构体一共占用了9字节。
packed 属性可以使变量或者结构体成员使用最小的对齐方式,对变量以字节对齐,对域是以位对齐。

list_for_each_entry(p, &tty_drivers, tty_drivers),这是一个宏,即
#define list_for_each_entry(pos, head, member)    \
 for (pos = list_entry((head)->next, typeof(*pos), member); \
      prefetch(pos->member.next), &pos->member != (head);  \
      pos = list_entry(pos->member.next, typeof(*pos), member))
其实就是一个for语句。
而里面的prefetch(pos->member.next)也是一个宏
即:#define prefetch(x) __builtin_prefetch(x) 
以下是__builtin_prefetch(x)的资料(可以与之前的unlikely,likely结合看):

另一种重要的性能改进方法是把必需的数据缓存在接近处理器的地方。缓存可以显著减少访问数据花费的时间。大多数现代处理器都有三类内存:

●一级缓存通常支持单周期访问
●二级缓存支持两周期访问
●系统内存支持更长的访问时间
为了尽可能减少访问延时并由此提高性能,最好把数据放在最近的内存中。手工执行这个任务称为预抓取。GCC 通过内置函数 __builtin_prefetch 支持数据的手工预抓取。在需要数据之前,使用这个函数把数据放到缓存中。如下所示,__builtin_prefetch 函数接收三个参数:

●addr   数据的地址  // 预取数据的地址
●rw 参数,使用它指明预抓取数据是为了执行读操作,还是执行写操作 //1 表示可写, 0 表示只读
●locality 参数,使用它指定在使用数据之后数据应该留在缓存中,还是应该清除  // 0表示读取完addr之后不用保留在cache中

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

    Linux 内核经常使用预抓取。通常是通过宏和包装器函数使用预抓取。清单 6 是一个辅助函数示例,它使用内置函数的包装器(见 ./linux/include/linux/prefetch.h)。这个函数为流操作实现预抓取机制。使用这个函数通常可以减少缓存缺失和停顿,从而提高性能。


清单 6. 范围预抓取的包装器函数

ifndef ARCH_HAS_PREFETCH
#define prefetch(x) __builtin_prefetch(x)
#endif

static inline void prefetch_range(void *addr, size_t len)
{
#ifdef ARCH_HAS_PREFETCH
char *cp;
char *end = addr + len;

for (cp = addr; cp < end; cp += PREFETCH_STRIDE)
prefetch(cp);
#endif
}

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

也就是说,使用likely(),执行 if 后面的语句的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

__builtin_expect(exp, c): 这里的意思是 exp==c 的概率很大,用来引导GCC编译器进行条件分支预测。

开发人员知道很可能执行哪个分支,并将最有可能执行的分支告诉编译器,让编译器优化指令序列,使指令尽可能地顺序执行,从而提高cpy预取指令的正确率。

 

2.3 Linux 内核常用的数据结构和算法

struct list_head {

  struct list_head *next, *prev;

};

#define LIST_HEAD_INIT(name) { &(name), &(name) }
list_head这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?

其实list_head不是拿来单独用的,它一般被嵌到其它结构中,如:

struct file_node{

  char c;

  struct list_head node;

};

此时list_head就作为它的父结构中的一个成员了,当我们知道list_head的地址(指针)时,我们可以通过list.c提供的宏 list_entry 来获得它的父结构的地址。
下面我们来看看list_entry的实现:

#define list_entry(ptr,type,member)\

  container_of(ptr,type,member)

 

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

#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 )

我们知道 0 地址内容是不能访问的,但 0地址的地址我们还是可以访问的, 这里用到一个取址运算符

(TYPE *)0 它表示将 0地址强制转换为TYPE类型,((TYPE *)0)-> MEMBER 也就是从0址址找到TYPE 的成员MEMBER 。

 

双向链表的遍历——list_for_each

//注:这里prefetch 是gcc的一个优化选项,也可以不要

#define list_for_each(pos, head) \

         for (pos = (head)->next; prefetch(pos->next), pos != (head); \

                 pos = pos->next)

生成双向链表的头结点——LIST_HEAD()

LIST_HEAD() -- 生成一个名为name的双向链表头节点

#define LIST_HEAD(name) \

struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)

{

  list->next = list;

  list->prev = list;

}

双向链表的插入操作 -- list_add()
将new所代表的结构体插入head所管理的双向链表的头节点head之后: (即插入表头)

从list中删除结点——list_del()

判断链表是否为空(如果双向链表head为空则返回真,否则为假)——list_empty()


/*
注:这个list.h 是为了配合示例程序而建的,内容来自:linux/include/linux/list.h 和相关文件
*/
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
 
struct list_head {
         struct list_head *next, *prev;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

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

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})


static inline void INIT_LIST_HEAD(struct list_head *list)
{
        list->next = list;
        list->prev = list;
}

static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next)
{
        next->prev = new;
        new->next = next;
        new->prev = prev;
        prev->next = new;
}


static inline void list_add(struct list_head *new, struct list_head *head)
{
        __list_add(new, head, head->next);
}
 
 
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
        next->prev = prev;
        prev->next = next;
}
 
static inline void list_del(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        entry->next = NULL;
        entry->prev = NULL;
}


#define prefetch(x) __builtin_prefetch(x)


//注:这里prefetch 是gcc的一个优化,也可以不要
#define list_for_each(pos, head) \
         for (pos = (head)->next; prefetch(pos->next), pos != (head); \
                 pos = pos->next)

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

#endif

static inline void list_add(struct list_head *new, struct list_head *head);

static inline void list_add_tail(struct list_head *new, struct list_head *head);

static inline void list_del(struct list_head *entry)

static inline void list_replace_init(struct list_head *old, struct list_head *new);

static inline void list_move(struct list_head *list, struct list_head *head)

static inline void list_move_tail(struct list_head *list,struct list_head *head)

list_empty()函数和list_empty_careful()函数都是用来检测链表是否为空的。

static inline void list_rotate_left(struct list_head*head)  //旋转链表的第一个节点到最后

static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) //分割链表

list:将剪切的结点要加进来的链表

head:被剪切的链表

entry:所指位于由head所指领头的链表内,它可以指向head,但是这样的话,head就不能被剪切了,在代码中调用了INIT_LIST_HEAD(list)。

链表的合并提供的接口有四个:

static inline void list_splice(const struct list_head *list, struct list_head *head)

static inline void list_splice_tail(struct list_head *list, struct list_head *head)

static inline void list_splice_init(struct list_head *list, struct list_head *head)

static inline void list_splice_tail_init(struct list_head *list ,struct list_head *head)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值