安装SLES(Suse Linux Enterprise Server)
时,在选择安装软件界面时,选择“C/C++ Compiler and Tools
”:
就会自动安装这个版本的Suse kernel
代码:
在这篇帖子中,提到pairs
和ipairs
的区别:
pairs() returns an iterator that will return the key and value of every key in the table, in no particular order. Usually, k and v are used to hold the key and value it returns. This is used to perform actions on each item in a table in turn, like when printing the contents of each value in the table.
ipairs() is very similar to pairs(), except that it will start at table[1] and iterate through all numerically indexed entries until the first nil value. It does this in order, which can be useful when you’re looking for the first item in a list that meets certain criterion.
pairs
遍历任意table
,返回的key
和value
没有什么顺序。而ipairs
只会返回key
是数字的元素。ipairs
通常用在访问数组上。
参考下面这个程序:
t = {hello = 100, 200, 300}
print("k, v in pairs:")
for k, v in pairs(t) do
print(k, v)
end
print("k, v in ipairs:")
for k, v in ipairs(t) do
print(k, v)
end
执行结果如下:
k, v in pairs:
1 200
2 300
hello 100
k, v in ipairs:
1 200
2 300
可以看到,pairs
可以遍历所有的key-value
对,而ipairs
只会访问key
是数字的元素。
这篇笔记来自于stackoverflow
的一篇帖子,答案如下:
From the documentation(http://www.kernel.org/doc/Documentation/devices.txt):
/dev/tty Current TTY device
/dev/console System console
/dev/tty0 Current virtual console
In the good old days /dev/console was System Administrator console. And TTYs were users' serial devices attached to a server.
Now /dev/console and /dev/tty0 represent current display and usually are the same. You can override it for example by adding console=ttyS0 to grub.conf. After that your /dev/tty0 is a monitor and /dev/console is /dev/ttyS0.
An exercise to show the difference between /dev/tty and /dev/tty0:
Switch to the 2nd console by pressing Ctrl+Alt+F2. Login as root. Type "sleep 5; echo tty0 > /dev/tty0". Press Enter and switch to the 3rd console by pressing Alt+F3.
Now switch back to the 2nd console by pressing Alt+F2. Type "sleep 5; echo tty > /dev/tty", press Enter and switch to the 3rd console.
You can see that "tty" is the console where process starts, and "tty0" is a always current console.
早些时候,/dev/console
是系统管理员控制台,而TTYs
则代表用户连接服务器的串行设备。而现在,/dev/console
和/dev/tty0
均指当前的显示设备,并且通常情况下是一样的。你可以修改/dev/console
所关联的设备。举个例子,在grub.conf
中加入console=ttyS0
。则现在,/dev/tty0
所关联的是显示器,而dev/console
则关联/dev/ttyS0
。
/dev/tty
是当前进程控制的tty
设备,而tty0
则是当前的控制台。当你在一个终端执行“sleep 5; echo tty0 > /dev/tty0
”命令后,切换到其它终端,则tty0
会在你切换后的终端显示。而执行“sleep 5; echo tty > /dev/tty
”命令后,无论切换到那个终端,tty
始终会在输入命令的终端显示。
以LDD3
中Compiling and Loading
一节的编译模块的Makefile
为例:
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
当在命令行执行make
命令时(当前工作目录即模块源文件所在目录),因为当前模块所在目录里没有定义KERNELRELEASE
,所以执行else
部分,即把KERNELDIR
和PWD
变量赋值。
接下来执行“$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
”命令。-C
选项的含义是把目录切换到KERNELDIR
目录下,然后读取KERNELDIR
目录下的Makefile
。M
选项是在编译modules
再切换回模块所在目录。此时由于KERNELRELEASE
变量已经定义,即可以得知需要编译obj-m
。
per-CPU
变量顾名思义,即当你声明一个per-CPU
变量时,当前系统上的每个CPU
都会有一份当前变量的copy
。使用per-CPU
变量好处是访问它几乎不需要加锁,因为每个CPU
都有一份copy
。此外,CPU
可以把这个变量放在自己的cache
里,访问起来会特别快。定义per-CPU
变量方法如下:
DEFINE_PER_CPU(type, name);
如果per-CPU
变量是数组,则定义方式如下:
DEFINE_PER_CPU(type[length], array);
per-CPU
变量可以导出,供其它模块使用:
EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
要在其它模块使用per-CPU
变量,则需要声明:
DECLARE_PER_CPU(type, name);
访问per-CPU
变量可以使用get_cpu_var(var)
和set_cpu_var(var)
这两个macro
:
/* <linux/percpu.h>*/
/*
* Must be an lvalue. Since @var must be a simple identifier,
* we force a syntax error here if it isn't.
*/
#define get_cpu_var(var) (*({ \
preempt_disable(); \
&__get_cpu_var(var); }))
/*
* The weird & is necessary because sparse considers (void)(var) to be
* a direct dereference of percpu variable (var).
*/
#define put_cpu_var(var) do { \
(void)&(var); \
preempt_enable(); \
} while (0)
因为kernel
线程是允许preemption
的,所以在get_cpu_var
中需要调用preempt_disable
,并且要和put_cpu_var
配对使用。
访问另一个CPU
的per-CPU
变量:
per_cpu(variable, int cpu_id);
在/dev
目录下执行ls -lt
命令:
上面红框框起来的部分就是设备号,前面是major
,后面是minor
。 major
号表示设备所使用的驱动,而minor
号则表示具体的设备。在上图中,tty
的驱动都是driver 4
,而利用minor
号区别不同的tty
设备。 另外,通过/proc/devices
文件也可以看到设备所使用的驱动,即major
号:
linux-a21w:/dev # cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
......
关于dev_t
,major
和minor
号定义如下(kernel
版本是4.0
):
/* <linux/types.h>: */
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
/* <linux/kdev_t.h> */
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
dev_t
占32 bit
长,其中高12
位是major
,低20
位是minor
。
获取设备号的两种方法:
(1)预先指定设备号:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
from
包含major
和minor
,通常情况下minor
指定为0
。count
指定连续设备号的数量,name
指定设备的名字。register_chrdev_region
实现如下:
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
可以看到register_chrdev_region
即是把from
开始连续count
个设备号(dev_t
类型,包含major
和minor
)都注册。
举个例子(/drivers/tty/tty_io.c
):
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console")
(2)动态分配设备号(推荐使用):
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev
是传出参数,为动态获得的设备号;firstminor
指定第一个minor
;count
和name
同register_chrdev_region
的参数定义。alloc_chrdev_region
实现如下:
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
举个例子(/drivers/watchdog/watchdog_dev.c
):
alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
释放设备号:
void unregister_chrdev_region(dev_t first, unsigned int count);
“soft lockup - CPU# stuck ...
”bug
的kernel log
类似这样:
[ 28.124107] BUG: soft lockup - CPU#0 stuck for 23s! [init:1]
[ 28.124720] Modules linked in:
[ 28.125247] Supported: Yes
[ 28.125763] Modules linked in:
[ 28.126277] Supported: Yes
[ 28.126774]
[ 28.127264] Pid: 1, comm: init Not tainted 3.0.101-63-xen #1
[ 28.127765] EIP: 0061:[<c00ded0a>] EFLAGS: 00000202 CPU: 0
[ 28.128002] EIP is at handle_mm_fault+0x18a/0x2b0
[ 28.128002] EAX: 0002bfc1 EBX: 00000000 ECX: 00000000 EDX: 00000000
[ 28.128002] ESI: 2bfc1067 EDI: 00000000 EBP: ebfc6200 ESP: ebc35d48
[ 28.128002] DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: e021
[ 28.128002] Process init (pid: 1, ti=ebc08000 task=ebc32ce0 task.ti=ebc34000)
[ 28.128002] Stack:
[ 28.128002] ebfc1778 ebfc6200 00000029 0002bfc1 00000000 080efc90 ebfc2570 ebfc9e40
[ 28.128002] ec7bd000 ebc35dfc 00000003 ebfc2570 080efc90 c0350ad4 00000029 00000100
[ 28.128002] 00000008 00000003 ebfc9e78 ebc32ce0 ebfc9e40 00000000 00000029 00000003
[ 28.128002] Call Trace:
[ 28.128002] [<c0350ad4>] do_page_fault+0x1f4/0x4b0
[ 28.128002] [<c034df54>] error_code+0x30/0x38
[ 28.128002] [<c01da35f>] clear_user+0x2f/0x50
[ 28.128002] [<c01480d4>] load_elf_binary+0xae4/0xc30
[ 28.128002] [<c01094d1>] search_binary_handler+0x1e1/0x2e0
[ 28.128002] [<c01097b4>] do_execve_common+0x1e4/0x280
[ 28.128002] [<c000a9c2>] sys_execve+0x52/0x80
[ 28.128002] [<c035443e>] ptregs_execve+0x12/0x18
[ 28.128002] [<c034dc3d>] syscall_call+0x7/0x7
[ 28.128002] [<c000933f>] kernel_execve+0x1f/0x30
[ 28.128002] [<c000424e>] init_post+0xde/0x130
[ 28.128002] [<c057d638>] kernel_init+0x160/0x18f
[ 28.128002] [<c0354526>] kernel_thread_helper+0x6/0x10
[ 28.128002] Code: 89 f2 89 f8 81 e2 00 f0 ff ff 25 ff 0f 00 00 89 54 24 0c 89 44 24 10 8b 44 24 0c 8b 54 24 10 0f ac d0 0c 89 44 24 0c 8b 44 24 0c <c1> ea 0c 89 54 24 10 c1 e0 05 03 44 24 20 e8 b3 90 ff ff 8b 54
......
这个Bug
背后的原理是这样的:
Linux kernel
针对每个CPU
都有一个watchdog
进程。使用ps -ef | grep watctdog
可以看到:
[nan@longriver ~]$ ps -ef | grep watchdog
root 6 2 0 Apr20 ? 00:00:16 [watchdog/0]
root 10 2 0 Apr20 ? 00:00:11 [watchdog/1]
root 14 2 0 Apr20 ? 00:00:10 [watchdog/2]
root 18 2 0 Apr20 ? 00:00:09 [watchdog/3]
nan 6726 4608 0 17:28 pts/28 00:00:00 grep watchdog
watchdog
进程会搜集所监控的CPU
的关于使用时间的信息([watchdog/X]
中的X
代表监控的CPU ID
),并把这些信息存在kernel
中。kernel
中有专门的interrupt
函数会调用softlockup
计数器,并把当前的时间与之前kernel
中存储的时间值进行比较。如果相差超过一个门限值,则就认为watchdog
进程没有获得足够的执行时间用来更新kernel
中的信息,也就是CPU
一直被其它task
占据着。这会被kernel
认为是一种不正常的现象,就会打印出如上所示的call trace
,register
等等信息。
Luajit
提供的FFI
库(ffi.*
)允许Lua
代码调用外部的C
函数和使用C
数据结构。但是默认情况下,FFI
库不会被加载和初始化。因此建议在每个使用FFI
库的Lua
文件开头加载:
local ffi = require("ffi")
看下面这个例子:
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *format, ...);
]]
ffi.C.printf("Hello world!\n")
执行结果如下:
Hello world!
(1)
ffi.cdef
的定义如下:
ffi.cdef(def)
def
必须是一个Lua
字符串,建议使用“[[...]]
”这种格式。ffi.cdef
包含的是对C
语言类型的定义和外部符号(变量和函数)的声明(仅仅是声明,并没有和实际的内存地址进行绑定,实际的绑定是通过C library namespace
)。要注意对C
类型的声明不会经过C
预处理器,除了#pragma pack
以外,包括#define
在内的指令都要进行处理替换,比如使用enum
等等。
(2)
ffi.C
是默认的C library namespace
。它同编译器有些类似,但不用显示地声明链接库。在POSIX
系统上,ffi.C
会在default
或global
的namespace
上绑定符号。包括libc
,libm
,等等。还有Luajit
自身提供的API
在RHEL
上安装package
时,经常看到同样名字的package
有两个:分别是带和不带devel
后缀的。例如:
elfutils-libelf.x86_64 : Library to read and write ELF files
elfutils-libelf-devel.x86_64 : Development support for libelf
两者区别是:不带devel
后缀的package
,通常只包含能让程序运行的动态库和配置文件。而带devel
后缀的package
,则包含使用这个package
开发程序的所有的必需文件。比如头文件,等等。有时devel package
还包含静态库。
参考资料:
What are *-devel packages?。
这两天折腾了一下Suse
(SLES 12 Beta
版本),感觉和RedHat
系列还是有一些不同。记录下来,以备以后查找:
(1)YaST2
YaST (Yet another Setup Tool)2
是Suse
系统上的配置工具的:
感觉很好用。配置网络,FTP
,Proxy
等等,很方便。另外,单击图标就可以启动软件了,让用惯了“双击”的我开始不大适应。
(2)zypper
命令行安装软件使用zypper
命令(in
代表install
):
zypper in git-core
卸载(rm
):
zypper rm git-core
另外注意,git
包的名字叫git-core
。
(3)命令窗口
使用Alt + F2
快捷键可以调出命令窗口:
输入gnome-terminal
可以打开一个终端。