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

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的初始化。


参考资料:

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

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

    转自:http://www.cnblogs.com/hazir/p/array_initialization.html 前几天看内核中系统调用代码,在系统调用向量表初始化中,有下面这段代码写的让我有点 ...

  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. C# 之 DataReader 和 DataSet 的区别

    本文转载自:http://www.cnblogs.com/xinaixia/p/4920630.html 1. 获取数据的方式[1]DataReader 为在线操作数据, DataReader会一直占 ...

  2. 【手记】未能从程序集System.Net.Http.Formatting中加载类型System.Net.Http.Formatting.FormUrlEncodedMediaTypeFormatter

    实际报错如图: 如果你跟我一样是在折腾Asp.Net WebApi 2.x时遇到这个问题,请参看如下办法: 删除现有System.Net.Http.Formatting引用(如果引用了的话) 重新引用 ...

  3. wpf模仿QQ表情

    效果图: style: <Style x:Key="LBXITEM_VERTICAL" TargetType="ListBoxItem"> < ...

  4. 【配置属性】—Entity Framework 对应表字段的类型的设定配置方法

    摘自:http://www.cnblogs.com/nianming/archive/2012/11/07/2757997.html Entity Framework Code First的默认行为是 ...

  5. Java中, for循环经典例子

    循环的两种应用:穷举和迭代. break跳出整个循环 一.穷举:将所有可能的情况都走一遍,用if筛选出符合条件. 百鸡百钱: 一只公鸡1文钱,一只母鸡2文钱,一只小鸡半文钱,需要买100只鸡, 正好花 ...

  6. 2>&1 linux

    2>&1使用 2>&1使用 一 相关知识 1)默认地,标准的输入为键盘,但是也可以来自文件或管道(pipe |).2)默认地,标准的输出为终端(terminal),但是也可 ...

  7. 【JAVA并发编程实战】10、并发程序的测试

    1.产生随机数 package cn.study.concurrency.ch12; public class Util { public static int xorShift(int y) { / ...

  8. 转移博客到xinqiyang.freeflare.com了,这里会继续更新.

    hi.... 欢迎大家来到这里,这里将转移到github page搭建的博客 http://xinqiyang.freeflare.com 了,现在习惯于实用markdown来写东西了,这样可以脱离浏 ...

  9. 多级弹出菜单jQuery插件ZoneMenu

    ZoneMenu是一个菜单jQuery插件,只需占用页面上的一个小区域,却可以实现多级菜单. 在线体验:http://keleyi.com/jq/zonemenu/ 点击这里下载 完整HTML文件代码 ...

  10. Java语言面向对象的一些基本特点

    封装 1. 面向对象语言使用class封装属性和方法. 2. 属性一般要求定义为private,封装保护 继承. 继承的例子随处可见.需要符合is-a关系,父类更加通用,子类更加具体.. 在子类中使用 ...