在前一章也提到过,之所以中断会分成上下两部分,是由于中断对时限的要求非常高,需要尽快的响应硬件。

主要内容:

  • 中断下半部处理
  • 实现中断下半部的机制
  • 总结中断下半部的实现
  • 中断实现示例

1. 中断下半部处理

那么对于一个中断,如何划分上下两部分呢?哪些处理放在上半部,哪些处理放在下半部?

这里有一些经验可供借鉴:

  1. 如果一个任务对时间十分敏感,将其放在上半部
  2. 如果一个任务和硬件有关,将其放在上半部
  3. 如果一个任务要保证不被其他中断打断,将其放在上半部
  4. 其他所有任务,考虑放在下半部

2. 实现中断下半部的机制

实现下半部的方法很多,随着内核的发展,产生了一些新的方法,也淘汰了一些旧方法。

目前使用最多的是以下3中方法

  • 2.1 软中断
  • 2.2 tasklet
  • 2.3 工作队列

2.1 软中断

软中断的代码在:kernel/softirq.c

软中断的流程如下:

流程图中几个步骤的说明:

① 注册软中断的函数 open_softirq参见 kernel/softirq.c文件)

/*
* 将软中断类型和软中断处理函数加入到软中断序列中
* @nr - 软中断类型
* @(*action)(struct softirq_action *) - 软中断处理的函数指针
*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
/* softirq_vec是个struct softirq_action类型的数组 */
softirq_vec[nr].action = action;
}
 

软中断类型目前有10个,其定义在 include/linux/interrupt.h 文件中:

enum
{
HI_SOFTIRQ=,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS
};

struct softirq_action 的定义也在 include/linux/interrupt.h 文件中

/*
* 这个结构体的字段是个函数指针,字段名称是action
* 函数指针的返回指是void型
* 函数指针的参数是 struct softirq_action 的地址,其实就是指向 softirq_vec 中的某一项
* 如果 open_softirq 是这样调用的: open_softirq(NET_TX_SOFTIRQ, my_tx_action);
* 那么 my_tx_action 的参数就是 softirq_vec[NET_TX_SOFTIRQ]的地址
*/
struct softirq_action
{
void (*action)(struct softirq_action *);
};
 

② 触发软中断的函数 raise_softirq 参见 kernel/softirq.c文件

/*
* 触发某个中断类型的软中断
* @nr - 被触发的中断类型
* 从函数中可以看出,在处理软中断前后有保存和恢复寄存器的操作
*/
void raise_softirq(unsigned int nr)
{
unsigned long flags; local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}

③ 执行软中断 do_softirq 参见 kernel/softirq.c文件

asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags; /* 判断是否在中断处理中,如果正在中断处理,就直接返回 */
if (in_interrupt())
return; /* 保存当前寄存器的值 */
local_irq_save(flags); /* 取得当前已注册软中断的位图 */
pending = local_softirq_pending(); /* 循环处理所有已注册的软中断 */
if (pending)
__do_softirq(); /* 恢复寄存器的值到中断处理前 */
local_irq_restore(flags);
}

④ 执行相应的软中断 - 执行自己写的中断处理

linux中,执行软中断有专门的内核线程,每个处理器对应一个线程,名称ksoftirqd/n (n对应处理器号)

通过top命令查看我的单核虚拟机,CentOS系统中的ksoftirqd线程如下:

[root@vbox ~]# top | grep ksoftirq
4 root 20 0 0 0 0 S 0.0 0.0 0:00.02 ksoftirqd/0

2.2 tasklet

tasklet也是利用软中断来实现的,但是它提供了比软中断更好用的接口(其实就是基于软中断又封装了一下),

所以除了对性能要求特别高的情况,一般建议使用tasklet来实现自己的中断。

tasklet对应的结构体在 <linux/interrupt.h> 中

struct tasklet_struct
{
struct tasklet_struct *next; /* 链表中的下一个tasklet */
unsigned long state; /* tasklet状态 */
atomic_t count; /* 引用计数器 */
void (*func)(unsigned long); /* tasklet处理函数 */
unsigned long data; /* tasklet处理函数的参数 */
};
 

tasklet状态只有3种值:

  1. 值 0 表示该tasklet没有被调度
  2. 值 TASKLET_STATE_SCHED 表示该tasklet已经被调度
  3. 值 TASKLET_STATE_RUN 表示该tasklet已经运行

引用计数器count 的值不为0,表示该tasklet被禁止。

tasklet使用流程如下:

1. 声明tasklet (参见<linux/interrupt.h>)

/* 静态声明一个tasklet */
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, , ATOMIC_INIT(), func, data } #define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, , ATOMIC_INIT(), func, data } /* 动态声明一个tasklet 传递一个tasklet_struct指针给初始化函数 */
extern void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
 

2. 编写处理程序

参照tasklet处理函数的原型来写自己的处理逻辑

void tasklet_handler(unsigned long date)

3. 调度tasklet

中断的上半部处理完后调度tasklet,在适当时候进行下半部的处理

tasklet_schedule(&my_tasklet)  /* my_tasklet就是之前声明的tasklet_struct */

2.3 工作队列

工作队列子系统是一个用于创建内核线程的接口,通过它可以创建一个工作者线程来专门处理中断的下半部工作。

工作队列和tasklet不一样,不是基于软中断来实现的。

缺省的工作者线程名称是 events/n (n对应处理器号)。

通过top命令查看我的单核虚拟机,CentOS系统中的events线程如下:

[root@vbox ~]# top | grep event
7 root 20 0 0 0 0 S 0.0 0.0 0:03.71 events/0

工作队列主要用到下面3个结构体,弄懂了这3个结构体的关系,也就知道工作队列的处理流程了。

/* 在 include/linux/workqueue.h 文件中定义 */
struct work_struct {
atomic_long_t data; /* 这个并不是处理函数的参数,而是表示此work是否pending等状态的flag */
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry; /* 中断下半部处理函数的链表 */
work_func_t func; /* 处理中断下半部工作的函数 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
}; /* 在 kernel/workqueue.c文件中定义
* 每个工作者线程对应一个 cpu_workqueue_struct ,其中包含要处理的工作的链表
* (即 work_struct 的链表,当此链表不空时,唤醒工作者线程来进行处理)
*/
/*
* The per-CPU workqueue (if single thread, we always use the first
* possible cpu).
*/
struct cpu_workqueue_struct { spinlock_t lock; /* 锁保护这种结构 */ struct list_head worklist; /* 工作队列头节点 */
wait_queue_head_t more_work;
struct work_struct *current_work; struct workqueue_struct *wq; /* 关联工作队列结构 */
struct task_struct *thread; /* 关联线程 */
} ____cacheline_aligned; /* 也是在 kernel/workqueue.c 文件中定义的
* 每个 workqueue_struct 表示一种工作者类型,系统默认的就是 events 工作者类型
* 每个工作者类型一般对应n个工作者线程,n就是处理器的个数
*/
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq; /* 工作者线程 */
struct list_head list;
const char *name;
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

使用工作者队列的方法见下图:

① 创建推后执行的工作 - 有静态创建和动态创建2种方法

/* 静态创建一个work_struct
* @n - work_struct结构体,不用事先定义
* @f - 下半部处理函数
*/
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f) /* 动态创建一个 work_struct
* @_work - 已经定义好的一个 work_struct
* @_func - 下半部处理函数
*/
#ifdef CONFIG_LOCKDEP
#define INIT_WORK(_work, _func) \
do { \
static struct lock_class_key __key; \
\
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, );\
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while ()
#else
#define INIT_WORK(_work, _func) \
do { \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while ()
#endif

工作队列处理函数的原型:

typedef void (*work_func_t)(struct work_struct *work);

② 刷新现有的工作,这个步骤不是必须的,可以直接从第①步直接进入第③步

刷新现有工作的意思就是在追加新的工作之前,保证队列中的已有工作已经执行完了。

/* 刷新系统默认的队列,即 events 队列 */
void flush_scheduled_work(void); /* 刷新用户自定义的队列
* @wq - 用户自定义的队列
*/
void flush_workqueue(struct workqueue_struct *wq);

③ 调度工作 - 调度新定义的工作,使之处于等待处理器执行的状态

/* 调度第一步中新定义的工作,在系统默认的工作者线程中执行此工作
* @work - 第一步中定义的工作
*/
schedule_work(struct work_struct *work); /* 调度第一步中新定义的工作,在系统默认的工作者线程中执行此工作
* @work - 第一步中定义的工作
* @delay - 延迟的时钟节拍
*/
int schedule_delayed_work(struct delayed_work *work, unsigned long delay); /* 调度第一步中新定义的工作,在用户自定义的工作者线程中执行此工作
* @wq - 用户自定义的工作队列类型
* @work - 第一步中定义的工作
*/
int queue_work(struct workqueue_struct *wq, struct work_struct *work); /* 调度第一步中新定义的工作,在用户自定义的工作者线程中执行此工作
* @wq - 用户自定义的工作队列类型
* @work - 第一步中定义的工作
* @delay - 延迟的时钟节拍
*/
int queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *work, unsigned long delay);

3. 总结中断下半部的实现

下面对实现中断下半部工作的3种机制进行总结,便于在实际使用中决定使用哪种机制

下半部机制

上下文

复杂度

执行性能

顺序执行保障

软中断 中断 高 
(需要自己确保软中断的执行顺序及锁机制)
好 
(全部自己实现,便于调优)
没有
tasklet 中断 中 
(提供了简单的接口来使用软中断)
同类型不能同时执行
工作队列 进程 低 
(在进程上下文中运行,与写用户程序差不多)
没有 
(和进程上下文一样被调度)

4. 中断实现示例

4.1 软中断的实现

本来想用内核模块的方法来测试一下软中断的流程,但是编译时发现软中断注册函数(open_softirq)和触发函数(raise_softirq)

并没有用EXPORT_SYMBOL导出,所以自定义的内核模块中无法使用。

测试的代码如下:

#include <linux/interrupt.h>
#include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); static void my_softirq_func(struct softirq_action*); static int testsoftirq_init(void)
{
// 注册softirq,这里注册的是定时器的下半部
open_softirq(TIMER_SOFTIRQ, my_softirq_func); // 触发softirq
raise_softirq(TIMER_SOFTIRQ); return ; } static void testsoftirq_exit(void)
{
printk(KERN_ALERT "*************************\n");
print_current_time();
printk(KERN_ALERT "testrbtree is exited!\n");
printk(KERN_ALERT "*************************\n"); } static void my_softirq_func(struct softirq_action* act)
{
printk(KERN_ALERT "=========================\n");
print_current_time();
printk(KERN_ALERT "my softirq function is been called!....\n");
printk(KERN_ALERT "=========================\n");
} module_init(testsoftirq_init);
module_exit(testsoftirq_exit);
 

其中头文件 kn_common.h 的相关内容参见之前的博客《Linux内核设计与实现》读书笔记(六)- 内核数据结构

由于内核没有用EXPORT_SYMBOL导出open_softirqraise_softirq函数,所以编译时有如下警告:

WARNING: "open_softirq" [/root/chap08/mysoftirq.ko] undefined!
WARNING: "raise_softirq" [/root/chap08/mysoftirq.ko] undefined!

注:编译用的系统时centos6.3 (uname -r结果 - 2.6.32-279.el6.x86_64)

没办法,只能尝试修改内核代码(将open_softirq和raise_softirq用EXPORT_SYMBOL导出),再重新编译内核,然后再尝试能否测试软中断。

主要修改2个文件,(既然要修改代码,干脆加了一种软中断类型):

/* 修改 kernel/softirq.c */
// ... 略 ...
char *softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL",
"TASKLET", "SCHED", "HRTIMER", "RCU", "WYB"
}; /* 追加了一种新的softirq,即 "WYB",我名字的缩写 ^_^ */ // ... 略 ... void raise_softirq(unsigned int nr)
{
unsigned long flags; local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
EXPORT_SYMBOL(raise_softirq); /* 追加的代码 */ void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
EXPORT_SYMBOL(open_softirq); /* 追加的代码 */ // ... 略 ... /* 还修改了 include/linux/interrupt.h */
enum
{
HI_SOFTIRQ=,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ WYB_SOFTIRQS, /* 追加的一种中断类型 */
NR_SOFTIRQS
};

重新编译内核后,在新的内核上再次实验软中断代码:

(编译内核方法参见:《Linux内核设计与实现》读书笔记(五)- 系统调用 3.3节)

测试软中断的代码:testsoftirq.c

#include <linux/interrupt.h>
#include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); static void my_softirq_func(struct softirq_action*); static int testsoftirq_init(void)
{
printk(KERN_ALERT "interrupt's top half!\n"); // 注册softirq,这里注册的是自定义的软中断类型
open_softirq(WYB_SOFTIRQS, my_softirq_func); // 触发softirq
raise_softirq(WYB_SOFTIRQS); return ; } static void testsoftirq_exit(void)
{
printk(KERN_ALERT "*************************\n");
print_current_time();
printk(KERN_ALERT "testsoftirq is exited!\n");
printk(KERN_ALERT "*************************\n"); } static void my_softirq_func(struct softirq_action* act)
{
printk(KERN_ALERT "=========================\n");
print_current_time();
printk(KERN_ALERT "my softirq function is been called!....\n");
printk(KERN_ALERT "=========================\n");
} module_init(testsoftirq_init);
module_exit(testsoftirq_exit);

Makefile:

obj-m += mysoftirq.o
mysoftirq-objs := testsoftirq.o kn_common.o #generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complie object
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
#clean
clean:
rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned

测试软中断的方法如下:

make
insmod mysoftirq.ko
rmmod mysoftirq
dmesg | tail - # 运行结果
interrupt's top half!
=========================
-- ::
my softirq function is been called!....
=========================
*************************
-- ::
testsoftirq is exited!
*************************

4.2 tasklet的实现

tasklet的实验用默认的内核即可,我们切换到centos6.3的默认内核(uname -r: 2.6.32-279.el6.x86_64)

从中我们也可以看出,内核之所以没有导出open_softirq和raise_softirq函数,可能还是因为提倡我们尽量用tasklet来实现中断的下半部工作。

tasklet测试代码:testtasklet.c

#include <linux/interrupt.h>
#include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); static void my_tasklet_func(unsigned long); /* mytasklet 必须定义在testtasklet_init函数的外面,否则会出错 */
DECLARE_TASKLET(mytasklet, my_tasklet_func, ); static int testtasklet_init(void)
{
printk(KERN_ALERT "interrupt's top half!\n"); // 如果在这里定义的话,那么 mytasklet是函数的局部变量,
// 后面调度的时候会找不到 mytasklet
// DECLARE_TASKLET(mytasklet, my_tasklet_func, 1000); // 调度tasklet, 处理器会在适当时候执行这个tasklet
tasklet_schedule(&mytasklet); return ; } static void testtasklet_exit(void)
{
printk(KERN_ALERT "*************************\n");
print_current_time();
printk(KERN_ALERT "testtasklet is exited!\n");
printk(KERN_ALERT "*************************\n"); } static void my_tasklet_func(unsigned long data)
{
printk(KERN_ALERT "=========================\n");
print_current_time();
printk(KERN_ALERT "my tasklet function is been called!....\n");
printk(KERN_ALERT "parameter data is %ld\n", data);
printk(KERN_ALERT "=========================\n");
} module_init(testtasklet_init);
module_exit(testtasklet_exit);

Makefile:

obj-m += mytasklet.o
mytasklet-objs := testtasklet.o kn_common.o #generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complie object
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
#clean
clean:
rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned

测试tasklet的方法如下:

make
insmod mytasklet.ko
rmmod mytasklet
dmesg | tail - # 运行结果
interrupt's top half!
=========================
-- ::
my tasklet function is been called!....
parameter data is
=========================
*************************
-- ::
testtasklet is exited!
*************************

4.3 工作队列的实现

workqueue的例子的中静态定义了一个工作,动态定义了一个工作。

静态定义的工作由系统工作队列(events/n)调度,

动态定义的工作由自定义的工作队列(myworkqueue)调度。

测试工作队列的代码:testworkqueue.c

#include <linux/workqueue.h>
#include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); static void my_work_func(struct work_struct *);
static void my_custom_workqueue_func(struct work_struct *); /* 静态创建一个工作,使用系统默认的工作者线程,即 events/n */
DECLARE_WORK(mywork, my_work_func); static int testworkqueue_init(void)
{
/*自定义的workqueue */
struct workqueue_struct *myworkqueue = create_workqueue("myworkqueue"); /* 动态创建一个工作 */
struct work_struct *mywork2;
mywork2 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(mywork2, my_custom_workqueue_func); printk(KERN_ALERT "interrupt's top half!\n"); /* 刷新系统默认的队列 */
flush_scheduled_work();
/* 调度工作 */
schedule_work(&mywork); /* 刷新自定义的工作队列 */
flush_workqueue(myworkqueue);
/* 调度自定义工作队列上的工作 */
queue_work(myworkqueue, mywork2); return ;
} static void testworkqueue_exit(void)
{
printk(KERN_ALERT "*************************\n");
print_current_time();
printk(KERN_ALERT "my workqueue test is exited!\n");
printk(KERN_ALERT "*************************\n"); } static void my_work_func(struct work_struct *work)
{
printk(KERN_ALERT "=========================\n");
print_current_time();
printk(KERN_ALERT "my workqueue function is been called!....\n");
printk(KERN_ALERT "=========================\n");
} static void my_custom_workqueue_func(struct work_struct *work)
{
printk(KERN_ALERT "=========================\n");
print_current_time();
printk(KERN_ALERT "my cutomize workqueue function is been called!....\n");
printk(KERN_ALERT "=========================\n");
kfree(work);
} module_init(testworkqueue_init);
module_exit(testworkqueue_exit);

Makefile:

obj-m += myworkqueue.o
myworkqueue-objs := testworkqueue.o kn_common.o #generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
#complie object
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
#clean
clean:
rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned 

测试workqueue的方法如下:

make
insmod myworkqueue.ko
rmmod myworkqueue
dmesg | tail - # 运行结果
interrupt's top half!
=========================
-- ::
my workqueue function is been called!....
=========================
=========================
-- ::
my cutomize workqueue function is been called!....
=========================
*************************
-- ::
my workqueue is exited!
*************************

《Linux内核设计与实现》读书笔记(八)- 中断下半部的处理的更多相关文章

  1. Linux内核设计与实现 读书笔记 转

    Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...

  2. Linux内核设计与实现 读书笔记

    第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的ta ...

  3. Linux内核设计与实现读书笔记(8)-内核同步方法【转】

    转自:http://blog.chinaunix.net/uid-10469829-id-2953001.html 1.原子操作可以保证指令以原子的方式执行——执行过程不被打断.内核提供了两组原子操作 ...

  4. Linux内核设计与实现——读书笔记2:进程管理

    1.进程: (1)处于执行期的程序,但不止是代码,还包括各种程序运行时所需的资源,实际上进程是正在执行的 程序的实时结果. (2)程序的本身并不是进程,进程是处于执行期的程序及其相关资源的总称. (3 ...

  5. Linux内核设计与实现——读书笔记1:内核简介

    内核:有的时候被称管理者或者操作系统核心,通常内核负责响应中断的中断服务程序, 负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间德内存管理程序 和网络,进程间通信等系统服务程序共同组 ...

  6. 《Linux内核设计与实现》第八周读书笔记——第四章 进程调度

    <Linux内核设计与实现>第八周读书笔记——第四章 进程调度 第4章 进程调度35 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配 ...

  7. 初探内核之《Linux内核设计与实现》笔记上

    内核简介  本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核   原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单.2. 高效 ...

  8. 《Linux内核设计与实现》 第八周读书笔记 第四章 进程调度

    20135307 张嘉琪 第八周读书笔记 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有 ...

  9. Linux内核架构与底层--读书笔记

    linux中管道符"|"的作用 命令格式:命令A|命令B,即命令1的正确输出作为命令B的操作对象(下图应用别人的图片) 1. 例如: ps aux | grep "tes ...

  10. Linux内核设计与实现 总结笔记(第十章)内核同步方法

    一.原子操作 原子操作可以保证指令以原子的方式执行----执行过程不被打断. 1.1 原子整数操作 针对整数的原子操作只能对atomic_t类型的数据进行处理. 首先,让原子函数只接收atomic_t ...

随机推荐

  1. VS2005环境下的DLL应用

    VS2005环境下的DLL应用 作者:一点一滴的Beer http://beer.cnblogs.com/ 以前写过一篇题为<VC++的DLL应用(含Demo演示)>的文章,当时是刚开始接 ...

  2. bzoj 3398 [Usaco2009 Feb]Bullcow 牡牛和牝牛——前缀和优化dp / 排列组合

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3398 好简单呀.而且是自己想出来的. dp[ i ]表示最后一个牡牛在 i 的方案数. 当前 ...

  3. 动态规划算法(后附常见动态规划为题及Java代码实现)

    一.基本概念 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移.一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划. 二.基本思想与策略 基本 ...

  4. Hybrid App混合模式移动应用开发(AngularJS+Cordova+Ionic)

    以前公司开发了某手机APP是通过jquerymobile来实现的,发现它对手机上的原生设备无能为力.于是在下一个项目到来之际,通过筛选最终决定使用cordova+Ionic.看起来简单,但是因为他们各 ...

  5. PostgreSQL 9.5 高可用、负载均衡和复制

    高可用.负载均衡和复制 1. 不同方案的比较 共享磁盘故障转移 共享磁盘故障转移避免了只使用一份数据库拷贝带来的同步开销. 它使用一个由多个服务器共享的单一磁盘阵列.文件系统(块设备)复制 DRBD是 ...

  6. Redis codis 搭建测试

    codis Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别,有部分命令支持 Codis ...

  7. Ubuntu16.04+TensorFlow r1.12环境搭建指南

    一.操作系统安装 OS版本:Ubuntu 16.04 (ubuntu-16.04.5-server-amd64.iso) CPU:4Core以上 内存:4GB以上 磁盘空间:80G以上 二.基础环境准 ...

  8. [MySQL]修改mysql数据库的root密码的方法

    方法1: 用SET PASSWORD命令 mysql -u root mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass ...

  9. 修改Windows帐户密码,导致Sql Server 2000无法启动

    修改Windows帐户密码,导致Sql Server 2000无法启动. --现象以管理员或同等权限用户登录 Windows XP,建立 Sql Server 2000 数据库.之后,在修改此 Win ...

  10. Maven构建war项目添加版本号

    上午接到一个新的需求,项目的war包打包之后,放在了阿里的OSS上,供其他项目下载更新时使用,但是只有一个项目名,也就是pom的artifactId,预期的结果是要加上一个版本号,能区分出是什么时候打 ...