Blog

元素编号

Content #

标准C语言规定数组和结构体必须按照固定顺序对成员(或元素)进行初始化赋值。GNU C语言为使数组和结构体初始化更加自由,特意放宽了此限制,使得数组可以在初始化期间借助下标对某些元素(元素可以是连续的或者不连续的)进行赋值,并在结构体初始化过程中允许使用成员名直接对成员进行赋值。

与此同时,GNU C语言还允许数组和结构体按照任意顺序对成员(或元素)进行初始化赋值。以下是两者的初始化实例:

unsigned char data[MAX] =
{
    [0]=10,
    [10 ... 50]=100,
    [55]=55,
};
struct file_operations ext2_file_operations=
{
    open:ext2_open,
    close:ext2_close,
};

Linux 2.6以后的内核源码已经开始使用上述初始化扩展。读者在编写Linux驱动时,推荐采用以下初始化方式:

struct file_operations ext2_file_operations=
{
    .read=ext2_read,
    .write=ext2_write,
};

From #

一个64位操作系统的设计与实现

可变参数宏

Content #

在GNU C语言中宏函数允许使用可变参数类型,例如:

#define pr_debug(fmt, arg...) \
printk(fmt, ##arg)

在这段代码中,当可变参数arg被忽略或为空时,printk函数中的##操作将迫使预处理器去掉它前面的那个逗号。如果在调用宏函数时,确实提供了若干个可变参数,那么GNU C会把这些可变参数放到逗号后面,使其能够正常工作。

From #

一个64位操作系统的设计与实现

Referring to a Type with typeof

Content #

typeof有两种用法:

  1. 作用于表达式:
typeof (x[0](1))

This assumes that x is an array of pointers to functions; the type described is that of the values of the functions.

  1. 作用于类型:
typeof (int *)

Here the type described is that of pointers to int.

typeof定义maximum宏:

#define max(a,b) \
  ({ typeof (a) _a = (a); \
      typeof (b) _b = (b); \
    _a > _b ? _a : _b; })

From #

case关键字支持范围匹配

Content #

GNU C语言允许case关键字匹配一个数值范围,由此可以取代多级的if条件检测语句。以下这段代码的执行条件是待匹配字符为小写字母:

case 'a'...'z':   /from 'a' to 'z'/
break;

下面是Linux内核中的代码例子。

//<arch/x86/platform/uv/tlb_uv.c>
static int local_atoi(const char *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 int at91sam9261_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;
        }
    }
}

From #

奔跑吧Linux内核——入门篇一个64位操作系统的设计与实现

...

零长数组

Content #

GNU C语言允许使用长度为0的数组来增强结构体的灵活性,其在动态创建结构体时有着非常明显的优势,例如下面这几行代码:

struct s {int n; long d[0]; };
int m = 数值;
struct s p = malloc(sizeof (struct s) + sizeof (long [m]));

struct s结构体中的数组成员变量d在作用上与指针极为相似,但是在为指针p开辟存储空间时却仅需执行一次malloc函数。由此可见,柔性数组成员不仅能够减少内存空间的分配次数提高程序执行效率,还能有效保持结构体空间的连续性。

From #

一个64位操作系统的设计与实现

序号占位符

Content #

序号占位符是输入/输出操作约束的数值映射,每个内嵌汇编表达式最多只有10 条输入/输出约束,这些约束按照书写顺序依次被映射为序号0~9。如果指令部分想引用序号占位符,必须使用百分号%前缀加以修饰,例如序号占位符%0对应第1个操作约束,序号占位符%1对应第2个操作约束,依次类推。指令部分为了区分序号占位符和寄存器,特使用两个百分号(%%)对寄存器加以修饰。在编译时,编译器会将每个占位符代表的表达式替换到相应的寄存器或内存中。

指令部分在引用序号占位符时,可以根据需要指定操作位宽是字节或者字,也可以指定操作的字节位置,即在%与序号占位符之间插入字母b表示操作最低字节,或插入字母h表示操作次低字节。

输出操作数按照顺序从%0开始,然后是输入操作数接着输出操作数的数量继续编号。例如,如果有一个输出操作数和四个输入操作数,那么输出是%0,输入是%1、 %2、%3、%4。

From #

一个64位操作系统的设计与实现

操作约束修饰符(Constraint Modifier Characters)

修饰符 #

  1. “=” Means that this operand is written to by this instruction: the previous value is discarded and replaced by new data.
  2. “+” Means that this operand is both read and written by the instruction. “=“和”+“必须是第一个字符的位置。
  3. “&” an earlyclobber operand, which is written before the instruction is finished using the input operands. 只能写在输出约束部分的第二个字符位置上,即只能位于=和 + 之后,它告

诉编译器不得为任何输入操作表达式分配该寄存器。因为编译器会在输入部分赋值前,先对 &符号修饰的寄存器进行赋值,一旦后面的输入操作表达式向该寄存器赋值,将会造成输入和输出数据混乱。

补充说明只有在输入约束中使用过模糊约束(使用q、r或g等约束缩写)时,在输出约束中使用符号&修饰才有意义!如果所有输入操作表达式都明确指派了寄存器,那么输出约束再使用符号 & 就没有任何意义。如果没有使用修饰符 &,那就意味着编译器将先对输入部分进行赋值,当指令部分执行结束后,再对输出部分进行操作。

From #

一个64位操作系统的设计与实现

操作约束(Constraints for asm Operands)

Content #

每个输入/输出表达式都必须指定自身的操作约束。操作约束的类型可以细分为寄存器约束、内存约束和立即数约束。在输出表达式中,还有限定寄存器操作的修饰符。

寄存器约束 #

限定了表达式的载体是一个寄存器,这个寄存器可以明确指派,亦可模糊指派再由编译器自行分配。寄存器约束可使用寄存器的全名,也可以使用寄存器的缩写名称,如下所示:

  __asm__ __volatile__("movl %0, %%cr0"::"eax"(cr0));
  __asm__ __volatile__("movl %0, %%cr0"::"a"(cr0));

如果使用寄存器的缩写名称,那么编译器会根据指令部分的汇编代码来确定寄存器的实际位宽。

内存约束 #

限定了表达式的载体是一个内存空间,使用约束名m表示。例如以下内嵌汇编表达式:

   __asm__ __volatile__ ("sgdt %0":"=m"(__gdt_addr)::);
   __asm__ __volatile__ ("lgdt %0"::"m"(__gdt_addr));

立即数约束 #

只能用于输入部分,它限定了表达式的载体是一个数值,如果不想借助任何寄存器或内存,那么可以使用立即数约束,比如下面这行代码:

   __asm__ __volatile__("movl %0, %%ebx"::"i"(50));

使用约束名i限定输入表达式是一个整数类型的立即数,如果希望限定输入表达式是一个浮点数类型的立即数,则使用约束名F。立即数约束只能使用在输入部分。

From #

一个64位操作系统的设计与实现

Clobbers(损坏部分)

Content #

损坏部分描述了在指令部分执行的过程中,将被修改的寄存器、内存空间或标志寄存器,并且这些修改部分并未在输出部分和输入部分出现过,格式为:

“损坏描述”, “损坏描述”, ……

如果需要声明多个寄存器,则必须使用逗号“, ”将它们分隔开,这点与输入/输出部分一致。

■ 寄存器修改通知。这种情况一般发生在寄存器出现于指令部分,又不是输入/输出操作表达式指定的寄存器,更不是编译器为r或g约束选择的寄存器。如果该寄存器被指令部分所修改,那么就应该在损坏部分加以描述,比如下面这行代码:

  __asm__ __volatile__ ("movl %0, %%ecx"::"a"(__tmp):"cx");

这段汇编表达式的指令部分修改了寄存器ECX的值,却未被任何输入/输出部分所记录,那么必须在损坏部分加以描述,一旦编译器发现后续代码还要使用它,便会在内嵌汇编语句的过程中做好数据保存与恢复工作。如果未在损坏部分描述,则很可能会影响后续程序的执行结果。

注意,已在损坏部分声明的寄存器,不能作为输入/输出操作表达式的寄存器约束,也不会被指派为q 、 r 、 g约束的寄存器。如果在输入/输出操作表达式中已明确选定寄存器,或者使用q 、 r 、 g约束让编译器指派寄存器时,编译器对这些寄存器的状态非常清楚,它知道哪些寄存器将会被修改。除此之外,编译器对指令部分修改的寄存器却一无所知。

■ 内存修改通知。除了寄存器的内容会被篡改外,内存中的数据同样会被修改。如果一个内嵌汇编语句的指令部分修改了内存数据,或者在内嵌汇编表达式出现的地方,内存数据可能发生改变,并且被修改的内存未使用m约束。此时,应该在损坏部分使用字符串memory,向编译器声明内存会发生改变。

如果损坏部分已经使用memory对内存加以约束,那么编译器会保证在执行汇编表达式之后,重新向寄存器装载已引用过的内存空间,而非使用寄存器中的副本,以防止内存与副本中的数据不一致。

■ 标志寄存器修改通知。当内嵌汇编表达式中包含影响标志寄存器R|EFLAGS的指令时,必须在损坏部分使用cc来向编译器声明这一点。

From #

一个64位操作系统的设计与实现

我们看到的是经过改造的形象

Content #

你能在下面这个松饼烘焙模具中装下几个松饼? 图 松饼烘焙模具可以装几个松饼?

你是不是看到上排中间的那个凸了出来,而不是凹陷下去呈洼状?你的视觉是专门用来识别物体的,尽管投射到视网膜上的图像是平面的,但大脑可以帮助你将世界感知成三维的。

如果你把这本书上下颠倒过来,就会发现情况刚好相反。你脑中的视觉皮层会做出预期:光线来自烤箱里向下照射的单一光源,就像太阳一样。那么,这些阴影就只能解释为光线照射到了一个凹洼的底部或是一个凸出的球体的侧边。当同样的阴影被添加到图像中时,阴影还是这样的光源产生的,于是你的大脑会自动创建一个三维的凸形(向外弯曲)或凹形(向内弯曲,像一个碗),即使图像显示在一张平面的纸上。你的大脑又一次向你展示了现实世界的一个经过改造和优化的版本。

大脑呈现给我们的并不总是这个世界的客观形象,而是经过改造的形象。你觉得下面两个方块哪个看起来更黑? 图 哪个方块看起来更黑?

你应该会觉得右边的方块看起来更黑,因为我们的视觉就是这样运作的。我们的视觉会增加相对于背景的对比度。大脑会为我们改善现状,让我们看到更多。其实这两个方块的颜色完全相同。这一点只要你把背景遮住,只看方块,就会发现。这个例子是许多事例中的一个,它们迫使我们意识到,我们并没有看到世界本来的样子。我们看到的,是这个世界更好的版本。

From #

大脑帝国