前几天用了一下LaTeX,中文支持是用CJK来解决的,前几天有网友留言告诉我xelatex用起来比方便多了,这两天又要写个文档,试了一下果然非常方便,我真是太out了,记录一下自己的使用过程吧。
在某个厦大的mirror里面下到了压缩的texlive包,http://mirrors.xmu.edu.cn/CTAN/systems/texlive/Images/texlive2009-20091107.iso.xz,1.4个G,验证md5之后解压,xz的压缩比真高,解压出一个3个G的iso,好不容易才放得下,然后随便找个地方挂上了,接着开始安装。
安装可以用图形界面的形式,不过需要安装perl-tk,安装的时候执行:
1
| sudo ./install-tl --gui |
我系统里面没有装perl-tk就直接在命令行下安装了,安装过程很简单,几乎都是默认配置,没怎么做改动,然后就安装完成了。
安装完成后需要添加几个环境变量,安装程序会给出提示,具体内容我也记不清了,反正就是按照它说的做的。
xelatex可以直接使用系统中的字体,不再需要像CJK那样自己去制作字体,而且也不会出现种乱码的情况,以前用CJK的时候总需要在vim里面反encoding设成gb2312然后再打开文件编辑,否则就会乱码,而且以前使用hyperref宏包的时候总会出现各种冲突,非常麻烦,左侧的树状目录中文也会乱码,现在这些问题全都解决了。
下载了一个网友制作的一个中文的宏包zhfontcfg.sty:
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
| % xetex/xelatex 字体设定宏包
\ProvidesPackage{zhfontcfg}
%[cm-default]选项主要用来解决使用数学环境时数学符号不能正常显示的问题
\usepackage[cm-default]{fontspec}
\usepackage{xunicode,xltxtra}
%如果没有它,会有一些 tex 特殊字符无法正常使用,比如连字符。
\defaultfontfeatures{Mapping=tex-text}
% 中文断行
\XeTeXlinebreaklocale "zh"
\XeTeXlinebreakskip = 0pt plus 1pt minus 0.1pt
%将系统字体名映射为逻辑字体名称,主要是为了维护的方便
\newcommand\fontnamehei{文泉驿正黑}
%\newcommand\fontnamesong{SimSun}
\newcommand\fontnamekai{AR PL KaitiM GB}
%\newcommand\fontnamemono{FreeMono}
\newcommand\fontnameroman{Times New Roman}
%%设置常用中文字号,方便调用
\newcommand{\erhao}{\fontsize{22pt}{\baselineskip}\selectfont}
\newcommand{\xiaoerhao}{\fontsize{18pt}{\baselineskip}\selectfont}
\newcommand{\sanhao}{\fontsize{16pt}{\baselineskip}\selectfont}
\newcommand{\xiaosanhao}{\fontsize{15pt}{\baselineskip}\selectfont}
\newcommand{\sihao}{\fontsize{14pt}{\baselineskip}\selectfont}
\newcommand{\xiaosihao}{\fontsize{12pt}{\baselineskip}\selectfont}
\newcommand{\wuhao}{\fontsize{10.5pt}{\baselineskip}\selectfont}
\newcommand{\xiaowuhao}{\fontsize{9pt}{\baselineskip}\selectfont}
\newcommand{\liuhao}{\fontsize{7.5pt}{\baselineskip}\selectfont}
\setmainfont[BoldFont=\fontnamehei]{\fontnamehei}
\setsansfont[BoldFont=\fontnamehei]{\fontnamekai}
%\setmonofont{\fontnamemono}
%楷体
\newfontinstance\KAI {\fontnamekai}
\newcommand{\kai}[1]{{\KAI#1}}
%黑体
\newfontinstance\HEI{\fontnamehei}
\newcommand{\hei}[1]{{\HEI#1}}
%英文
\newfontinstance\ENF{\fontnameroman}
\newcommand{\en}[1]{\,{\ENF#1}\,} |
把它放到/usr/local/texlive/2009/texmf-dist/tex/xelatex/fontspec这个目录下mktexlsr更新一下缓存就可以使用了,非常方便,我去掉了这个宏里面几个我没有的字体,最郁闷的是宋体居然没有搞定,我这里只有从windows下面搞过来的simsun.ttc,貌似这个文件里面有两个字体:宋体和新宋体,然后折腾了一顿也不行,最后实在也没有找到simsun.ttf,也就放弃了,本来也不怎么用宋体,其实这个包也没怎么用,只是为了以后方便才装上的,一直用的都是Vera Sans YuanTi Mono,
1
| \setmainfont{Vera Sans YuanTi Mono} |
另外下面的命令可以查看系统中安装的中文字体:
关于宋体的问题,有看到这篇文章的朋友知道怎么解决的劳烦帮下忙啊
tasklet是IO驱动程序中实现可延迟函数的一种首选的方法,一个tasklet只可以在一个CPU上同步地执行,不同的tasklet可以在不同地CPU上同步地执行。
tasklet的实现是建立在两个软件中断的基础之上的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,本质上没有什么区别,只不过HI_SOFTIRQ的优先级更高一些,建立在HI_SOFTIRQ上的tasklet会早于TASKLET_SOFTIRQ执行。
tasklet链表是存放在tasklet_vec和tasklet_hi_vec这两个数组里面的,数组的长度就是cpu的个数,这是两个percpu数组。linux为smp定义了一系列的宏用于初始化获取删除percpu变量
1
| DEFINE_PER_CPU(struct tasklet_struct , tasklet_vec) |
这个宏的作用是创建一个类型为tasklet_struct类型的tasklet_vec数组,这个数组即属于percpu数据,具体的实现机制我暂时还没有搞明白。
1
| __per_cpu_var(tasklet_vec) |
根据percpu变量的名字获取percpu数据
创建一个tasklet首先需要创建一个tasklet描述符,即struct tasklet_struct结构体,并用tasklet_init()函数初始化它。
@include/linux/interput.h
1
2
3
4
5
6
7
8
| struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
}; |
其中count是一个原子变量的锁时数器,大于0时表示当前tasklet被禁止,tasklet_enable()和tasklet_disable()函数通过减少和增加这个字段的值来实现当前tasklet的启用和禁止。
state是tasklet的状态标志,只有两个选项:
@include/linux/interput.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| enum
{
TASKLET_STATE_SCHED, /* tasklet已经被调度,但还尚未执行 */
TASKLET_STATE_RUN /* tasklet正在执行,只针对SMP有效,单处理器无意义 */
};
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
} |
在创建完tasklet描述符之后就需要对tasklet进行调度,即把tasklet描述符插入到tasklet_vec中相应链表中去。调度函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
/* 在flasgs中保存eflags寄存器的IF标志,并关中段 */
local_irq_save(flags);
t->next = NULL;
/* 将tasklet描述符插入到tasklet_vec链表的尾部 */
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
/* 激活TASKLET_SOFTIRQ软中断 */
raise_softirq_irqoff(TASKLET_SOFTIRQ);
/* 恢复eflags寄存器的值,并打开中断 */
local_irq_restore(flags);
} |
至此tasklet的调度就完成了,对tasklet可延迟函数的处理将交由软中断处理函数处理,下面看TASKLET_SOFTIRQ软中断的处理函数:
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
| static void tasklet_action(struct softirq_action *a)
{
struct tasklet_struct *list;
/* 禁用本地中断 */
local_irq_disable();
/* 将tasket_vec中对应的tasklet链表头的地址保存在list临时变量中
* 并清空tasklet_vec中的链表,以允许新的tasklet */
list = __get_cpu_var(tasklet_vec).head;
__get_cpu_var(tasklet_vec).head = NULL;
__get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
/* 重新启用本地中断 */
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
}
} |
最近在阅读linux内核源码,把自己的一些理解发上来,一方面看到的朋友可以帮我指正我理解偏差的地方,别一方面也算是做一个简单的总结。
首先调用open_softirq()函数来初始化软件中断处理函数,将软件中断处理函数根据软中断的下标号插入到softirq_vec数组中,实现过程很简单如下:
1
2
3
4
| void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
} |
softirq_vec数据有32个元素,对应的是可以有32个软件中断,但实际上linux只是使用了其中的6个软中断,相应的每一个CPU都会有一个对应的32位的掩码__softirq_pending描述挂起的软中断,每一位对应一个软件中断,__soctirq_penging在irq_cpustat_t中定义,如下:
@include/asm/hardirq.h
1
2
3
4
5
6
| typedef struct {
unsigned int __softirq_pending;
unsigned long idle_timestamp;
unsigned int __nmi_count; /* arch dependent */
unsigned int __irq_count; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t; |
其中local_softirq_pending()宏用于选择当前CPU所对应的__softirq_penging掩码。相关的宏如下:
@include/linux/irq_cpustat.h
1
2
3
4
| #define local_softirq_pending() \
__IRQ_STAT(smp_processor_id() , __softirq_pending)
#define __IRQ_STAT(cpu , member) (irq_stat[cpu].member) |
接着调用raise_softirq()函数来激活软中断,函数如下:
@kernel/softirq.c
1
2
3
4
5
6
7
8
9
| void raise_softirq(unsigned int nr)
{
unsigned long flags;
/* 将eflags寄存器的IF标志保存到flags,并且禁用了本地CPU上的中断 */
local_irq_save(flags);
raise_softirq_irqoff(nr);
/* 根据flags变量的值恢复eflags寄存器的IF标志,重新打开本地CPU上的中断 */
local_irq_restore(flags);
} |
raise_softirq_irqoff()函数必须是在禁止中断的情况下执行的,它首先调用__raise_softirq_irqoff()宏激活软件中断,其实也就是设置当前CPU所对应的__softirq_pending所对应的软中断的位,以表示该软中断已激活。如果当前正处于中断或者软中断当中,那么raise_softirq_irqoff执行结束,否则的话就调用wakeup_softirqd()函数激活ksoftirqd/n内核线程来处理软中断。
@kernel/softirq.c
1
2
3
4
5
6
7
| inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
if (!in_interrupt())
wakeup_softirqd();
} |
__rarse_softirq_irqoff()宏在/include/linux/interput.h中定义:
1
2
3
4
| #defome __raise_softirq_irqoff(nr) \
do{ or_softirq_pending(1UL << (nr)); } while(0)
#define or_softirq_pending(x) (local_softirq_pending() |= (x)) |
其中local_softirq_pending()宏已经在上面列出来了,我不太明白的是__raise_softirq_irqoff()这个宏为什么要放到一个循环里面,它也只是执行了一次,不过linux既然这样写就一定有它的道理,慢慢再去研究吧。
这样软中断已经激活,其处理函数已经加入到了softirq_sec数组中,并且相应的标志位已经合适地设置,系统会周期性地检查已经挂起的软中断,当在local_softirq_pending()中的某个位检测到挂起的软中断的时候,就会调用do_softirq()函数对软中断进行处理。
do_softirq()函数定义如下:
@ kernel/softirq.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 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);
} |
如果在中断上下文中调用了do_softirq函数或者禁用了软件中断,那么in_interrupt函数返回1,这时候do_softirq()不做任何事情,否则,它会把eflags寄存器IF标志保存在flags中并禁用中断,然后将local_softirq_pending()的值保存在pending变量中,检测pending的值,如果pending不为0,则表示pending的某一位被设置,当前CPU就有对应的挂起的软中断需要进行处理,调用__do_softirq()对挂起的软中断进行处理,处理完成之后再根据flags变量的值恢复eflags寄存器并打开中断。
接下来看__do_softirq()函数:
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
46
47
48
49
50
51
| asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
/* 增加preempt_count字段中软中断计数器的值,
* 以此来禁用软中断,因为软件中断的可延迟函数
* 一般是在开中断的情况下执行的,这样就可能在__do_softirq 执行
* 的过程中有别一个__do_softirq的实例正在执行
* ,这样就会影响可延迟函数的串行化执行*/
__local_bh_disable((unsigned long)__builtin_return_address(0));
restart:
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
do {
if (pending & 1) {
int prev_count = preempt_count();
/* 执行软件中断处理函数 */
h->action(h);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %td %p"
"with preempt_count %08x,"
" exited with %08x?\n", h - softirq_vec,
h->action, prev_count, preempt_count());
preempt_count() = prev_count;
}
}
h++;
pending >>= 1;
} while (pending);
/* 禁用本地软中断 */
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
_local_bh_enable();
} |
内核循环地检测每一个pending的位,以处理当前位挂机的软件中断,为了防止内核在执行__do_softirq()的过程中不停地有新的softirq产生,以导致内核无暇顾及其它,__do_softirq()在循环检测的时候设置了外层循环的最大次数为10次,对pending的每一位检测10次之后如果pending的值仍不为0,则表示当前CPU仍有未处理的挂起的软件中断,这时候__do_softirq不再对它进行处理,而是唤醒内核线程ksoftirqd/n对它进行处理。
在函数开始的时候,先将软件中断的位图__softirq_pending保存在pending局部变量中,然后调用set_softirq_pending(0)以清空软中断位图,从而允许新的软中断的到来。接着调用local_irq_enable()来激活软中断。
Recent Comments