kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

try_to_wake_up函数

try_to_wake_up函数通过把进程状态设置为TASK_RUNNING,并把该进程插入本地CPU运行队列rq来达到唤醒睡眠和停止的进程的目的。
例如:调用该函数唤醒等待队列中的进程,或恢复执行等待信号的进程。该函数接受的参数有:
- 被唤醒进程的描述符指针(p)
- 可以被唤醒的进程状态掩码(state)
- 一个标志(sync),用来禁止被唤醒的进程抢占本地CPU上正在运行的进程

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
45
static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync)
{
	int cpu, this_cpu, success = 0;
	unsigned long flags;
	long old_state;
	struct rq *rq;
#ifdef CONFIG_SMP
	struct sched_domain *sd, *this_sd = NULL;
	unsigned long load, this_load;
	int new_cpu;
#endif
	rq = task_rq_lock(p, &flags);
	old_state = p->state;
	if (!(old_state & state))
		goto out;
	if (p->array)
		goto out_running;
	cpu = task_cpu(p);
	this_cpu = smp_processor_id();
#ifdef CONFIG_SMP
... // [多处理器负载平衡工作](/blog/2015/02/11/kernel-sched-balance/)
#endif /* CONFIG_SMP */
	if (old_state == TASK_UNINTERRUPTIBLE) {
		rq->nr_uninterruptible--;
		/*
		 * Tasks on involuntary sleep don't earn
		 * sleep_avg beyond just interactive state.
		 */
		p->sleep_type = SLEEP_NONINTERACTIVE; //简单判断出非交互进程
	} else
		if (old_state & TASK_NONINTERACTIVE)
			p->sleep_type = SLEEP_NONINTERACTIVE;//同上
	activate_task(p, rq, cpu == this_cpu);
	if (!sync || cpu != this_cpu) {
		if (TASK_PREEMPTS_CURR(p, rq))
			resched_task(rq->curr);
	}
	success = 1;
out_running:
	trace_sched_wakeup(rq, p, success);
	p->state = TASK_RUNNING;
out:
	task_rq_unlock(rq, &flags);
	return success;
}

代码解释如下:
1.首先调用task_rq_lock( )禁止本地中断,并获得最后执行进程的CPU(他可能不同于本地CPU)所拥有的运行队列rq的锁。CPU的逻辑号存储在p->thread_info->cpu字段。

2.检查进程的状态p->state是否属于被当作参数传递给函数的状态掩码state,如果不是,就跳到第9步终止函数。

3.如果p->array字段不等于NULL,那么进程已经属于某个运行队列,因此跳转到第8步。

4.在多处理器系统中,该函数检查要被唤醒的进程是否应该从最近运行的CPU的运行队列迁移到另外一个CPU的运行队列。实际上,函数就是根据一些启发式规则选择一个目标运行队列。

5.如果进程处于TASK_UNINTERRUPTIBLE状态,函数递减目标运行队列的nr_uninterruptible字段,并把进程描述符的p->activated字段设置为-1。

6.调用activate_task( )函数:

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
static void activate_task(struct task_struct *p, struct rq *rq, int local)
{
	unsigned long long now;
	now = sched_clock();
#ifdef CONFIG_SMP
...
#endif
	if (!rt_task(p))
		p->prio = recalc_task_prio(p, now); //计算平均睡眠时间并返回之后的优先级。
	if (p->sleep_type == SLEEP_NORMAL) {
		if (in_interrupt())
			p->sleep_type = SLEEP_INTERRUPTED;
		else {
			p->sleep_type = SLEEP_INTERACTIVE;
		}
	}
	p->timestamp = now;
	__activate_task(p, rq);
}
static void __activate_task(struct task_struct *p, struct rq *rq)
{
	struct prio_array *target = rq->active;
	trace_activate_task(p, rq);
	if (batch_task(p))
		target = rq->expired;
	enqueue_task(p, target);
	inc_nr_running(p, rq);
}

它依次执行下面的子步骤:
a) 调用sched_clock( )获取以纳秒为单位的当前时间戳。如果目标CPU不是本地CPU,就要补偿本地时钟中断的偏差,这是通过使用本地CPU和目标CPU上最近一次发生时钟中断的相对时间戳来达到的:now = (sched_clock( ) - this_rq( )->timestamp_last_tick) + rq->timestamp_last_tick;
b) 调用recalc_task_prio(),把进程描述的指针和上一步计算出的时间戳传递给它。recalc_task_prio()主要更新进程的平均睡眠时间和动态优先级,下一篇博文将详细说明这个函数。
c) 根据下表设置p->activated字段的值,该字段的意义为:
值 说明
0 进程处于TASK_RUNNING 状态。
1 进程处于TASK_INTERRUPTIBLE 或TASK_STOPPED 状态,而且正在被系统调用服务例程或内核线程唤醒。
2 进程处于TASK_INTERRUPTIBLE 或TASK_STOPPED 状态,而且正在被中断处理程序或可延迟函数唤醒。
-1 进程处于TASK_UNINTERRUPTIBLE 状态而且正在被唤醒。 d) 使用在第6a步中计算的时间戳设置p->timestamp字段。
e) 把进程描述符插入活动进程集合:

1
2
enqueue_task(p, rq->active);
rq->nr_running++;

7.如果目标CPU不是本地CPU,或者没有设置sync标志,就检查可运行的新进程的动态优先级是否比rq运行对了中当前进程的动态优先级高(p->prio < rq->curr->prio);如果是,就调用resched_task()抢占rq->curr。在单处理器系统中,后面的函数只是执行set_tsk_need_resched()来设置rq->curr进程的TIF_NEED_RESCHED标志。在多处理器系统中,resched_task()也检查TIF_NEED_RESCHED的旧值是否为0、目标CPU与本地CPU是否不同、rq->curr进程的TIF_POLLING_NRFLAG标志是否清0(目标CPU没有轮询进程TIF_NEED_RESCHED标志的值)。如果是,resched_task()调用smp_send_reschedule()产生IPI,并强制目标CPU重新调度。

8.把进程的p->state字段设置为TASK_RUNNING状态。

9.调用task_rq_unlock()来打开rq运行队列的锁并打开本地中断。

10.返回1(若成功唤醒进程)或0(如果进程没有被唤醒)

内核线程使用

http://blog.csdn.net/newnewman80/article/details/7050090

kthread_create:创建线程。
1
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char *namefmt, ...);

线程创建后,不会马上运行,而是需要将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。

kthread_run :创建并启动线程的函数:
1
struct task_struct *kthread_run(int (*threadfn)(void *data),void *data,const char *namefmt, ...);
kthread_stop:通过发送信号给线程,使之退出。
1
int kthread_stop(struct task_struct *thread);

线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。
但如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止。

1. 头文件

1
2
3
#include <linux/sched.h>       //wake_up_process()
#include <linux/kthread.h>      //kthread_create()、kthread_run()   
#include <err.h>                //IS_ERR()、PTR_ERR()  

2. 实现

2.1创建线程

kernel thread可以用kernel_thread创建,但是在执行函数里面必须用daemonize释放资源并挂到init下,还需要用completion等待这一过程的完成。为了简化操作kthread_create闪亮登场。 在模块初始化时,可以进行线程的创建。使用下面的函数和宏定义:

1
2
3
struct task_struct *kthread_create(int (*threadfn)(void *data),     
					void *data,  
					const char namefmt[], ...);  
1
2
3
4
5
6
7
8
#define kthread_run(threadfn, data, namefmt, ...)                      \
({                                                                     \
	struct task_struct *__k                                            \
		   = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__);  \
	if (!IS_ERR(__k))                                                  \
		   wake_up_process(__k);                                       \
	__k;                                                               \
})  

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static struct task_struct *test_task;  
static int test_init_module(void)  
{  
	int err;  
	test_task = kthread_create(test_thread, NULL, "test_task");  
	if (IS_ERR(test_task)) {  
		printk("Unable to start kernel thread./n");  
		err = PTR_ERR(test_task);  
		test_task = NULL;  
		return err;  
	}  
	wake_up_process(test_task);  
	return 0;  
}  
module_init(test_init_module);  
2.2线程函数

在线程函数里,完成所需的业务逻辑工作。主要框架如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int threadfunc(void *data) {
	...        
	while(1) {
		set_current_state(TASK_UNINTERRUPTIBLE);
		if (kthread_should_stop()) break;
		if () { //条件为真
			//进行业务处理
		} else { //条件为假
			//让出CPU运行其他线程,并在指定的时间内重新被调度
			schedule_timeout(HZ);
		}
	}
	...
	return 0;
}
2.3结束线程

在模块卸载时,可以结束线程的运行。使用下面的函数:

1
int kthread_stop(struct task_struct *k);

例如:

1
2
3
4
5
6
7
8
static void test_cleanup_module(void)  
{  
	if (test_task) {  
		kthread_stop(test_task);  
		test_task = NULL;  
	}  
}  
module_exit(test_cleanup_module);  

设置普通线程优先级

1
2
void set_user_nice(struct task_struct *p, long nice);
// -20 <= nice < 20

将线程设置为实时线程并设置优先级

1
2
3
4
int sched_setscheduler(struct task_struct *p, int policy, struct sched_param *param);
struct sched_param {
	int sched_priority; // 实时线程对应区间[1, 99]
};

CFS 调度模块(在 kernel/sched_fair.c 中实现)用于以下调度策略:SCHED_NORMAL、SCHED_BATCH 和 SCHED_IDLE。
对于 SCHED_RR 和 SCHED_FIFO 策略,将使用实时调度模块(该模块在 kernel/sched_rt.c 中实现)。

top中NI, PR

NI,nice,动态修正CPU调度。范围(-20~19)。越大,cpu调度越一般,越小,cpu调度越偏向它。一般用于后台进程,调整也是往大了调,用来给前台进程让出CPU资源。命令行下可以用renice设置。

PR:优先级,会有两种格式,一种是数字(默认20),一种是RT字符串。

PR默认是20,越小,优先级越高。修改nice可以同时修改PR,测试过程:先开一个窗口,运行wc,另开一个窗口运行top,按N按照PID倒序排,按r输入要renice的PID,然后输入-19~20之间的值,可以看到NI变成输入的值,PR=PR+NI。修改NI得到PR的范围是0~39。优先级由高到低

RT是real-time。只能用chrt -p (1~99) pid来修改。chrt -p 1 1234会将1234的PR改成-2,chrt -p 98 1234变成-99。chrt -p 99 1234会变成RT……只要chrt过,修改nice后PR不会再更改。修改chrt得到的PR范围是RT~-2。优先级由高到低

NUMA技术相关笔记

http://blog.csdn.net/jollyjumper/article/details/17168175

起源于在mongo启动脚本中看到numactl --interleave=all mongod ...

NUMA,非统一内存访问(Non-uniform Memory Access),介于SMP(对称多处理)和MPP(大规模并行处理)之间,各个节点自有内存(甚至IO子系统),访问其它节点的内存则通过高速网络通道。NUMA信息主要通过BIOS中的ACPI(高级配置和编程接口)进行配置,Linux对NUMA系统的物理内存分布信息从系统firmware的ACPi表中获得,最重要的是SRAT(System Resource Affinity Table)和SLIT(System locality Information Table)表。SRAT表包含CPU信息、内存相关性信息,SLIT表则记录了各个节点之间的距离,在系统中由数组node_distance[]记录。这样系统可以就近分配内存,减少延迟。

Linux中用一个struct pg_data_t表示一个numa节点,Linux内核支持numa调度,并实现CPU的负载均衡。

查看是否支持:

dmesg | grep -i numa

要查看具体的numa信息用numastat
1
2
3
4
5
6
7
8
numastat
	                       node0           node1
numa_hit             19983469427     20741805466
numa_miss             1981451471      2503049250
numa_foreign          2503049250      1981451471
interleave_hit         849781831       878579884
local_node           19627390917     20298995632
other_node            2337529981      2945859084

numa_hit是打算在该节点上分配内存,最后从这个节点分配的次数;
num_miss是打算在该节点分配内存,最后却从其他节点分配的次数;
num_foregin是打算在其他节点分配内存,最后却从这个节点分配的次数;
interleave_hit是采用interleave策略最后从该节点分配的次数;
local_node该节点上的进程在该节点上分配的次数
other_node是其他节点进程在该节点上分配的次数

lscpu可以看到两个node的cpu归属:
1
2
3
4
lscpu
...
NUMA node0 CPU(s):     0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30
NUMA node1 CPU(s):     1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31
numactl --hardware命令

会返回不同节点的内存总大小,可用大小,以及node distance等信息。

各个cpu负载情况,使用命令:mpstat -P ALL(需要安装sysstat)

Linux上使用numactl设定进程的numa策略。常见的情况是,数据库daemon进程(mongodb,mysql)可能会吃掉很多内存,而一个numa节点上的内存很有限,内存不够时虚拟内存频繁与硬盘交换数据,导致性能急剧下降(标识是irqbalance进程top中居高不下),这时应该采用interleave的numa策略,允许从其他节点分配内存。

各个内存的访问延迟如何?numactl man中的example提供了参考,我在公司的服务器上测了一下:

写速度:
1
2
3
4
5
6
7
8
9
10
numactl --cpubind=0 --membind=0 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024

1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 0.546679 s, 2.0 GB/s

numactl --cpubind=0 --membind=1 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 0.612825 s, 1.8 GB/s
读速度:

测试从同一个节点读取:

1
2
3
4
5
numactl --cpubind=0 --membind=0 dd if=/dev/zero of=/dev/shm/A bs=1M count=1000
date +%s.%N
numactl --cpubind=0 --membind=0 cp /dev/shm/A /dev/null
date +%s.%N
rm /dev/shm/A

花费0.264556884765625秒,速度是3.779905410081901GB/s。

从另一个节点读取:

1
2
3
4
5
numactl --cpubind=0 --membind=0 dd if=/dev/zero of=/dev/shm/A bs=1M count=1000
date +%s.%N
numactl --cpubind=1 --membind=1 cp /dev/shm/A /dev/null
date +%s.%N
rm /dev/shm/A

花费0.3308408260345459秒,速度是3.022601569419312GB/s。

加速效果还是很明显的。

参考:

http://www.ibm.com/developerworks/cn/linux/l-numa/
http://www.dedecms.com/knowledge/data-base/nosql/2012/0820/8684.html