Linux Kernel代码艺术——数组初始化【转】
转自: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代码艺术——数组初始化【转】的更多相关文章
- Linux Kernel代码艺术——数组初始化
前几天看内核中系统调用代码,在系统调用向量表初始化中,有下面这段代码写的让我有点摸不着头脑: const sys_call_ptr_t sys_call_table[__NR_syscall_max+ ...
- Linux Kernel代码艺术——系统调用宏定义
我们习惯在SI(Source Insight)中阅读Linux内核,SI会建立符号表数据库,能非常方便地跳转到变量.宏.函数等的定义处.但在处理系统调用的函数时,却会遇到一些麻烦:我们知道系统调用函数 ...
- Linux Kernel 代码艺术——编译时断言
本系列文章主要写我在阅读Linux内核过程中,关注的比较难以理解但又设计巧妙的代码片段(不关注OS的各个模块的设计思想,此部分我准备写在“深入理解Linux Kernel” 系列文章中),一来通过内核 ...
- Linux Kernel 代码艺术——编译时断言【转】
转自:http://www.cnblogs.com/hazir/p/static_assert_macro.html 本系列文章主要写我在阅读Linux内核过程中,关注的比较难以理解但又设计巧妙的代码 ...
- 使用linux kernel代码编译perf工具
环境:Qemu + ARMv8 perf是一款综合性分析工具,大到系统全局性性能,再小到进程线程级别,甚至到函数及汇编级别. 在内核源码目录下执行编译脚本: #!/bin/bash cross_com ...
- 在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6564592 在前一篇文章提到,从源代码树下载下 ...
- Linux kernel Vhost-net 和 Virtio-net代码详解
场景 Host上运行qemu kvm虚拟机,其中虚拟机的网卡类型为virtio-net,而Host上virtio-net backend使用vhost-net 数据包进入虚拟机代码分析 首先看vhos ...
- Linux kernel的中断子系统之(七):GIC代码分析
返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...
- Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback
Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback 上一篇# Linux Kernel文件系统写I/O流程代码分析(一),我们看到Buffered IO,写操作写入到 ...
随机推荐
- 第129天:node.js安装方法
node.js安装方法 第一步:双击node.js安装包开始安装,注意64位和32位,按照自己的进行安装 第二步:在安装过程中一直选择next,在选择安装目录时,大多数默认安装在C盘,我安装在了D盘, ...
- 【EF】EF Code-First数据迁移
Code-First数据迁移 首先要通过NuGet将EF升级至最新版本. 新建MVC 4项目MvcMigrationDemo 添加数据模型 Person 和 Department,定义如下: usi ...
- 如何在VScode中添加代码片段
拿 VUE 举例,新建 VUE 文件,输入前缀,出现代码段 文件 ---> 首选项 ---> 用户代码片段 在输入框中输入 vue ,找到 vue.json ,然后在 vue.json ...
- BZOJ 1835 基站选址(DP+线段树)
# include <cstdio> # include <cstring> # include <cstdlib> # include <iostream& ...
- 洛谷 P3924 康娜的线段树 解题报告
P3924 康娜的线段树 题目描述 小林是个程序媛,不可避免地康娜对这种人类的"魔法"产生了浓厚的兴趣,于是小林开始教她\(OI\). 今天康娜学习了一种叫做线段树的神奇魔法,这种 ...
- Active Directory中获取域管理员权限的攻击方法
Active Directory中获取域管理员权限的攻击方法 译:by backlion 0x00 前言 攻击者可以通过多种方式在Active Directory中获得域管理员权限, ...
- 安装redis环境
1 安装redis至 /home/www/redis目录下 [root@cuijian www]# tar zxf redis-2.8.13.tar.gz [root@cuijian www]# cd ...
- centos上部署git
安装Git #安装依赖环境 [root@wys01 wys]# yum install curl-devel expat-devel gettext-devel openssl-devel zlib- ...
- Spring MVC 向前台页面传值-ModelAndView
ModelAndView 该对象中包含了一个model属性和一个view属性 model:其实是一个ModelMap类型.其实ModelMap是一个LinkedHashMap的子类 view:包含了一 ...
- selenium - webdriver - cookie操作
WebDriver提供了操作Cookie的相关方法,可以读取.添加和删除cookie信息. WebDriver操作cookie的方法: get_cookies(): 获得所有cookie信息. get ...