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),因此可以把函数赋给变量。

什么是“Intel VT-d”?

Intel VT-d(以下简称VT-d)”代表“Intel Virtualization Technology for Directed I/O”。“VT(Virtualization Technology)”泛指Intel所有的虚拟化技术,而“VT-d”则是虚拟化技术解决方案中的一种。VT-d的整体思想就是用硬件方式支持隔离和限制对设备的访问。

VT-d有以下主要功能:
a)分配I/O设备:这个功能允许管理员根据需求,灵活地为虚拟机分配I/O设备。
b)DMA remapping:支持针对虚拟机DMA访问的地址转换。
c)Interrupt remapping:支持虚拟机对设备中断的路由和隔离。
d)可靠性功能:记录关于DMAInterrupt的错误访问。

参考资料:
Understanding VT-d: Intel Virtualization Technology for Directed I/O

DMA Remapping简介

DMA(Direct Nemory Access) Remapping是一种用来限制硬件设备只能使用DMA访问预先分配的内存区域(domain or physical memory regions)的技术。DMA Remapping会把DMA请求里的地址转化成正确的物理内存地址,同时还会检查设备是否允许访问指定的内存。请看下图:

dma_remapping

虚拟机的操作系统(Guest OS)所提供的物理地址称为Guest Physical Address (GPA) ,它不一定与实际的物理地址一致,也就是Host Physical Address (HPA),而DMA技术则要求访问真实的物理地址。DMA Remapping技术可以把Guest OS提供的GPA转化成HPA,然后数据就可以直接发送到Guest OS的缓冲区(buffer)了。

主机平台(host platform)可以支持一个或多个DMA remapping硬件单元(hardware unit),每个硬件单元remapping从它控制的作用域内发出的DMA remapping请求。主机固件(BIOS)需要把每个DMA remapping硬件单元报给系统软件(比如操作系统)。

DMA remapping硬件单元使用source-id来标示发出DMA请求的设备。对一个PCI Express设备,source-id就是resource identifier

 ________________________________________________________
|____Bus(8 bits)_________|__Device(5 bits)|_func(3 bits)_|

Root-entry作为最顶层的数据结构,会把某特定PCI总线上的设备映射到对应的domain。一个context-entry会把一个地址总线上的某个具体设备映射到对应的domain。参考下图:

root-entry

每个root-entry会有一个指向一个context-entry的表的指针,而每个context-entry则会包含如何用来进行地址转化的结构。

Linux kernel 笔记 (1) ——CPU在做什么?

In fact, in Linux, we can generalize that each processor is doing exactly one of three things at any given moment:
a) In user-space, executing user code in a process
b) In kernel-space, in process context, executing on behalf of a specific process
c) In kernel-space, in interrupt context, not associated with a process, handling an
interrupt

Linux中,任何时候,CPU都在做下面三件事中的一件:

a)运行进程的用户空间代码;
b)运行进程的内核空间代码;
c)处理中断(也是工作在内核空间,但不与任何进程关联)。

Go语言字符串浅析

Go语言中,字符串就是一个只读(read-only)的,可以包含任意字节(byte)的切片(slice)。由于Go源码文件都是使用UTF-8编码的,所以直接在源码中输入的字符串也都是用UTF-8编码的(前提是字符串里没有byte-level escapes,即字节层面上的转义)。请看下面这个例子:

package main
import "fmt"

func main() {
    s := "日志log"

    fmt.Println(len(s))
}

执行结果:

9

s是一个UTF-8编码的字符串,“日志”两个汉字各占3个字节,所以一共是9个字节。

再看一个例子:

package main
import "fmt"

func main() {
    s:="\xFF\xFF"

    fmt.Println(len(s))
}

执行结果:

2

虽然0xFF不是一个合法的UTF-8编码字符,但是因为字符串可以包含任意字节,所以s仍是一个合法的字符串。

Go语言引入rune类型(实际就是int32)来表示字符(character),“日志log”这个字符串包含了5个字符,9个字节。 可以用for range循环来遍历UTF-8编码字符串的每个字符:

package main
import "fmt"

func main() {
    s := "日志log"
    for index, runeValue := range s {
        fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
}

执行结果:

U+65E5 '日' starts at byte position 0
U+5FD7 '志' starts at byte position 3
U+006C 'l' starts at byte position 6
U+006F 'o' starts at byte position 7
U+0067 'g' starts at byte position 8

参考资料:
Strings, bytes, runes and characters in Go

printk函数简介

printk函数是Linux kernel开发中用来打印log的常用函数,同使用在用户空间程序的printf函数很类似。实现代码如下:

asmlinkage int printk(const char *fmt, ...)
{
    va_list args;
    int r;

#ifdef CONFIG_KGDB_KDB
    if (unlikely(kdb_trap_printk)) {
        va_start(args, fmt);
        r = vkdb_printf(fmt, args);
        va_end(args);
        return r;
    }
#endif
    va_start(args, fmt);
    r = vprintk_emit(0, -1, NULL, 0, fmt, args);
    va_end(args);

    return r;
}

举个例子(注意KERN_ALERT"DEBUG ..."之间没有逗号):

printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);

KERN_ALERT代表log的级别,参考kern_levels.h

#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions */
#define KERN_ERR    KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO   KERN_SOH "6"    /* informational */
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages */ 

数字越大,代表优先级越低。

另外关于printk格式化字符串形式,参考printk-formats.txt

/proc/sys/kernel/printk这个文件显示了和printk相关的log级别设置:

[root@localhost kernel]# cat /proc/sys/kernel/printk
7       4       1       7

4个值的含义依次如下:
console_loglevel:当前consolelog级别,只有更高优先级的log才被允许打印到console
default_message_loglevel:当不指定log级别时,printk默认使用的log级别;
minimum_console_loglevelconsole能设定的最高log级别;
default_console_loglevel:默认的consolelog级别。

请看下面这个例子:

[root@localhost kernel]# echo 8 > /proc/sys/kernel/printk
[root@localhost kernel]# cat /proc/sys/kernel/printk
8       4       1       7

可以看到,console_loglevel的级别修改成了8

参考资料:
Debugging by printing

什么是IOMMU?

在计算机领域,IOMMU(Input/Output Memory Management Unit)是一个内存管理单元(Memory Management Unit),它的作用是连接DMA-capable I/O总线(Direct Memory Access-capable I/O Bus)和主存(main memory)。传统的内存管理单元会把CPU访问的虚拟地址转化成实际的物理地址。而IOMMU则是把设备(device)访问的虚拟地址转化成物理地址。为了防止设备错误地访问内存,有些IOMMU还提供了访问内存保护机制。参考下图:

282px-MMU_and_IOMMU.svg

IOMMU的一个重要用途是在虚拟化技术(virtualization):虚拟机上运行的操作系统(guest OS)通常不知道它所访问的host-physical内存地址。如果要进行DMA操作,就有可能破坏内存,因为实际的硬件(hardware)不知道guest-physicalhost-physical内存地址之间的映射关系。IOMMU根据guest-physicalhost-physical内存地址之间的转换表(translation table),re-mapping硬件访问的地址,就可以解决这个问题。

另外,在AMDVIRTUALIZING IO THROUGH THE IO MEMORY MANAGEMENT UNIT (IOMMU)文档中,也有一个更全面的总结图:

iommu

参考资料:
IOMMU