kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

Linux模式设计4-数据对齐

http://blog.chinaunix.net/uid-20608849-id-3027967.html

内核在某些应用中,为了实现某种机制,比如分页,或者提高访问效率需要保证数据或者指针地址对齐到某个特定的整数值,比如连接代码脚本。这个值必须是2N。数据对齐,可以看做向上圆整的一种运算。

1
2
3
4
5
include/linux/kernel.h
#define ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a) - 1)
#define __ALIGN_MASK(x, mask) (((x) + (mask))&~(mask))
#define PTR_ALIGN(p, a) ((typeof(p))ALIGN((unsigned long)(p), (a)))
#define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0)

内核提供了两个用来对齐的宏ALIGN和PTR_ALIGN,一个实现数据对齐,而另一个实现指针的对齐。它们实现的核心都是__ALIGN_MASK,其中mask参数为低N位全为1,其余位全为0的掩码,它从圆整目标值2N - 1得到。__ALIGN_MASK得到对齐值,对于数据来说直接返回即可,而对于指针则需要进行强制转换。IS_ALIGNED宏用来判断当前值是否对齐与指定的值。内核中的分页对齐宏定义如下:

1
2
3
4
5
6
7
8
arch/arm/include/asm/page.h
/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)

include/linux/mm.h
/* to align the pointer to the (next) page boundary */
#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)

PAGE_SIZE定义在体系架构相关的代码中,通常为4K。内核中提供的特性功能的对齐宏均是对ALIGN的扩展。下面提供一个代码示例,并给出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
......
int main()
{
	int a = 0 ,i = 0;
	int *p = &a;

	for(; i < 6; i++)
	 printf("ALIGN(%d, 4): %x\n", i, ALIGN(i, 4));
	
	printf("p:%p, PTR_ALIGN(p, 8): %p\n", p, PTR_ALIGN(p, 8));
	printf("IS_ALIGNED(7, 8): %d, IS_ALIGNED(16, 8): %d\n", IS_ALIGNED(7, 8), IS_ALIGNED(16, 8));
	
	return 0;
}

对齐宏测试结果:

1
2
3
4
5
6
7
ALIGN(0, 4): 0
ALIGN(1, 4): 4
......
ALIGN(4, 4): 4
ALIGN(5, 4): 8
p:0xbf96c01c, PTR_ALIGN(p, 8): 0xbf96c020
IS_ALIGNED(7, 8): 0, IS_ALIGNED(16, 8): 1

内核态使用FPU、MMX和XMM寄存器

https://www.cnblogs.com/wz19860913/archive/2010/05/25/1742583.html

保存和加载FPU、MMX和XMM寄存器

从Intel 80486DX开始,FPU(算术浮点单元)被集成到了CPU中,浮点算术功能用ESCAPE指令来执行,操纵CPU中的浮点寄存器集。显然,当一个进程正在使用ESCAPE指令,那么浮点寄存器的内容就属于它的硬件上下文。

为了加速多媒体程序的执行,Intel在微处理器中引入了新的指令集——MMX,MMX指令也作用于FPU的浮点寄存器。这样,MMX就不能和FPU指令混用,但是OS内核就可以忽略新的MMX指令集,因为保存浮点寄存器的功能代码也能够应用于MMX的状态。

MMX使用SIMD(单指令多数据)流水线,Pentium III增强了这种SIMD能力,引入SSE(Streaming SIMD Extensions)扩展。该功能增强了8个128位寄存器(XMM寄存器)的功能,这些寄存器不和FPU/MMX寄存器重叠,因此能够与FPU/MMX指令混用。

Pentium IV还引入了SSE2扩展,支持高精度浮点值,SSE2和SSE使用同一个XMM寄存器组。

80x86微处理器不在TSS中保存FPU、MMX和XMM寄存器的值,不过还是提供了一些支持,能够在需要时保存它们。cr0寄存器有一个TS(Task-Switching)标志位,每当执行硬件上下文切换时,TS置位,每当TS被置位后进程执行ESCAPE、MMX、SSE或SSE2指令,控制器就产生一个“Device not available”异常。这样,TS标志位就能够让OS内核只有在真正需要时才保存或恢复FPU、MMX和XMM寄存器。

假设进程A使用了数学协处理器,那么当进程A被切换出去的时候,内核设置TS并将浮点寄存器的内容保存到进程A的TSS中(原著这么写,但是应该是保存到进程A描述符的一个字段中,TSS是与CPU关联的,进程没有TSS)。

如果新进程B不使用数学协处理器,那么内核就不需要恢复浮点寄存器的内容,但是,一旦进程B执行FPU、MMX等指令,CPU就产生一个“Device not available”异常,相应的异常处理程序就会用保存在进程B中的相关值来恢复浮点寄存器。

处理FPU、MMX和XMM寄存器的数据结构存放在进程描述符的thread字段的i387子字段中(即thread.i387),由i387_union联合体描述,其格式如下:

1
2
3
4
5
union i387_union {
	struct i387_fsave_struct    fsave; /* 保存FPU、MMX寄存器的内容 */
	struct i387_fxsave_struct   fxsave;/* 保存SSE和SSE2寄存器内容 */
	struct i387_soft_struct     soft;  /* 由无数学协处理器的老式CPU模型使用 */
};

此外,进程描述符中还包含了两个附加的标志:

thread_info结构中status字段的TS_USEDFPU标志,表示进程当前执行过程中是否使用过FPU、MMX和XMM寄存器。 task_struct结构的flags字段的PF_USED_MATH标志,表示thread.i387的内容是否有意义。

保存和加载FPU、MMX和XMM寄存器主要用到unlazy_fpu宏,该宏在switch_to函数中使用,下一篇会对其进行分析。

内核态使用FPU、MMX和XMM寄存器

OS内核也可以使用FPU、MMX和XMM寄存器,当然,这么做的时候应该避免干扰用户态进程。因此,Linux使用如下方法来解决:

在内核使用协处理器之前,如果用户态进程使用了FPU(TS_USEDFPU标志为1),内核就要调用kernel_fpu_begin()函数,该函数里又调用save_init_fpu()来保存寄存器内容,然后重新设置cr0寄存器的TS标志。 使用完协处理器之后,内核调用kernel_fpu_end宏设置cr0寄存器的TS标志。 当用户态进程恢复执行时,math_state_restore()函数将恢复FPU、MMX和XMM寄存器的内容。

需要注意的是,如果当前用户态进程有在用数学协处理器时,kernel_fpu_begin()函数的执行时间比较长,甚至无法通过FPU、MMX或XMM达到加速的目的。因此,内核只在有限的场合使用FPU、MMX或XMM指令,比如移动或清除大内存区字段、计算校验和等。

linux 下的浮点运算

linux 下的浮点运算

  1. intel 平台下,如果有浮点计算,都会用专门的浮点指令来执行。但是double/float 类型的加减乘除,gcc 是用的一般指令来做的,没有用浮点指令来做。

  2. 还是intel 平台下,一个进程在被cpu调度到之后,运行的第一条浮点指令会触发“no deveice avaible”异常,从而导致执行相应的中断处理程序。后续的不再触发。这个中断处理程序中,一般会做的是,恢复/保存浮点运行相关的环境。如果该时间段内没有浮点操作,那么就不用恢复/保存浮点运行环境,从而减少开销。这也是为什么要设计成这样的原因。

  3. 内核也可以执行浮点操作,只需要调用前后用 kernel_fpu_begin() and kernel_fpu_end() 括起来。但是有可能这个的开销已经超过了用浮点指令带来的便捷。所以内核应该尽量少用浮点操作。


Linux内核与浮点计算

在Linux内核里无法直接进行浮点计算,这是从性能上的考虑,因为这样做可以省去在用户态与内核态之间进行切换时保存/恢复浮点寄存器 FPU的操作,当然,这到底可以提升多少性能我还不得而知,不过就目前而言,Linux内核的确就是这样做的。

比如下面这个测试模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Makefile
MDIR = $(shell pwd)
ifeq (, $(KSRC))
	KSRC := /usr/src/linux-`uname -r`
endif

ifeq (, $(PROJECT_DIR))
	PROJECT_DIR := $(PWD)/../
endif

module := float_test

obj-m := $(module).o

srcs =  $(wildcard, *.c)

$(module)-objs := $(addsuffix .o, $(basename $(srcs)))

EXTRA_CFLAGS += -g $(FLAG) -I$(PROJECT_DIR)/inc -I${SHAREDHDR} -I$(KERNELHDR) -O2 -D__KERNEL__ -DMODULE $(INCLUDE) -DEXPORT_SYMTAB

TARGET = $(module).ko

all:
	make -C $(KSRC) M=$(MDIR) modules

debug:
	make EXTRA_FLAGS="${EXTRA_CFLAGS} -DDEBUG" -C $(KSRC) M=$(MDIR) modules

clean:
	make -C $(KSRC) M=$(MDIR) clean

install: all
	cp -f $(TARGET) $(INSTALL_DIR)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * float_test.c
 */
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/desc.h>

static float float_test(float a, float b)
{
	return a/b;
}

static int __init test_module_init(void)
{
	float_test(1.0, 1.0);
	return 0;
}

static void __exit test_module_fini(void)
{

	//Do Nothing
	return;
}

module_init(test_module_init);
module_exit(test_module_fini);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lenky0401 at gmail dot com");

编译它将得到如下错误提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@localhost t]# make
make -C /usr/src/linux-`uname -r` M=/home/gqk/t modules
make[1]: Entering directory `/usr/src/linux-2.6.38.8'
  CC [M]  /home/gqk/t/float_test.o
/home/gqk/t/float_test.c: In function ‘float_test’:
/home/gqk/t/float_test.c:12: error: SSE register return with SSE disabled
make[2]: *** [/home/gqk/t/float_test.o] Error 1
make[1]: *** [_module_/home/gqk/t] Error 2
make[1]: Leaving directory `/usr/src/linux-2.6.38.8'
make: *** [all] Error 2
[root@localhost t]#

[root@localhost t]# make V=1
make -C /usr/src/linux-`uname -r` M=/home/gqk/t modules
make[1]: Entering directory `/usr/src/linux-2.6.38.8'
test -e include/generated/autoconf.h -a -e include/config/auto.conf || (        \
	echo;                               \
	echo "  ERROR: Kernel configuration is invalid.";       \
	echo "         include/generated/autoconf.h or include/config/auto.conf are missing.";\
	echo "         Run 'make oldconfig && make prepare' on kernel src to fix it.";  \
	echo;                               \
	/bin/false)
mkdir -p /home/gqk/t/.tmp_versions ; rm -f /home/gqk/t/.tmp_versions/*
make -f scripts/Makefile.build obj=/home/gqk/t
  gcc -Wp,-MD,/home/gqk/t/.float_test.o.d  -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.4.4/include -I/usr/src/linux-2.6.38.8/arch/x86/include -Iinclude  -include include/generated/autoconf.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O1 -m64 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=2048 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -g -I/home/gqk/t/..//inc -I -I -O2 -D__KERNEL__ -DMODULE -DEXPORT_SYMTAB  -DMODULE  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(float_test)"  -D"KBUILD_MODNAME=KBUILD_STR(float_test)" -c -o /home/gqk/t/.tmp_float_test.o /home/gqk/t/float_test.c
/home/gqk/t/float_test.c: In function ‘float_test’:
/home/gqk/t/float_test.c:12: error: SSE register return with SSE disabled
make[2]: *** [/home/gqk/t/float_test.o] Error 1
make[1]: *** [_module_/home/gqk/t] Error 2
make[1]: Leaving directory `/usr/src/linux-2.6.38.8'
make: *** [all] Error 2
[root@localhost t]#

注意到其中的gcc编译选项:-mno-sse -mno-mmx -mno-sse2,这几个选项是Linux内核编译模块时自动带上的,就是它们(具体就是-mno-sse)明确禁止了Linux内核无法使用浮点数。

在Linux内核里很少会有使用浮点数的需求,即便是有也大多是通过变通的办法解决,在下面链接里有一些很好的扩展介绍,感兴趣的可以看看:

http://stackoverflow.com/questions/6397430/overhead-of-supporting-floating-point-arithmetic-inside-the-linux-kernel

http://stackoverflow.com/questions/10212892/how-to-avoid-fpu-when-given-float-numbers

http://www.linuxsmiths.com/blog/?p=253


http://blog.csdn.net/vbskj/article/details/38408467

c scanf/fscanf 的%n和%[]使用方法

http://blog.csdn.net/wesweeky/article/details/6439777

一、

%n说明符输出有效字符数量,%n在scanf和printf中都可使用。与%n相对应的形参是一个int类型的指针,%n不影响scanf和printf的返回值。例如:

1
scanf("%d %d%n", &i, &j, &k);

如果输入434 6434,则k等于8,而scanf的返回值仍然为2。又如:

1
scanf("%c%n", &ch, &k);

输入“sbcdefdg”后,k等于1,而不是8,因为%c只取一个字符,%n输出的是有效字符数量。

%n用在printf函数里,表示输出的字符数量,例如:

1
printf("i=%d, j=%d/n%n", i, j, &k);

在i=343、j=123的情况下,k=12,同时%n不影响printf的返回值,其返回值仍然为12,而不是14。

二、

众所周之,scanf以空白字符为定界符,但如果输入的字符串是以其它字符为定界符的,那怎么办?[]就是专门处理这个问题的转换说明符。[]转换说明符可以通过两种方式产生结果字符集,如果第一个[字符右边没有抑扬符(^),那么处于[]之间的字符就是结果字符集,不在其中的可输入字符都作为定界符;如果左边[符号紧靠一个抑扬符(^),那么意义相反,^和]之间的字符是定界符,其余可输入字符是结果字符集。

在使用[]说明符之前,得先明白两个概念:一是扫描列表。扫描列表(scanlist)指的是包含在[和]两个字符之间除紧靠左边[字符的抑扬符之外的字符,例如:

1
scanf("%[abcd]", ptr);

abcd组成扫描列表。二是扫描字符集(scanset)。扫描字符集指的是结果字符集,例如上面的例子,结果字符集就是abcd。如果输入一个字符串“cbadkjf”,那么ptr得到的字符串是cbad,kjf三个字符都属于定界符,输入到k字符时输入字符串被截断,kjf三个字符被留在stdin里面。如果带有抑扬符,例如:

1
scanf("%[^abcd]", ptr);

扫描列表仍然是abcd,但扫描字符集是除abcd外的可输入字符。如果输入字符串“jksferakjjdf”,ptr得到的字符串是“jksfer”。如果想限制输入字符串的字符数量,可以象s说明符那样,在[]前面使用位域,例如:

1
scanf("%10[^abcd]", ptr);

这样结果字符串最多只能包含10个字符(除'/0'字符外)。

[符号可以作为扫描列表中的一个成员,但]字符除紧贴最左边的[字符或抑扬符两种情况外,其余情况下都不会被看作扫描列表的成员。例如“%[]abcd]”或者“%[^]abcd]”,上述两种情况下]字符属于扫描列表的成员,但如果是“%[ab]cd]”,中间的]字符不会被看作扫描列表的成员,而且输入输出的结果会是乱七八糟的。

对于减号-,只有在紧贴[字符或抑扬字符以及作为扫描列表最后一个成员时,-字符才会被视为扫描列表的成员。c标准把其余情况规定为编译器相关的。大多数编译器把这种情况的减号定义为连字符,例如:

1
scanf("%[a-zA-Z]", ptr);

那么扫描列表由大小写各26个字母组成。少数编译器仍旧把这种情况下的减号视为扫描列表成员。

1
fscanf(fd,"%*[^/n]/n"); // %*是虚读,没有存,只是让指针跳过了这个变量!

sk 的锁,spin_lock_bh、lock_sock

一、修改sk的锁 sk_lock.slock

tcp协议栈对struct sock sk有两把锁,第一把是sk_lock.slock,第二把则是sk_lock.owned。sk_lock.slock用于获取struct sock sk对象的成员的修改权限;sk_lock.owned用于区分当前是进程上下文或是软中断上下文,为进程上下文时sk_lock.owned会被置1,中断上下文为0。

如果是要对sk修改,首先是必须拿锁sk_lock.slock,其后是判断当前是软中断或是进程上下文,如果是进程上下文,那么一般也不能修改sk

中断上下文可以用下面的锁,也就是下面的锁只有 spin_lock

1
2
3
4
5
6
/* BH context may only use the following locking interface. */
#define bh_lock_sock(__sk)      spin_lock(&((__sk)->sk_lock.slock))
#define bh_lock_sock_nested(__sk) \
				spin_lock_nested(&((__sk)->sk_lock.slock), \
				SINGLE_DEPTH_NESTING)
#define bh_unlock_sock(__sk)    spin_unlock(&((__sk)->sk_lock.slock))

非中断上下文可以直接用 spin_lock_bh(&((sk)->sk_lock.slock))

获得sk_lock.slock 锁后还要判断 sock_owned_by_user(sk), 如果被进程上下文占用也一般不能操作sk

1
#define sock_owned_by_user(sk)  ((sk)->sk_lock.owned)

二、进程上下文获取sk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static inline void lock_sock(struct sock *sk)
{
	lock_sock_nested(sk, 0);
}

void lock_sock_nested(struct sock *sk, int subclass)
{
	might_sleep();

	// 先获取 sk_lock.slock 锁
	spin_lock_bh(&sk->sk_lock.slock);
	// 如果有进程占用sk,则调__lock_sock等待占用sk的进程结束
	if (sk->sk_lock.owned)
		__lock_sock(sk);

	// 此时占用sk的进程结束了,但前进程就可以占用sk
	sk->sk_lock.owned = 1;
	// 进程占用sk时不占 sk_lock.slock
	spin_unlock(&sk->sk_lock.slock);

	/*
	 * The sk_lock has mutex_lock() semantics here:
	 */
	mutex_acquire(&sk->sk_lock.dep_map, subclass, 0, _RET_IP_);
	local_bh_enable();
}

static void __lock_sock(struct sock *sk)
	__releases(&sk->sk_lock.slock)
	__acquires(&sk->sk_lock.slock)
{
	DEFINE_WAIT(wait);

	for (;;) {
		prepare_to_wait_exclusive(&sk->sk_lock.wq, &wait,
					TASK_UNINTERRUPTIBLE);
		spin_unlock_bh(&sk->sk_lock.slock);
		schedule();
		spin_lock_bh(&sk->sk_lock.slock);
		if (!sock_owned_by_user(sk))
			break;
	}
	finish_wait(&sk->sk_lock.wq, &wait);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void release_sock(struct sock *sk)
{
	/*
	 * The sk_lock has mutex_unlock() semantics:
	 */
	mutex_release(&sk->sk_lock.dep_map, 1, _RET_IP_);

	spin_lock_bh(&sk->sk_lock.slock);
	if (sk->sk_backlog.tail)
		__release_sock(sk); // 处理backlog的包

	/* Warning : release_cb() might need to release sk ownership,
	 * ie call sock_release_ownership(sk) before us.
	 */
	if (sk->sk_prot->release_cb)
		sk->sk_prot->release_cb(sk);

	// 获取sk_lock.slock,然后清除 sk_lock.owned
	sock_release_ownership(sk);
	if (waitqueue_active(&sk->sk_lock.wq))
		wake_up(&sk->sk_lock.wq);
	spin_unlock_bh(&sk->sk_lock.slock);
}

static inline void sock_release_ownership(struct sock *sk)
{
	sk->sk_lock.owned = 0;
}