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

Lua笔记(8)——C API对table的操作

先看下面的C程序(hello.c):

#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    lua_getglobal(L, "io");
    lua_getfield(L, -1, "write");
    lua_pushstring(L, "Hello\n");

    lua_pcall(L, 1, 0, 0);
    return 0;
}

lua_getglobal函数定义如下:

void lua_getglobal (lua_State *L, const char *name);
Pushes onto the stack the value of the global name.

是把全局变量的值push到堆栈里。在这个程序中,即是把io这个table的值存到堆栈中。

lua_getfield函数定义如下:

void lua_getfield (lua_State *L, int index, const char *k);
Pushes onto the stack the value t[k], where t is the value at the given index. As in Lua, this function may trigger a metamethod for the "index" event

也即把堆栈中指定索引(indextable中的keyk的值push到堆栈。在程序中,即把io.write函数存到堆栈中。

接下来lua_pushstring是把io.write函数的参数push到堆栈,而lua_pcall即执行io.write函数。

执行结果:

[root@Fedora test]# ./hello
Hello

Linux kernel 笔记 (3) ——page和zone

MMU(Memory Management Unit)用来管理内存的最小单位是一个物理页面(physical page,在32-bit系统上通常每个页面为4k64-bit系统上为8k)。Linux kernel使用struct page结构体(定义在<linux/mm_types.h>)来代表每个物理页面:

struct page {
    /* First double word block */
    unsigned long flags;        /* Atomic flags, some possibly
                     * updated asynchronously */
    struct address_space *mapping;  /* If low bit clear, points to
                     * inode address_space, or NULL.
                     * If page mapped as anonymous
                     * memory, low bit is set, and
                     * it points to anon_vma object:
                     * see PAGE_MAPPING_ANON below.
                     */
    ......
}  

受制于一些硬件限制,有些物理页面不能用来进行某种操作。所以kernel把页面分成不同的zone
ZONE_DMA:用来进行DMA操作的页面;
ZONE_DMA32:也是用来进行DMA操作的页面,不过仅针对32-bit设备;
ZONE_NORMAL:包含常规的,用来映射的页面;
ZONE_HIGHMEM:包含不能永久地映射到kernel的地址空间(address space)的页面。
还有其它的zone定义(例如:ZONE_MOVABLE)。zone_type定义在 <linux/mmzone.h>文件里:

enum zone_type {
#ifdef CONFIG_ZONE_DMA
    /*
     * ZONE_DMA is used when there are devices that are not able
     * to do DMA to all of addressable memory (ZONE_NORMAL). Then we
     * carve out the portion of memory that is needed for these devices.
     * The range is arch specific.
     *
     * Some examples
     *
     * Architecture     Limit
     * ---------------------------
     * parisc, ia64, sparc  <4G
     * s390         <2G
     * arm          Various
     * alpha        Unlimited or 0-16MB.
     *
     * i386, x86_64 and multiple other arches
     *          <16M.
     */
    ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
    /*
     * x86_64 needs two ZONE_DMAs because it supports devices that are
     * only able to do DMA to the lower 16M but also 32 bit devices that
     * can only do DMA areas below 4G.
     */
    ZONE_DMA32,
#endif
    /*
     * Normal addressable memory is in ZONE_NORMAL. DMA operations can be
     * performed on pages in ZONE_NORMAL if the DMA devices support
     * transfers to all addressable memory.
     */
    ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
    /*
     * A memory area that is only addressable by the kernel through
     * mapping portions into its own address space. This is for example
     * used by i386 to allow the kernel to address the memory beyond
     * 900MB. The kernel will set up special mappings (page
     * table entries on i386) for each page that the kernel needs to
     * access.
     */
    ZONE_HIGHMEM,
#endif
    ZONE_MOVABLE,
    __MAX_NR_ZONES
};  

如果CPU体系结构允许DMA操作访问任何地址空间,那就没有ZONE_DMA。同理,Intel X86_64处理器可以访问所有的内存空间,也就不存在ZONE_HIGHMEM。 关于“High memory”,可以参考这篇文章

Linux kernel 笔记 (2) ——kernel代码的一些特性

Linux kernel代码相对于user-space程序的一些特性:
(1)kernel代码不能访问标准C程序库(libc)和头文件(例如打印使用printk,而不是printf);
(2)kernel代码是用GNU C实现的,不是ANSI C(比如:内联函数,内联汇编,分支预测等等);
(3)kernel程序没有内存保护;
(4)避免在kernel代码中引入浮点运算;
(5)kernel程序为每个进程预分配的堆栈空间很小;
(6)因为kernel程序是可抢占的(preemptive),支持symmetrical multiprocessing (SMP)和异步中断(asynchronous interrupt),所以开发kernel程序时要时刻考虑并发和同步问题;
(7)可移植性对于kernel代码非常重要。

Scala语言的特性

Scala语言有以下几个特性:

(1)Scala代码运行在JVM上,因此可以利用Java语言的丰富资源。
(2)Scala语言是一门“纯面向对象的编程语言(pure object-oriented programming(OOP) language)”:每个变量都是对象,每个运算符都是方法;
(3)Scala语言也是一门函数式编程语言(functional programming(FP) language),因此可以把函数赋给变量。