Linux kernel 笔记 (6) ——__init和__initdata

__init__initdata定义在include/linux/init.h

/* These macros are used to mark some functions or 
 * initialized data (doesn't apply to uninitialized data)
 * as `initialization' functions. The kernel can take this
 * as hint that the function is used only during the initialization
 * phase and free up used memory resources after
 *
 * Usage:
 * For functions:
 * 
 * You should add __init immediately before the function name, like:
 *
 * static void __init initme(int x, int y)
 * {
 *    extern int z; z = x * y;
 * }
 *
 * If the function has a prototype somewhere, you can also add
 * __init between closing brace of the prototype and semicolon:
 *
 * extern int initialize_foobar_device(int, int, int) __init;
 *
 * For initialized data:
 * You should insert __initdata between the variable name and equal
 * sign followed by value, e.g.:
 *
 * static int init_variable __initdata = 0;
 * static const char linux_logo[] __initconst = { 0x32, 0x36, ... };
 *
 * Don't forget to initialize data not at file scope, i.e. within a function,
 * as gcc otherwise puts the data into the bss section and not into the init
 * section.
 * 
 * Also note, that this data cannot be "const".
 */

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init      __section(.init.text) __cold notrace
#define __initdata  __section(.init.data)
#define __initconst __constsection(.init.rodata)

__init修饰函数时表明函数只在kernel初始化阶段使用(函数放在.init.text区),在初始化完成后,这些函数所占用的内存就可以回收利用。举例如下:

static int __init parse_dmar_table(void)
{
    ....
}

__initdata作用类似于__init,只不过它修饰变量,并且存放在.init.data区。

PCI总线相关术语

本文列举PCI总线的相关术语:

Agent:可以操作总线的设备(device)或实体(entity)。
Master:可以发起一次总线事务(transaction)的agent
Transaction:在PCI上下文中,一次transaction包含一次address phase和一次或多次data phase。也被称为burst transfer
Initiator:获得总线控制权的master,也就是发起transactionagent
Target:在address phase认识到自己addressagentTarget会响应transaction
Central Resource:主机系统上提供总线支持(如产生CLK信号等等),总线仲裁等等功能的元素。
Latency:在一次transaction中,两次状态转换之间消耗的时钟周期。Latency用来度量一个agent响应另外一个agent请求所花的时间,因此是性能的一个度量指标。

 

Lua笔记(11)——closure

Lua中,closure就是一个函数加上其它能够让这个函数正确访问non-local变量(也称之为upvalue)的一切(Simply put, a closure is a function plus all it needs to access non-local variables correctly.)。参看下面程序:

function newCounter()
    local i = 0
    return function()
        i = i + 1
        return i
        end
end

c1 = newCounter()
print(c1())
print(c1())

c2 = newCounter()
print(c2())

newCounter()被称作factory,它用来创建closure functionclosure function所访问的变量i。执行代码如下:

1
2
1

可以看到,每次调用newCounter(),都会返回一个新的closure,而这些closure都会访问自己的i变量。

Linux kernel IOMMU代码分析笔记(2)

系统BIOS会检查平台的remapping硬件功能,并且会在主机的系统地址空间内定位memory-mapped remapping硬件寄存器。BIOS通过DMA Remapping Reporting (DMAR) ACPI table向系统软件报告remapping硬件单元信息。

除了RSDPFACS,所有的ACPI table定义都包含一个共同的header定义:

struct acpi_table_header {
    char signature[ACPI_NAME_SIZE]; /* ASCII table signature */
    u32 length;     /* Length of table in bytes, including this header */
    u8 revision;        /* ACPI Specification minor version number */
    u8 checksum;        /* To make sum of entire table == 0 */
    char oem_id[ACPI_OEM_ID_SIZE];  /* ASCII OEM identification */
    char oem_table_id[ACPI_OEM_TABLE_ID_SIZE];  /* ASCII OEM table identification */
    u32 oem_revision;   /* OEM revision number */
    char asl_compiler_id[ACPI_NAME_SIZE];   /* ASCII ASL compiler vendor ID */
    u32 asl_compiler_revision;  /* ASL compiler version */
};

DMA Remapping table定义如下(可以看到包含有acpi_table_header):

struct acpi_table_dmar {
    struct acpi_table_header header;    /* Common ACPI table header */
    u8 width;       /* Host Address Width */
    u8 flags;
    u8 reserved[10];
};

对所有的DMA Remapping结构体,都会包含一个type和一个length

/* DMAR subtable header */

struct acpi_dmar_header {
    u16 type;
    u16 length;
};

DMA Remapping Hardware单元(类型为0)为例:

/* 0: Hardware Unit Definition */

struct acpi_dmar_hardware_unit {
    struct acpi_dmar_header header;
    u8 flags;
    u8 reserved;
    u16 segment;
    u64 address;        /* Register Base Address */
};

其它的还有acpi_dmar_reserved_memoryacpi_dmar_atsr等等定义。

Linux kernel 笔记 (5) ——spin_lock_irqsave

Linux kernel中最基本的lock原语就是spin lock(自旋锁)。实例代码如下:

static DEFINE_SPINLOCK(xxx_lock);

unsigned long flags;

spin_lock_irqsave(&xxx_lock, flags);
... critical section here ..
spin_unlock_irqrestore(&xxx_lock, flags);

以上代码总是安全的(包括可以用在中断处理函数),并可以在UPSMP上都可以正常工作。

Linux kernel 笔记 (4) ——memory barrier

由于编译器和处理器可以打乱程序的执行顺序,所以有些时候,我们需要“memory barrier”来保证内存访问指令的执行顺序(loadstore指令):

(1)rmb():提供一个读(load)操作的“memory barrier”,保证读操作不会跨越rmb()进行reorder。即rmb()之前的读(load)操作不会reorder rmb()之后,而rmb()之后的读(load)操作不会reorder rmb()之前。

(2)wmb():提供一个写(store)操作的“memory barrier”,同rmb类似,不过wmb()是保证写操作不会跨越wmb()进行reorder

(3)mb():保证读写操作都不会跨越mb()进行reorder

(4)read_barrier_depends()rmb()的一个变种,但仅保证有依赖关系的读操作不会跨越rmb()进行reorder

其它还有smp_rmb()smp_wmb()搜索。

UP VS SMP

UP(Uni-Processor):系统只有一个处理器单元,即单核CPU系统。

SMP(Symmetric Multi-Processors):系统有多个处理器单元。各个处理器之间共享总线,内存等等。在操作系统看来,各个处理器之间没有区别。

要注意,这里提到的“处理器单元”是指“logic CPU”,而不是“physical CPU”。举个例子,如果一个“physical CPU”包含2core,并且一个core包含2hardware thread。则一个“处理器单元”就是一个hardware thread

Lua笔记(10)——变参函数

Lua中把...称为变参表达式(vararg expression)。参看下面程序:

function fwrite(fmt, ...)
    io.write(string.format(fmt, ...))
end

fwrite("%s\n", "Hello world!")

要注意,...作为函数参数时,要作为唯一一个或者最后一个。

{...}会返回一个包含所有参数的数组(一个table)。参看下面程序:

function add(...)
    local t = {...}
    local s = 0
    for i = 1, #t do
        s = s + t[i]
    end
    print(s)
end

add(1, 2, 3, 4)

执行结果:

10

Linux kernel IOMMU代码分析笔记(1)

Linux kernel代码版本是3.10

intel-iommu.h头文件定义了root-entry table address寄存器:

#define DMAR_RTADDR_REG 0x20    /* Root entry table */

DMAR_RTADDR_REG只在iommu_set_root_entry这个函数中使用(这个函数作用是更新root-entry table的地址):

static void iommu_set_root_entry(struct intel_iommu *iommu)
{
    void *addr;
    u32 sts;
    unsigned long flag;

    addr = iommu->root_entry;

    raw_spin_lock_irqsave(&iommu->register_lock, flag);
    dmar_writeq(iommu->reg + DMAR_RTADDR_REG, virt_to_phys(addr));

    writel(iommu->gcmd | DMA_GCMD_SRTP, iommu->reg + DMAR_GCMD_REG);

    /* Make sure hardware complete it */
    IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
              readl, (sts & DMA_GSTS_RTPS), sts);

    raw_spin_unlock_irqrestore(&iommu->register_lock, flag);
}

DMAR_RTADDR_REG存储的是root-entry table的物理地址(virt_to_phys()函数把virtual address转成physical address)。整个root-entry table4KiB大小,并且要求起始地址是“页面对齐的(page-alignedpage长度是4KiB)”,所以DMAR_RTADDR_REG12 bits0。更新DMAR_RTADDR_REG代码如下:

dmar_writeq(iommu->reg + DMAR_RTADDR_REG, virt_to_phys(addr));

更新完DMAR_RTADDR_REG寄存器,还要把global command寄存器的SRTPSet Root Table Pointer)位置1

writel(iommu->gcmd | DMA_GCMD_SRTP, iommu->reg + DMAR_GCMD_REG);

最后读取Global Status寄存器的RTPSRoot Table Pointer Status)位,确认更新成功:

IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
                  readl, (sts & DMA_GSTS_RTPS), sts);

Lua笔记(9)——函数传参和返回值

Lua中,调用函数要加上括号(即使没有参数也不例外)。只有以下情况可以不加:函数只有一个参数,且参数是字符串或是table constructor

> print "Hello world!"
Hello world!
> print {x=10}
table: 0x1d81810

传给函数多余的实参(argument)会被丢弃,多余的形参(parameter)会赋值nil

> function f(a, b) print(a, b) end
> f(1)
1       nil
> f(1, 2)
1       2
> f(1, 2, 3)
1       2

根据实际情况,Lua会自动调整函数返回值的数量:
(1)当调用函数作为一条statement时,会丢弃所有的返回值;
(2)当调用函数作为一个表达式时,只会保留第一个返回值;
(3)只有当调用函数作为一系列表达式(a list of expressions)中的最后一个(或是唯一一个)时,才会得到所有的返回值。所谓的一系列表达式(a list of expressions)有四种情况:多重赋值(multiple assignments),函数调用传参(arguments to function calls),构建tabletable constructor)和return语句。

针对(1),(2),请看下面程序(test.lua):

function f()
        return 1, 2
end

f()

a, b = f(), 3
print(a, b)

执行结果如下:

[root@localhost ~]# ./test.lua
1       3

针对(3),请看下面程序(test.lua):

function f()
        return 1, 2
end

function g(a, b)
        print(a, b)
end

function h()
        return f()
end

a, b = f()
print(a, b)

g(f())

t = {f()}
print(t[1], t[2])

print(h())

执行结果如下:

[root@localhost ~]# ./test.lua
1       2
1       2
1       2
1       2

注意:在调用的函数外面再加上一层括号,可以强制函数只返回一个结果:

function f()
        return 1, 2
end

print(f())
print((f()))

执行结果:

[root@localhost ~]# ./test.lua
1       2
1