转自:http://www.cnblogs.com/hazir/p/array_initialization.html

前几天看内核中系统调用代码,在系统调用向量表初始化中,有下面这段代码写的让我有点摸不着头脑:

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
/*
* Smells like a compiler bug -- it doesn't work
* when the & below is removed.
*/
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_32.h>
};

咱先不管上面代码的意思,先来回顾一下 C 语言中数组初始化的相关知识,然后再回头来理解上面这段代码。

数组初始化

C 语言中数组的初始化,可以在定义时就给出其初始值,以逗号隔开,用花括号括起来,例如:

int my_array[5] = {0, 1, 2, 3, 4};

当然你可以不用显示地去初始化所有的元素,例如,下面的代码就是显示初始化了数组的前三项,后面两项默认为0:

int my_array[5] = {0, 1, 2};

在 C89 标准中,要求按照数组中元素固定的顺序对数组的元素进行初始化;然而在 ISO C99 中,你可以以任意的顺序对数组元素初始化,只是需要给出数组元素所在的索引号;当然 GNU 编译器 GCC 对 C89 进行了扩展,也允许这么做。为了指明初始特殊的数组元素,需要在元素值前加上 [index] =,如:

int my_array[6] = { [4] = 29, [2] = 15 };
或者写成:
int my_array[6] = { [4] 29, [2] 15 }; //省略到索引与值之间的=,GCC 2.5 之后该用法已经过时了,但 GCC 仍然支持
两者均等价于:
int my_array[6] = {0, 0, 15, 0, 29, 0};

GNU 还有一个扩展:在需要将一个范围内的元素初始化为同一值时,可以使用 [first ... last] = value 这样的语法:

int my_array[100] = { [0 ... 9] = 1, [10 ... 98] = 2, 3 };

这是将my_array数组的第0~9个元素初始化为1, 第10~98个元素初始化为2, 第99个元素初始化为3(你也可以显示地写成[99] = 3)。** 注意 **:在语法中... 两边必须要留有空格符。

回到上面

对数组特定元素进行初始化我之前还真没遇到过,但也是 C 标准所支持的。内核中系统调用表是指根据系统调用号来找到系统调用的函数入口地址,结合上面数组初始化这个语法点,再回头看看上面系统调用表的定义:

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_32.h>
};

先对表中所有 __NR_syscall_max+1 项初始化为指向 sys_ni_syscall 的函数,该函数只返回 -ENOSYS,表示该系统调用未实现。接下来包含一个头文件#include <asm/syscalls_32.h>,该文件是在编译时生成的,内容为:

__SYSCALL_I386(0, sys_restart_syscall, sys_restart_syscall)
__SYSCALL_I386(1, sys_exit, sys_exit)
__SYSCALL_I386(2, sys_fork, stub32_fork)
__SYSCALL_I386(3, sys_read, sys_read)
__SYSCALL_I386(4, sys_write, sys_write)
__SYSCALL_I386(5, sys_open, compat_sys_open)
...

__SYSCALL_I386 是一个宏定义:

#define __SYSCALL_I386(nr, sym, compat) [nr] = sym,

这样上面的系统调用表定义就展开为:

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = &sys_ni_syscall,
[0] = sys_restart_syscall,
[1] = sys_exit,
[2] = sys_fork,
[3] = sys_read,
//...
};

当用户进程发生系统调用,通过软中断 int 0x80 或者 sysenter 指令陷入到内核态,首先保存寄存器,然后检查系统调用号是否合法,最后跳转到相应的内核系统调用函数中执行:

ENTRY(system_call)
pushl_cfi %eax # 保存原始 eax
SAVE_ALL # 保存寄存器帧
GET_THREAD_INFO(%ebp)
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) # 检查是否跟踪系统调用标志
jnz syscall_trace_entry
cmpl $(NR_syscalls), %eax # 检查系统调用号是否合法
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4) # 调用相应函数,等价于 call sys_call_table[%eax*4]

上面就是系统调用的进入过程,比较简单,这里只是说明了我们之前定义的系统调用表 sys_call_table 的用处。

再举一例

内核中还有其他地方应用到此种初始化数组的方法:

/* There are machines which are known to not boot with the GDT
being 8-byte unaligned. Intel recommends 16 byte alignment. */
static const u64 boot_gdt[] __attribute__((aligned(16))) = {
/* CS: code, read/execute, 4 GB, base 0 */
[GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
/* DS: data, read/write, 4 GB, base 0 */
[GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
/* TSS: 32-bit tss, 104 bytes, base 4096 */
/* We only have a TSS here to keep Intel VT happy;
we don't actually use it for anything. */
[GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
};

这是对系统启动时对全局符号表GDT的初始化。


参考资料:

本博客的内容如果没有标注转载字样,均属个人原创!欢迎学习交流,如果觉得有价值,欢迎转载,转载请注明出处,谢谢!


邮箱:haifenglinying#yahoo.cn (#->@)

个人主页:www.hazirguo.com

Linux Kernel代码艺术——数组初始化【转】的更多相关文章

  1. Linux Kernel代码艺术——数组初始化

    前几天看内核中系统调用代码,在系统调用向量表初始化中,有下面这段代码写的让我有点摸不着头脑: const sys_call_ptr_t sys_call_table[__NR_syscall_max+ ...

  2. Linux Kernel代码艺术——系统调用宏定义

    我们习惯在SI(Source Insight)中阅读Linux内核,SI会建立符号表数据库,能非常方便地跳转到变量.宏.函数等的定义处.但在处理系统调用的函数时,却会遇到一些麻烦:我们知道系统调用函数 ...

  3. Linux Kernel 代码艺术——编译时断言

    本系列文章主要写我在阅读Linux内核过程中,关注的比较难以理解但又设计巧妙的代码片段(不关注OS的各个模块的设计思想,此部分我准备写在“深入理解Linux Kernel” 系列文章中),一来通过内核 ...

  4. Linux Kernel 代码艺术——编译时断言【转】

    转自:http://www.cnblogs.com/hazir/p/static_assert_macro.html 本系列文章主要写我在阅读Linux内核过程中,关注的比较难以理解但又设计巧妙的代码 ...

  5. 使用linux kernel代码编译perf工具

    环境:Qemu + ARMv8 perf是一款综合性分析工具,大到系统全局性性能,再小到进程线程级别,甚至到函数及汇编级别. 在内核源码目录下执行编译脚本: #!/bin/bash cross_com ...

  6. 在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6564592 在前一篇文章提到,从源代码树下载下 ...

  7. Linux kernel Vhost-net 和 Virtio-net代码详解

    场景 Host上运行qemu kvm虚拟机,其中虚拟机的网卡类型为virtio-net,而Host上virtio-net backend使用vhost-net 数据包进入虚拟机代码分析 首先看vhos ...

  8. Linux kernel的中断子系统之(七):GIC代码分析

    返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...

  9. Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback

    Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback 上一篇# Linux Kernel文件系统写I/O流程代码分析(一),我们看到Buffered IO,写操作写入到 ...

随机推荐

  1. 【bzoj4897】[Thu Summer Camp2016]成绩单 区间dp

    题目描述 给你一个数列,每次你可以选择连续的一段,付出 $a+b\times 极差^2$ 的代价将其删去,剩余部分拼到一起成为新的数列继续进行此操作.求将原序列全部删去需要的最小总代价是多少. 输入 ...

  2. (转)rabbitmq的web管理界面无法使用guest用户登录

    转至http://www.cnblogs.com/mingaixin/p/4134920.html 安装最新版本的rabbitmq(3.3.1),并启用management plugin后,使用默认的 ...

  3. 【刷题】BZOJ 1030 [JSOI2007]文本生成器

    Description JSOI交给队员ZYX一个任务,编制一个称之为"文本生成器"的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版.该软件可以随机生 ...

  4. [SCOI2016]幸运数字 线性基

    题面 题面 题解 题面意思非常明确:求树上一条链的最大异或和. 我们用倍增的思想. 将这条链分成2部分:x ---> lca , lca ---> y 分别求出这2个部分的线性基,然后合并 ...

  5. 【BZOJ3712】Fiolki(并查集重构树)

    [BZOJ3712]Fiolki(并查集重构树) 题面 BZOJ 题解 很神仙的题目. 我们发现所有的合并关系构成了一棵树. 那么两种不同的东西如果产生反应,一定在两个联通块恰好联通的时候反应. 那么 ...

  6. C内存对齐问题-bus error!总线错误!其实是 字符串字面量修改问题!

    最近写个小程序,出现bus error! int main(void) { /** * char :1个字节 * char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也 ...

  7. 【莫队】【P3901】 数列找不同

    Description 现在有一个长度为\(~n~\)的数列\(~A_1~,~A_2~\dots~A_n~\),\(~Q~\)个询问\(~[l_i~,~r_i]~\),每次询问区间内是否有元素相同 I ...

  8. 图像格式转换之BMP格式转换为JPG格式

    // bmp2jpg.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include "jpeglib.h" #inc ...

  9. phpstorm 自定义属性没语法提示的问题

    在右侧Options勾选上Custom HTML tag attributes,并且在下面的输入框输入自定义属性列表,逗号分隔.

  10. 图片虚拟目录--即图片保存在window硬盘上面

    这个是图片保存在电脑的硬盘上面的图片上传设置,既不是在web工程中,也不是在专门的图片服务器中,下面是配置方法: r 这里的Document base 我们这里设置为F:\images 如果在浏览器访 ...