查看原文
其他

[笨叔点滴8] GNU C语言的扩展

小笨叔 奔跑吧Linux社区 2019-04-24

会迟到了,领导脸上有点绿,问:几点了

我说:3900点

领导:我问你什么时候

我:收盘的时候

领导怒了:出去!

我也急:长生了,全跌停,出。。出。。出不去”


昨天笨叔点滴7里给小伙伴留了小小的疑问?为啥子小明的程序像打了“真”的狂犬疫苗一样乱跳呢?有的朋友说,它没找到真正的栈,看下面这个图。



小明以为通过get_current_pt_regs()函数可以获取第一个进程init_task的栈,可是init_task这个变量,小明同学只是定义成全局的一个变量,它是存在data区的,但是小明天真的以为这个init_task分配了8KB的空间,实际上并没有,仅仅是初始化了一个struct task_struct结构体而已,它的实际大小也只有sizeof(task_struct)。所以通get_current_pt_regs()函数去找栈,那必定会改写了其他数据区的内容哟。


那在Linux内核中是怎么做的? (下面是内核的做法,当然现在内核是存放的thread_info的数据结构,但原理是类似的)

1. 在vmlinux.lds的链接脚本中data段里预留了8KB的空间给第一个进程用作内核栈。


2. 然后定义一个union,并且把 init_task这个变量 安装到这个".data..init_task"段里。



所以,就好比上面那张A股图,你以为“底”是在这里,其实真正的“底”是在那边。所以C语言时刻提醒我们需要关注内存情况,思考每个变量它究竟存在内存的什么地方,处处都是“雷”,和A股一样,很容易踩雷,那天就要被ST了。


今天我们和大家分享一下GNU C语言的一些扩展。




01 GCC 扩展



GCCC编译器除了支持ANSI C标准之外,还对C语言进行了很多的扩充。这些扩充对代码优化、目标代码布局以及安全检查等方面提供了很强的支持,因此支持GNU扩展的C语言称为GNU C语言。Linux内核采用GCC编译器,所以Linux内核的代码自然使用了很多GCC的新扩充特性。本章介绍一些GCCC语言扩充的新特性,希望读者在学习Linux内核的时候需要特别留意和关注。

1)     语句表达式

GNU C语言中,括号里的复合语句可以看作是一个表达式,称为语句表达式。在一个语句表达式里,可以使用循环、跳转和局部变量等。这个特性通常用在宏定义中,可以让宏定义变的更安全,如比较两个值的大小。

#define max(a,b) ((a) >(b) ? (a) : (b))

 

上述代码会安全问题,ab有可能会计算两次,比如a传入i++b传入j++。在GNU C中,如果知道ab的类型,可以这样写这个宏。

#define maxint(a,b) \

  ({int _a = (a), _b = (b); _a > _b ? _a :_b; })

 

如果你不知道ab的类型,还可以使用typeof类转换宏。

 

<include/linux/kernel.h>

 

#define min(x, y) ({            \

     typeof(x) _min1 = (x);         \

     typeof(y) _min2 = (y);         \

     (void) (&_min1 == &_min2);     \

     _min1 < _min2 ? _min1 : _min2; })

 

typeof也是GNU C语言的一个扩充用法,可以用来构造新的类型,通常和语句表达式一起使用。下面是一些例子。

typeof (*x) y;

typeof (*x) z[4];

typeof (typeof (char *)[4])m;

 

第一句是声明yx指针指向的类型。第二句声明z是一个数组,其中数组的类型是x指针指向的类型。第三句声明m是一个指针数组,和“char *m[4]”声明是一样的。

 

2)     零长数组

GNU C允许使用变长数组,在定义数据结构的时候非常有用。

 

<mm/percpu.c>

 

struct pcpu_chunk {

     struct list_head   list;       /* linked to pcpu_slot lists */

     unsigned long      populated[];    /* populated bitmap */

};

 

数据结构最后一个元素定义为零长度数组,不占结构体空间。这样,我们可以根据对象大小动态的去分配结构的大小。

 

struct line {

  int length;

  char contents[0];

};

 

struct line *thisline =malloc(sizeof(struct line) + this_length);

thisline->length =this_length;

 

如上述例子,structline数据结构定义了一个intlength变量和一个变长数组contents[0],这个struct line数据结构的大小只包含int类型的大小,不包含contents的大小,也就是sizeof (struct line) = sizeof(int)。创建结构体对象时,可根据实际的需要指定这个可变长数组的长度,并分配相应的空间,如上述实例代码分配了this_length个字节的内存,并且可以通过contents[index]来访问第index个地址的数据。

 

3)     case范围

GNU C支持指定一个case的范围作为一个标签,如:

case low ... high:

case 'A' ... 'Z':

 

这里lowhigh表示一个区间范围,另外在ASCII字符代码也非常有用,下面是Linux内核中的代码例子。

 

<arch/x86/platform/uv/tlb_uv.c>

 

static int local_atoi(constchar *name)

{

     int val = 0;

 

     for (;; name++) {

         switch (*name) {

         case '0' ... '9':

             val = 10*val+(*name-'0');

             break;

         default:

             return val;

         }

     }

}

 

另外还可以用整形数来表示范围,但是这里需要注意在“...”两边需要有空格,否则编译出错。

<drivers/usb/gadget/udc/at91_udc.c>

 

static intat91sam9261_udc_init(struct at91_udc *udc)

{

 

     for (i = 0; i < NUM_ENDPOINTS; i++) {

         ep = &udc->ep[i];

 

         switch (i) {

         case 0:

             ep->maxpacket = 8;

             break;

         case 1 ... 3:

             ep->maxpacket = 64;

             break;

         case 4 ... 5:

             ep->maxpacket = 256;

             break;

         }

     }

 

}

 

4)     标号元素

标准C语言要求数组或结构体初始化值必须以固定顺序出现,但在GNU C语言中,可以通过指定索引或结构体成员名来初始化,不必按照原来的固定顺序进行初始化。

 

结构体成员的初始化在Linux内核中经常使用,如在设备驱动中初始化file_operations数据结构,下面是Linux内核中的一个代码例子。

 

<drivers/char/mem.c>

 

static const structfile_operations zero_fops = {

     .llseek     = zero_lseek,

     .read       =new_sync_read,

     .write      = write_zero,

     .read_iter  =read_iter_zero,

     .aio_write  =aio_write_zero,

     .mmap       = mmap_zero,

};

 

如上述代码中的zero_fops的成员llseek初始化为zero_lseek函数,read成员初始化为new_sync_read函数,依次类推。当file_operations数据结构的定义发生变化时,这种初始化方法依然能保证已知元素的正确性,对于未初始化成员的值为0或者NULL


未完待续。。。


[往期精彩]

[旗舰篇] 第一季旗舰篇资料汇总

[笨叔点滴1] 为什么do_page_fault函数里代码需要判断用户态还是内核态?

[笨叔点滴2] 为啥子ARM32体系结构中每个处理模式都有一个单独的栈?

[笨叔点滴3] “栈”谁便宜了?

[笨叔点滴4]“栈”谁便宜了2

[笨叔点滴5] git rebase和git merge究竟有啥区别?

[笨叔点滴6] 叔,这个git咋玩啊?

[笨叔点滴7] 再也回不去的C语言

LinuxCon 2018北京游记(1)

LinuxCon2018北京笨叔笨游记 2

《奔跑吧linux内核》配套资源迁移到码云上

考点来了:4月18号视频更新

代码导读之如何使用qemu来单步调试head.S

代码导读之内存管理初始化 - 启动汇编

视频更新:内存管理代码框架导读

DMA那些事儿

私密VIP群答疑

高级运维必杀技:如何图形化单步调试RHEL/Centos 7里的内核?

首发:Meltdown漏洞分析与实践

[奔跑吧Linux内核] 故乡

致敬Beyond

文章已于修改

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存