关于作者

用户名:chongsoft
笔名:chongsoft
地区: 北京-海淀
行业:本科

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言


访问统计:
文章个数:63
评论个数:8
留言条数:1




Powered by BlogDriver 2.1

Make everyday colorful!

 

学而下者谓之器,学而上者谓之道

文章

ARM Linux中断向量表搬移设计过程

ARM Linux中断向量表搬移设计过程

Copyright ©  by Chongsoft, 2009-3-8

ccerty_cn@yahoo.com.cn

Preface 引言

我在这里用一些篇幅来描述一下arm体系结构下Linux中怎样来初始化中断向量表的,因为这个方法很具有通用性,我把它叫做代码大挪移。您说搬代码谁不会阿,不就是拷贝吗,的确如此,但是拷贝也有技巧。拷贝很简单啦,其实就是memcpy,这不用提,我在这里想说的是,你怎么把你的代码设计成能随便拷贝的,换句专业的术语,叫与位置无关的代码,拷到哪都能用。我以前也用过类似的方法作启动,今天拿来说说。

Scenario 1  第一场景 copy

我们先看实际复制动作。代码的位置在arch/arm/traps.c中,kernel version: 2.6.27 这个是初始化部分的代码,setup_arch()->early_trap_init(). 熟悉初始化部分的朋友们可能见到过这段代码。

 

void __init early_trap_init(void)

{

       unsigned long vectors = CONFIG_VECTORS_BASE;

       extern char __stubs_start[], __stubs_end[];

       extern char __vectors_start[], __vectors_end[];

       extern char __kuser_helper_start[], __kuser_helper_end[];

       int kuser_sz = __kuser_helper_end - __kuser_helper_start;

 

       /*

        * Copy the vectors, stubs and kuser helpers (in entry-armv.S)

        * into the vector page, mapped at 0xffff0000, and ensure these

        * are visible to the instruction stream.

        */

       memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

       memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

       memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

      

}

 

实际copy动作一目了然,就是两个memcpy(第三个实际上是拷贝一些别的东西,原理是一样的,这里不提了). Copy的源是vectors,这个值是CONFIG_VECTORS_BASE一般来讲,是0xffff0000,当然你可以根据硬件的设定自己配制这个值。把什么东西往那copy?第一部分是从__vectors_start__vectors_end之间的代码,第二部分是从__stubs_start__stubs_end之间的代码,而第二部分是copyvectors + 0x200起始的位置。也就是说,两部分之间的距离是0x200,512个字节。

我们来看__vectors_start__vectors_end__stubs_start__stubs_end到底是什么东西,只要知道它们在哪里定义的,就知道怎么回事了。

Scenario 2  第二场景 主角闪亮登场

它们埋伏在arch/arm/kernel/entry-armv.S中,个文件是arm中各个模式的入口代码,熟悉arm的朋友们知道arm有几种模式,不知道的自己查查,不说了。我们取一个片断,和我们的阐述相关的部分。为了让大家看得更清楚,我删掉了部分代码和注释,把主干凸显出来。有兴趣的朋友可以查看源代码,研究全部,里面还是比较有内涵的。

 

       .globl      __stubs_start

__stubs_start:

/*

 * Interrupt dispatcher

 */

       vector_stub    irq, IRQ_MODE, 4

// 请注意这里:vector_stub是一个宏,展开后是一块代码,下面是个跳转表,我们将代码结// 构展开,大致是这样的结构: (后面的vector_stub    dabt, ABT_MODE, 8等展开过程全一样,在此略过不提)

// -------------------------------- begin 展开

.align       5

vector_irq:

       sub  lr, lr, 4

      

       @ Save r0, lr_<exception> (parent PC) and spsr_<exception>

       @ (parent CPSR)

       @

       stmia       sp, {r0, lr}            @ save r0, lr

       mrs  lr, spsr

       str   lr, [sp, #8]             @ save spsr

      

       @ Prepare for SVC32 mode.  IRQs remain disabled.

       @

       mrs  r0, cpsr

       eor   r0, r0, IRQ_MODE ^ SVC_MODE)

       msr  spsr_cxsf, r0

      

       @ the branch table must immediately follow this code

       @

       and  lr, lr, #0x0f

       mov r0, sp

       ldr   lr, [pc, lr, lsl #2]

       movs      pc, lr                     @ branch to handler in SVC mode

       // -------------------------------- end 展开

 

       .long       __irq_usr               @  0  (USR_26 / USR_32)

       .long       __irq_invalid                 @  1  (FIQ_26 / FIQ_32)

       .long       __irq_invalid                 @  2  (IRQ_26 / IRQ_32)

       .long       __irq_svc                     @  3  (SVC_26 / SVC_32)

       。。。

.long       __irq_invalid                 @  f

 

/*

 * Data abort dispatcher

 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC

 */

       vector_stub    dabt, ABT_MODE, 8

 

       .long       __dabt_usr              &nbs;    @  0  (USR_26 / USR_32)

       .long       __dabt_invalid               @  1  (FIQ_26 / FIQ_32)

       .long       __dabt_invalid               @  2  (IRQ_26 / IRQ_32)

       .long       __dabt_svc                   @  3  (SVC_26 / SVC_32)

    。。。

       .long       __dabt_invalid               @  f

 

/*

 * Prefetch abort dispatcher

 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC

 */

       vector_stub    pabt, ABT_MODE, 4

 

       .long       __pabt_usr                   @  0 (USR_26 / USR_32)

       .long       __pabt_invalid               @  1 (FIQ_26 / FIQ_32)

       .long       __pabt_invalid               @  2 (IRQ_26 / IRQ_32)

       .long       __pabt_svc                   @  3 (SVC_26 / SVC_32)

。。。

       .long       __pabt_invalid               @  f

 

/*

 * Undef instr entry dispatcher

 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC

 */

       vector_stub    und, UND_MODE

 

       .lon       __und_usr                    @  0 (USR_26 / USR_32)

       .long       __und_invalid                @  1 (FIQ_26 / FIQ_32)

       .long       __und_invalid                @  2 (IRQ_26 / IRQ_32)

       .long       __und_svc                    @  3 (SVC_26 / SVC_32)

    。。。

       .long       __und_invalid                @  f

 

       .align       5

 

vector_fiq:

       disable_fiq

       subs pc, lr, #4

 

vector_addrexcptn:

       b     vector_addrexcptn

/*

 * We group all the following data together to optimise

 * for CPUs with separate I & D caches.

 */

       .align       5

 

.LCvswi:

       .word      vector_swi

 

       .globl      __stubs_end

__stubs_end:

 

       .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 

       .globl      __vectors_start

__vectors_start:

       swi  SYS_ERROR0

       b     vector_und + stubs_offset

       ldr   pc, .LCvswi + stubs_offset

       b     vector_pabt + stubs_offset

       b     vector_dabt + stubs_offset

       b     vector_addrexcptn + stubs_offset

       b     vector_irq + stubs_offset

       b     vector_fiq + stubs_offset

 

       .globl      __vectors_end

__vectors_end:

 

为了让大家看得更清,我把代码的结构再次简化成这样:

       .globl      __stubs_start

__stubs_start:

.align       5

vector_irq:

[code part]                // 展开代码

[jump table part]           // 地址跳转表

。。。

.align       5

vector_dabt:

[code part]

[jump table part]

 

。。。

.align       5

vector_ pabt:

[code part]

[jump table part]

 

。。。

.align       5

vector_und:

[code part]

[jump table part]

 

。。。

.align       5

vector_fiq:

。。。

 

       .globl      __stubs_end

__stubs_end:

 

       .globl      __vectors_start

__vectors_start:

       swi  SYS_ERROR0

       b     vector_und + stubs_offset

       ldr   pc, .LCvswi + stubs_offset

       b     vector_pabt + stubs_offset

       b     vector_dabt + stubs_offset

       b     vector_addrexcptn + stubs_offset

       b     vector_irq + stubs_offset

       b     vector_fiq + stubs_offset

 

       .globl      __vectors_end

__vectors_end:

 

在这里我不花过多的篇幅去解释代码的意思,这不是本文的目的,只要你把结构看清,就达到目的了。但我会花点时间研究一下展开代码部分(蓝色)的特征,这部分代码是与位置无关的代码,我们稍微研究一下,它为什么会这么写。

.align       5

span lang="EN-US" style="COLOR: blue">vector_irq:

[code part]                // 展开代码

[jump table part]           // 地址跳转表

。。。

 

首先这部分代码大致都是一样的结构,前面是一些代码,后面跟着一个跳转表。跳转表里面定义了一些地址。我们截取这部分看

。。。

@ the branch table must immediately follow this code

@

and  lr, lr, #0x0f      (1)       // lr中当前存储了上一个状态寄存器的值,对后几位做与,

// 就是取在中断前处在用户态还是核心态,这个值用作跳

// 转表的索引

mov r0, sp            (2)  // 用做他用,sp值当第一个参数传给后面函数

ldr   lr, [pc, lr, lsl #2]   (3)  // pc是当前执行指令地址加8,即跳转表的基地址,lr是索引

                         // 很好的技巧,取pc找当前地址什么时候都没错

movs      pc, lr                     @ branch to handler in SVC mode

 

[jump table]

       .long       __irq_usr               @  0  (USR_26 / USR_32)

       .long     &nbp; __irq_invalid                 @  1  (FIQ_26 / FIQ_32)

       .long       __irq_invalid                 @  2  (IRQ_26 / IRQ_32)

       .long       __irq_svc                     @  3  (SVC_26 / SVC_32)

 

 

 

真正的跳转在最后一句完成,大家都看得很清楚。跳到哪里去了,如果中断以前是svc模式,就会跳到__irq_svc。我们发现这里不会直接用b(bl,bx)个,

ü         一是b跳转后面是个偏移,而这个偏移是有限制的,不能太大

ü         二是b跳转后面的偏移你不知道在代码拷贝后还是不是那个样子,因为我们要搬移代码,所以如果你不能确定搬移后的偏移不变,那你就用绝对地址,而上面的代码前三句就是算出绝对地址来,然后用绝对地址赋值给pc直接完成跳转。

 

这些都是一些技巧,总之你要注意的是写位置无关的代码时涉及到跳转部分,用b跳转还是直接赋成绝对地址(通过跳转表实现),如果你不能保证搬移后的偏移一致,写这部分就要注意了,要用一些技巧的。

大家可以去用gcc -fPIC-S选项汇编一个小的函数看看,fPIC就是与位置无关选项,相信编译过动态库的人都熟悉,看看它是怎么做的。你会发现异曲同工。

Scenario 3  第三场景 大搬移

我用一个章节来介绍大搬移的过程,以及一些在搬移中Linux出现的问题及解决方案。我把整的搬移过程做成一张图里,然后讨论了一些技术细节。我们看到这是一个巨大无比的图,我们这章节的所阐述的内容都在图里。

  大搬移

我们将搬移前的代码组织称为Code/Load 视图,因为这是代码中的(或image中的)组织情况,把搬移后的代码组织称为Exec视图,反映的是代码执行时代码在内存中的情况。我刚才讲过了第一场景的情况,忘了的回到第一场景中去看,两个memcpy的执行过程在图中也有表示,就是蓝色和红色的带箭头的虚线,这就是代码从code viewexec view的拷贝过程,一目了然,不用多说。

现在出现了一个问题,就是我们发现在__vector_start__vector_end之间的代码有点怪异,我们再次摘到这里来看:

 

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

       .globl      __vectors_start

__vectors_start:

       swi  SYS_ERROR0

       b     vector_und + stubs_offset

       ldr   pc, .LCvswi + stubs_offset

       b     vector_pabt + stubs_offset

       b     vector_dabt + stubs_offset

       b     vector_addrexcptn + stubs_offset

       b     vector_irq + stubs_offset

       b     vector_fiq + stubs_offset

 

       .globl      __vectors_end

__vectors_end:

 

在第二个场景中我们说过,这叫做位置无关的代码,因为要拷贝到别的地方。而且里面都是跳转指令。我们发现了除了第三个行代码用了绝对地址进行了跳转,其它都是用的b跳转。举个例子,b      vector_dabt + stubs_offset(vector_dabt__stubs_start__stubs_end之间),如果你用b vector_dabt, 这肯定是有问题的,因为copy之后exec view的组织(map)是不一样的,所以b后这个偏移就不对了。这里面,我们就要对这个偏移进行一次调整。Stubs_offset就是这个调整值,是可以计算出来的,具体的计算过程在图中讲得比较清楚,这里不提了。大家可以在图中看到详细的推导过程。

其实尽管ldr   pc, .LCvswi + stubs_offset这条指令用的是绝对地址跳转,用得跳转表的方法,但找地址的过程也用到了这个技术。我们看到

       .align       5

.LCvswi:

       .word      vector_swi

.LCvswi这个位置存储的是一个地址,就是要跳到这个地方。.align 5的意思是32字节对齐,这个是保证cache line对齐的,不提了。在exec view中找这个地址,就得加上个offset.原理是一样的,因为.LCvswi__stubs_start__stubs_end之间,这个区域被搬移了,不能直接用这个标签地址了,vector_swi没有被搬移,所以可以直接用。

 

总结一下。我觉得我要讲的东西虽然是Linux中的技术细节,描述的确是代码搬移过程原理和注意事项。其实更重要的是,我们如何把这一个过程倒过来,即在涉及到代码搬移的场合中如何进行设计,如何运用这些技术实现这一设计过程。你可以遵循这样的指导步骤:

1.      画出那个大图来,按自己的要求确定Code viewExec view, 设计搬移区段和设计搬移的位置

2.      写出要搬移的代码,运用位置无关的技术(上面提到的)进行编码和检验

3.      用类似memcpy的代码进行搬移

 

- 作者: chongsoft 2009年03月8日, 星期日 17:21  回复(0) |  引用(0) 加入博采

S3C2440 中断系统 (转)

原理:
CPU在正常執行工作時,由CPU內部或外部產生例外的request,要求 CPU suspend working job,做些必要的處理,以滿足各種預料外的狀況。

種類:
有兩種 硬體中斷 and 軟體中斷
硬體中斷:
硬體 能夠利用CPU的 interrupt request signal 來通知CPU中斷
軟體中斷:
CPU自己發出

處理interrupt原則
1.當下 CPU狀態記錄下來
2.跳到中斷處理程式做中斷處理


S3C2440 user manual第十四章有提及 interrupt controller
1.提供60個 interrupt sources, including DMA controller, UART, IIC, USB host and device, RTC(INT_TICK:RTC Time tick interrupt)[INT_RTC:RTC alarm interrupt].
2.interrupt request分兩種 FIQ(fast interrupt request) and IRQ(normal interrupt request)
3.CPU會根據硬體優先權 把結果寫到 interrupt pending register (紀錄哪個中斷產生)
,S3C2440A有兩個 interrupt pending registers: source pending register (SRCPND) and interrupt pending register(INTPND) 負責紀錄哪個中斷需要產生
如果要中斷來源要產生中斷,相對應SRCPND = 1 and INTPND = 1
4. interrupt mask register 負責作遮罩 讓某個中斷disable


INTERRUPT PRIORITY GENERATRING BLOCK
有32個 interrupt requests
先由6個 arbiter(仲裁者)x
決定誰的中斷可以採納 轉成 req0~5
最後交給第7個中斷來決定

arbiter
每個仲裁者 有one bit arbiter mode control(ARB_MODE) and two bits of selection control signals(ARB_SEL)
ARB_MODE 則負責啟動or 關閉 the function - 調整優先權
ARB_SEL 調整 interrupt 優先權順序


INTERRUPT CONTROLLER SPECIAL REGISTERS
1. source pending register (SRCPND):所有 interrupt request from the interrupt sources 被登記到 source pending register
SRCPND 位置在 0x4A000000 R/W
0 = The interrupt has not been requested
這樣來說 哪個interrupt source要產生interrupt 先在此註冊(registe)
ex. 啟動RTC tick interrupt
設定此register為(0x1<<8)

2. interrupt mode register: 紀錄 interrupt request mode: FIQ and IRQ
0 = IRQ mode
1 = FIQ mode
順帶一提
只有一個interrupt能夠被設定為FIQ mode in the interrupt controller
(our system doesn't use this much)

3. interrupt mask register(INTMSK): 負責作遮罩
0 = interrupt service is available
1= interrupt service is masked
[so HWIntItem has function Enable() and Disable()
void Enable(){INTMSK &= ~(0x1<<8)}
In RTC init will call CHWIntItem::Enable to unmask RTC tick]

4. priority register ::負責控制arbiter 跟上述 interrupt priority generating block and interrupt priority有關
[Register, Address, R/W, Description, Reset Value]
[PRIORITY,0x4A00000C, R/W, IRQ priority register, 0x7F]
擁有21bit
0~6 bit 分別控制 arbiter 0~6 是否要調整priority
0 = priority does not rotate
1 = priority rotate enable

7~20 bit 分別去調整priority優先順序
可參考14-5
7-8 bit 負責作arbiter 0 priority調整
00 = REQ 1-2-3-4
01 = REQ 2-3-4-1
10 = REQ 3-4-1-2
11 = REQ 4-1-2-3
arbiter0 有四個 interrupt request
REQ1/EINT0
REQ2/EINT1
REQ3/EINT2
REQ4/EINT3
default value is 00
[our system does not use this register for some purpose]

5. interrupt pending register(INTPND)
bit in the pending register shows whether the corresponding interrupt request has the highest priority.
Only one bit can be set to 1. in INTPNDD.
Manual 中有提到 in 14-144
"Like the SRCPND register, this register has to be cleared in the interrupt service routine after clearing the SRCPND register."
clearpending function can set 1 and 5


continue
INTERRUPT OFFSET (INTOFFSET)
(not used in system)
The value in the INTOFFSET show which interrupt request of IRQ mode is in the INTPND register.
This bit can be cleared automatically by clearing SRCPND and INTPND.

SUB SOURCE PENDING(SUBSRCPND) REGISTER
indicate the interrupt request status.
1 = the interrupt source has asserted the interrupt request

INTERRUPT SUBMASK(INTSUBMSK) REGISTER
Determine which interrupt source is masked. The masked interrupt source will not be serviced.
1 = interrupt service is masked.
The function is the same with INTMSK. the difference is interrupt source.
ex. INTMSK第九個bit 負責 INT_WDT_AC97
INTUBMSK 第13個bit 負責INT_WDT
第14個bit 負責INT_AC97


Reference:
http://www.eforth.com.tw/Csoc/eSOCFM/esoc_dol/AN/AN0001.pdf
S3C2440 user manual

- 作者: chongsoft 2009年01月16日, 星期五 12:30  回复(0) |  引用(0) 加入博采

setsockopt使用 (转)

Linux系统调用--getsockopt/setsockopt函数详解

2007-09-07 23:33

【 getsockopt/setsockopt系统调用】  
   
功能描述:
获取或者设置与某个套接字关联的选 项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该 将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议 号TCP。


用法:
#include <sys/types.h>
#include <sys/socket.h>

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

参数:  
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。

   
返回说明:  
成功执行时,返回0。失败返回-1,errno被设为以下的某个值  
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字

http://club.cn.yahoo.com/bbs/threadview/1200062866_62__pn1.html

--------------------------
开发网络程序使用setsockopt的一些好并且需要常注意的地方:

1. 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用
closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
TIME_WAIT的过程:
BOOL   bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL   bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体
应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
   u_short     l_onoff;
   u_short     l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
// 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
Note:1.在设置了逗留延时,用于一个非阻塞的socket是作用不大的,最好不用;
     2.如果想要程序不经历SO_LINGER需要设置SO_DONTLINGER,或者设置l_onoff=0;

10.还一个用的比较少的是在SDI或者是Dialog的程序中,可以记录socket的调试信息:
(前不久做过这个函数的测试,调式信息可以保存,包括socket建立时候的参数,采用的
具体协议,以及出错的代码都可以记录下来)
BOOL bDebug=TRUE;
setsockopt(s,SOL_SOCKET,SO_DEBUG,(const char*)&bDebug,sizeof(BOOL));

zhaizi
http://www.libing.net.cn/post/setsockopt.php

- 作者: chongsoft 2008年11月9日, 星期日 15:06  回复(0) |  引用(0) 加入博采

Anti-strace 技术 strace及相关内核的实现细节(转)

--[ 内容
    0 - 前言
    1 - strace的原理
    2 - strace死循环的分析
    3 - anti-strace
        3.1 方法一
        3.2 方法二
        3.3 方法三
    4 - anti anti-strace
        4.1 int3的情况
        4.2 kill的情况
    5 - 参考
    6 - strace.4.5.8.patch
--[ 0 - 前言
前面在介绍Burneye加密文档的时候,碰到了两个问题,一个是GDB中无法配置断点,另一个
问题是strace时死循环。GDB的问题已找到,无法配置断点是GDB的一个Bug,具体的结
论见[1].至于strace的问题,一直没有解决,假如真能写出一个让strace死循环的程式,
也算是一种anti-strace的技术。但是一直没有将死循环的现象用程式重现。
后来经Grip2的指点,发现了问题的原因,经过对内核和strace源代码的研究,写了一个
防止strace死循环的patch,之后更进一步的patch,使得strace更加健壮,能够跟踪anti-
strace程式的系统调用。
在这里感谢Grip2的帮助和测试程式。
本文的环境是Redhat Fedora Core 2, Linux 2.6.5/2.6.8.1,
            gcc 3.3.1, strace 4.5.8
--[ 1 - strace的原理
要想了解strace的原理,首先得谈谈ptrace。
ptrace是操作系统为了调试为用户程式提供的系统接口。先来看看ptrace的用法:[2]
       long  ptrace(enum __ptrace_request request, pid_t pid, void *addr, void
       *data)
strace需要使用的__ptrace_request主要有以下几个:
被调试的进程)
       PTRACE_TRACEME 自己主动提供被跟踪的请求,当被跟踪的进程收到信号时,会先被
                      父进程截获.同样,execve时也是如此.
监控进程strace)
       PTRACE_GETREGS 获得被跟踪进程的寄存器状况,周详的结构请参见asm/user.h的
                      user_regs_struct
       PTRACE_PEEKDATA 获得系统堆栈中的参数
       PTRACE_SYSCALL 这是最重要的,每次本跟踪的进程在系统调用时,ptrace会返回
                      两次,一次是系统调用之前,会调用PTRACE_PEEKDATA获得参数
                      值,另一次是系统调用之后,会调用PTRACE_GETREGS获得返回值
ENTRY(system_call)
...
                                        # system call tracing in operation
        testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
        jnz syscall_trace_entry
...
syscall_trace_entry:
        movl $-ENOSYS,EAX(%esp)
        movl %esp, %eax
        xorl %edx,%edx
        call do_syscall_trace    do_sigaction()
2298         if (signal_pending(current)) {
2299                 /*
2300                  * If there might be a fatal signal pending on multiple
2301                  * threads, make sure we take it before changing the action.
2302                  */
2303                 spin_unlock_irq(¤t->sighand->siglock);
2304                 return -ERESTARTNOINTR;
2305         }
也就是说当程式执行sys_signal时,假如有信号还未处理,就直接返回-ERESTARTNOINTR
接下来当系统调用返回时,会依次调用
resume_userspace->work_pending->work_notify_sig->do_notify_resume->do_sign
al
590  no_signal:
591         /* Did we come from a system call? */
592         if (regs->orig_eax >= 0) {
593                 /* Restart the system call - no handlers present */
594                 if (regs->eax == -ERESTARTNOHAND ||
595                     regs->eax == -ERESTARTSYS ||
596                     regs->eax == -ERESTARTNOINTR) {
597                         regs->eax = regs->orig_eax;
598                         regs->eip -= 2;
599                 }
600                 if (regs->eax == -ERESTART_RESTARTBLOCK){
601                         regs->eax = __NR_restart_syscall;
602                         regs->eip -= 2;
603                 }
604         }
605         return 0;
606 }
此时,  regs->eip -= 2;正好代表int $0x80 (0xcd 0x80)两个字节,可见,内核重启
系统调用。
那么,究竟什么时候signal_pending(current)为真呢?经过GDB调试,发现strace在处理
sys_signal系统调用时,syscall.c:trace_syscall会调用signal的sys_signal函数
        sys_res = (*sysent[tcp->scno].sys_func)(tcp)
signal.c::sys_signal()
...
#ifndef USE_PROCFS
                        if (tcp->u_arg[0] == SIGTRAP) {
                                tcp->flags |= TCB_SIGTRAPPED;
                                kill(tcp->pid, SIGSTOP);
                        }
#endif /* !USE_PROCFS */
死循环的问题就在这个kill(tcp->id, SIGSTOP)上,当程式被处于跟踪的时刻,向已
停止的进程发送SIGSTOP本来是没有必要的,不知道strace的作者为什么还要单独来上这
么一句?Linux 2.4和2.6内核又出现了差异,在2.6内核的do_sigaction判断了是否有信
号pending,而2.4却没有,因此在2.4的机器上,运行strace不会出现死循环的情况,在
2.6上,我们只须将改行注释掉即可。
我们能够认为这是strace和2.6内核不兼容的一个Bug!
注意:假如您在程式里用的是C库的signal,实际上使用的是sys_rt_sigaction而不是
sys_signal,而strace在处理sys_rt_sigaction时,并没有kill(tcp->pid, SIGSTOP);
也许strace的作者认为用C写的程式不会用到sys_signal?
接下来让我们试一试新的strace来跟踪burneye加密的程式
#./strace /tmp/ls.new (ls.new是burneye加密的程式)
execve("/tmp/ls.new", ["/tmp/ls.new"], [/* 20 vars */]) = 0
signal(SIGTRAP, 0x5371991)              = 0 (SIG_DFL)
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
+++ killed by SIGSEGV ++
OK,不死循环了,但是我们现在还无法继续跟踪,因为burneye加密的时候使用了某些anti-
strace的技术。
--[ 3 - anti-strace的原理
了解了strace的工作原理,就能够有针对的使用anti-strace的技术
--[ 3.1 方法一
利用上边介绍的strace和2.6内核的不兼容,造成死循环.对上边打过patch的strace不适用
测试程式(Grip2提供)
#include
#include
#include
#include
#include
#include
#include
void sig_handler(int sig)
{
        printf("signal trap\n");
        return;
}
static inline int my_signal(int num, void *func)
{
        int ret;
       
        __asm__ __volatile__ ( "int $0x80"
                                :"=a"(ret)
                                :"0" (48), "b" ((long)num),
                                "c" ((int)func));
        return ret;
       
}       
       
int main(int argc, char *argv[])
{
        my_signal(SIGTRAP, sig_handler);
        return 0;
}
注意一定不能使用C库的signal,原因在前边已提过
--[ 3.2 方法二
自己发送int3
这种方法是Silvio Cesare在[3]中介绍的方法,由程式自己执行int3,这样会产生一个
陷阱,内核会向程式发送一个SIGTRAP信号,由于程式被跟踪,因此信号由strace截获,
根据strace的源代码,if (ptrace(PTRACE_SYSCALL, pid, (char *) 1, 0)
#include
#include
#include
#include
#include
#include
static int not_trace
void sig_handler(int sig)
{
        not_trace++;
        return;
}
       
int main(int argc, char *argv[])
{
        signal(SIGTRAP, sig_handler);
        __asm__ __volatile__ ( "int3" );
        if(!not_trace){
                printf("TRACING...\n");
                exit(-1);
        }
        return 0;
}
--[ 3.3 方法三
程式使用kill向自己发送SIGTRAP,跟方法二类似,这里就不赘述了
        kill(getpid(), SIGTRAP);
--[ 4 - anti anti-strace
现在我们来进一步完善strace,让他也能对付方法二和方法三,其实问题的关键就是看
strace在ptrace退出之后,下次使用ptrace能不能将SIGTRAP信号返回给被跟踪程式。
根据ptrace的手册页对PTRACE_CONT和PTRACE_SYSCALL的描述
PTRACE_CONT ... If data is non-zero and not SIGSTOP, it is interpreted as a
             signal to be delivered to the child ...
PTRACE_SYSCALL ... Restarts the stopped child as for PTRACE_CONT
看来我们只须在下一次调用ptrace时指定返回的信号是SIGTRAP即可,但是不能胡乱发送,
只有在发现int3和kill(pid, SIGTRAP)的情况下才适用。
--[ 4.1 int3的情况
首先,我们需要判断int3的情况,因此,需要在strace.c::trace()最后添加以下几行
tracing:
                if(ptrace(PTRACE_GETREGS, pid, NULL, (int)®s) scno != __NR_sigreturn && tcp->scno != __NR_rt_sigreturn)
--[ 4.2 kill的情况
kill情况比较简单,只须在sys_kill调用的第二次ptrace返回时,将返回值设定为SIGTRAP
                if(tcp->scno == __NR_kill){
                        if(!(tcp->flags & TCB_INSYSCALL)){
                                tprintf("\n!! Self SIGTRAP !!\n");
                                if(ptrace(PTRACE_SYSCALL,
                                        pid,
                                        (char *)1,
                                        SIGTRAP)

附录:

strace使用及举例

(一) strace 命令
  
 
  strace: option requires an argument -- e
  usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]
   [-p pid] ... [-s strsize] [-u username] [-E var=val] ...
   [command [arg ...]]
   or: strace -c [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...
   [command [arg ...]]
  -c -- count time, calls, and errors for each syscall and report summary
  -f -- follow forks, -ff -- with output into separate files
  -F -- attempt to follow vforks, -h -- print help message
  -i -- print instruction pointer at time of syscall
  -q -- suppress messages about attaching, detaching, etc.
  -r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs
  -T -- print time spent in each syscall, -V -- print version
  -v -- verbose mode: print unabbreviated argv, stat, termio[s], etc. args
  -x -- print non-ascii strings in hex, -xx -- print all strings in hex
  -a column -- alignment COLUMN for printing syscall results (default 40)
  -e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...
   options: trace, abbrev, verbose, raw, signal, read, or write
  -o file -- send trace output to FILE instead of stderr
  -O overhead -- set overhead for tracing syscalls to OVERHEAD usecs
  -p pid -- trace process with process id PID, may be repeated
  -s strsize -- limit length of print strings to STRSIZE chars (default 32)
  -S sortby -- sort syscall counts by: time, calls, name, nothing (default time)
  -u username -- run command as username handling setuid and/or setgid
  -E var=val -- put var=val in the environment for command
  -E var -- remove var from the environment for command
  
  
  strace - 跟踪系统调用和信号
  
  usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] [-o file]
  [-p pid] [-s strsize] [-u username] [command [arg]]
  strace -c [-e expr] [-O overhead] [-S sortby] [command [arg]]
  
  -a column
   指定显示返回值的列位置,默认是40(从0开始计数),就是说"="出现在40列的位
   置。
  
  -c 产生类似下面的统计信
  
   strace -c -p 14653 (Ctrl-C)
   % time seconds usecs/call calls errors syscall
   ------ ----------- ----------- --------- --------- ----------------
   53.99 0.012987 3247 4 2 wait4
   42.16 0.010140 2028 5 read
   1.78 0.000429 61 7 write
   0.76 0.000184 10 18 ioctl
   0.50 0.000121 2 52 rt_sigprocmask
   0.48 0.000115 58 2 fork
   0.18 0.000043 2 18 rt_sigaction
   0.06 0.000014 14 1 1 stat
   0.03 0.000008 4 2 sigreturn
   0.02 0.000006 2 3 time
   0.02 0.000006 3 2 1 setpgid
   ------ ----------- ----------- --------- --------- ----------------
   100.00 0.024053 114 4 total
  
   -d 输出一些strace自身的调试信息到标准输出
  
   strace -c -p 14653 -d (Ctrl-C)
   [wait(0x137f) = 14653]
   pid 14653 stopped, [SIGSTOP]
   [wait(0x57f) = 14653]
   pid 14653 stopped, [SIGTRAP]
   cleanup: looking at pid 14653
   % time seconds usecs/call calls errors syscall
   ------ ----------- ----------- --------- --------- ----------------
   ------ ----------- ----------- --------- --------- ----------------
   100.00 0.000000 0 total
  
   -e expr
   A qualifying expression which modifies which events to trace or how to trace
   them. The format of the expression is:
  
   [qualifier=][!]value1[,value2]...
  
   这里qualifier可以是trace、abbrev、verbose、raw、signal、read或者write。
   value是qualifier相关的符号或数值。缺省qualifier是trace。!表示取反。
   -eopen等价于-e trace=open,表示只跟踪open系统调用。-etrace=!open意思是
   跟踪除open系统调用之外的其他所有系统调用。此外value还可以取值all和none。
  
   某些shell用!表示重复历史指令,此时可能需要引号、转义符号(\)的帮助。
  
   -e trace=set
   只跟踪指定的系统调用列表。决定跟踪哪些系统调用时,-c选项很有用。
   trace=open,close,read,write意即只跟踪这四种系统调用,缺省是trace=all
  
   -e trace=file
   跟踪以指定文件名做参数的所有系统调用。
  
   -e trace=process
   Trace all system calls which involve process management. This is
   useful for watching the fork, wait, and exec steps of a process.
  
   -e trace=network
   跟踪所有和网络相关的系统调用
  
   -e trace=signal
   Trace all signal related system calls.
  
   -e trace=ipc
   Trace all IPC related system calls.
  
   -e abbrev=set
   Abbreviate the output from printing each member of large structures.
   缺省是abbrev=all,-v选项等价于abbrev=none
  
   -e verbose=set
   Dereference structures for the specified set of system calls.
   The default is verbose=all.
  
   -e raw=set
   Print raw, undecoded arguments for the specifed set of system calls.
   This option has the effect of causing all arguments to be printed in
   hexadecimal. This is mostly useful if you don"t trust the decoding or
   you need to know the actual numeric value of an argument.
  
   -e signal=set
   只跟踪指定的信号列表,缺省是signal=all。signal=!SIGIO (or signal=!io)
   导致 SIGIO 信号不被跟踪
  
   -e read=set
   Perform a full hexadecimal and ASCII dump of all the data read from
   file descriptors listed in the specified set. For example, to see all
   input activity on file descriptors 3 and 5 use -e read=3,5. Note that
   this is independent from the normal tracing of the read(2) system call
   which is controlled by the option -e trace=read.
  
   -e write=set
   Perform a full hexadecimal and ASCII dump of all the data written to
   file descriptors listed in the specified set. For example, to see all
   output activity on file descriptors 3 and 5 use -e write=3,5. Note
   that this is independent from the normal tracing of the write(2)
   system call which is controlled by the option -e trace=write.
  
   -f
   follow forks,跟随子进程?
  
   Trace child processes as they are created by currently traced
   processes as a result of the fork(2) system call. The new process
   is attached to as soon as its pid is known (through the return value
   of fork(2) in the parent process). This means that such children may
   run uncontrolled for a while (especially in the case of a vfork(2)),
   until the parent is scheduled again to complete its (v)fork(2)
   call. If the parent process decides to wait(2) for a child that is
   currently being traced, it is suspended until an appropriate child
   process either terminates or incurs a signal that would cause it to
   terminate (as determined from the child"s current signal disposition).
  
   意思应该是说跟踪某个进程时,如果发生fork()调用,则选择跟踪子进程
   可以参考gdb的set follow-fork-mode设置
  
   -F
   attempt to follow vforks
   (On SunOS 4.x, this is accomplished with some dynamic linking trickery.
   On Linux, it requires some kernel functionality not yet in the
   standard kernel.) Otherwise, vforks will not be followed even if -f
   has been given.
  
   类似-f选项
  
   -ff
   如果-o file选项有效指定,则跟踪过程中新产生的其他相关进程的信息分别写
   入file.pid,这里pid是各个进程号。
  
   -h
   显示帮助信息
  
   -i
   显示发生系统调用时的IP寄存器值
   strace -p 14653 -i
  
   -o filename
   指定保存strace输出信息的文件,默认使用标准错误输出stderr
  
   Use filename.pid if -ff is used. If the argument begins with `|" or
   with `!" then the rest of the argument is treated as a command and all
   output is piped to it. This is convenient for piping the debugging
   output to a program without affecting the redirections of executed
   programs.
  
   -O overhead
   Set the overhead for tracing system calls to overhead microseconds.
   This is useful for overriding the default heuristic for guessing how
   much time is spent in mere measuring when timing system calls using
   the -c option. The acuracy of the heuristic can be gauged by timing
   a given program run without tracing (using time(1)) and comparing
   the accumulated system call time to the total produced using -c.
  
   好象是用于确定哪些系统调用耗时多
  
   -p pid
  
   指定待跟踪的进程号,可以用Ctrl-C终止这种跟踪而被跟踪进程继续运行。可以
   指定多达32个-p参数同时进行跟踪。
  
   比如 strace -ff -o output -p 14653 -p 14117
  
   -q
   Suppress messages about attaching, detaching etc. This happens
   automatically when output is redirected to a file and the command is
   run directly instead of attaching.
  
   -r
   Print a relative timestamp upon entry to each system call. This
   records the time difference between the beginning of successive
   system calls.
  
   strace -p 14653 -i -r
  
   -s strsize
   指定字符串最大显示长度,默认32。但文件名总是显示完整。
   -S sortby
   Sort the output of the histogram printed by the -c option by the
   specified critereon. Legal values are time, calls, name, and nothing
   (default time).
  
   -t
   与-r选项类似,只不过-r采用相对时间戳,-t采用绝对时间戳(当前时钟)
  
   -tt
   与-t类似,绝对时间戳中包含微秒
  
   -ttt
   If given thrice, the time printed will include the microseconds and
   the leading potion will be printed as the number of seconds since
   the epoch.
  
   -T
   这个选项显示单个系统调用耗时
  
   -u username
   用指定用户的UID、GID以及辅助组身份运行待跟踪程序
  
   -v
   冗余显示模式
   Print unabbreviated versions of environment, stat, termios, etc. calls.
   These structures are very common in calls and so the default behavior
   displays a reasonable subset of structure members. Use this option to
   get all of the gory details.
  
   -V
   显示strace版本信息
  
   -x 以16进制字符串格式显示非ascii码,比如"\x08",默认采用8进制,比如"\10"
  
   -xx 以16进制字符串格式显示所有字节

===============================================

(二)应用
strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。
  strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。
  下面记录几个常用 option .
  1 -f -F选项告诉strace同时跟踪fork和vfork出来的进程
  2 -o xxx.txt 输出到某个文件。
  3 -e execve 只记录 execve 这类系统调用
  -------------------------------------------------------------------------------------------------------------------------
  进程无法启动,软件运行速度突然变慢,程序的"SegmentFault"等等都是让每个Unix系统用户头痛的问题,
  本文通过三个实际案例演示如何使用truss、strace和ltrace这三个常用的调试工具来快速诊断软件的"疑难杂症"。
  
  
  truss和strace用来跟踪一个进程的系统调用或信号产生的情况,而 ltrace用来跟踪进程调用库函数的情况。truss是早期为System V R4开发的调试程序,包括Aix、FreeBSD在内的大部分Unix系统都自带了这个工具;
  而strace最初是为SunOS系统编写的,ltrace最早出现在GNU/DebianLinux中。
  这两个工具现在也已被移植到了大部分Unix系统中,大多数Linux发行版都自带了strace和ltrace,而FreeBSD也可通过Ports安装它们。
  
  你不仅可以从命令行调试一个新开始的程序,也可以把truss、strace或ltrace绑定到一个已有的PID上来调试一个正在运行的程序。三个调试工具的基本使用方法大体相同,下面仅介绍三者共有,而且是最常用的三个命令行参数:
  
  -f :除了跟踪当前进程外,还跟踪其子进程。
  -o file :将输出信息写到文件file中,而不是显示到标准错误输出(stderr)。
  -p pid :绑定到一个由pid对应的正在运行的进程。此参数常用来调试后台进程。
  
   使用上述三个参数基本上就可以完成大多数调试任务了,下面举几个命令行例子:
  truss -o ls.truss ls -al: 跟踪ls -al的运行,将输出信息写到文件/tmp/ls.truss中。
  strace -f -o vim.strace vim: 跟踪vim及其子进程的运行,将输出信息写到文件vim.strace。
  ltrace -p 234: 跟踪一个pid为234的已经在运行的进程。
  
   三个调试工具的输出结果格式也很相似,以strace为例:
  
  brk(0) = 0x8062aa8
  brk(0x8063000) = 0x8063000
  mmap2(NULL, 4096, PROT_READ, MAP_PRIVATE, 3, 0x92f) = 0x40016000
  
  每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。 truss、strace和ltrace的工作原理大同小异,都是使用ptrace系统调用跟踪调试运行中的进程,详细原理不在本文讨论范围内,有兴趣可以参考它们的源代码。
  举两个实例演示如何利用这三个调试工具诊断软件的"疑难杂症":
  
  案例一:运行clint出现Segment Fault错误
  
  操作系统:FreeBSD-5.2.1-release
  clint是一个C++静态源代码分析工具,通过Ports安装好之后,运行:
  
  # clint foo.cpp
  Segmentation fault (core dumped)
   在Unix系统中遇见"Segmentation Fault"就像在MS Windows中弹出"非法操作"对话框一样令人讨厌。OK,我们用truss给clint"把把脉":
  
  # truss -f -o clint.truss clint
  Segmentation fault (core dumped)
  # tail clint.truss
   739: read(0x6,0x806f000,0x1000) = 4096 (0x1000)
   739: fstat(6,0xbfbfe4d0) = 0 (0x0)
   739: fcntl(0x6,0x3,0x0) = 4 (0x4)
   739: fcntl(0x6,0x4,0x0) = 0 (0x0)
   739: close(6) = 0 (0x0)
   739: stat("/root/.clint/plugins",0xbfbfe680) ERR#2 'No such file or directory'
  SIGNAL 11
  SIGNAL 11
  Process stopped because of: 16
  process exit, rval = 139
  我们用truss跟踪clint的系统调用执行情况,并把结果输出到文件clint.truss,然后用tail查看最后几行。
  注意看clint执行的最后一条系统调用(倒数第五行):stat("/root/.clint/plugins",0xbfbfe680) ERR#2 'No such file or directory',问题就出在这里:clint找不到目录"/root/.clint/plugins",从而引发了段错误。怎样解决?很简单: mkdir -p /root/.clint/plugins,不过这次运行clint还是会"Segmentation Fault"9。继续用truss跟踪,发现clint还需要这个目录"/root/.clint/plugins/python",建好这个目录后 clint终于能够正常运行了。
  
  案例二:vim启动速度明显变慢
  
  操作系统:FreeBSD-5.2.1-release
  vim版本为6.2.154,从命令行运行vim后,要等待近半分钟才能进入编辑界面,而且没有任何错误输出。仔细检查了.vimrc和所有的vim脚本都没有错误配置,在网上也找不到类似问题的解决办法,难不成要hacking source code?没有必要,用truss就能找到问题所在:
  
  # truss -f -D -o vim.truss vim
  
  这里-D参数的作用是:在每行输出前加上相对时间戳,即每执行一条系统调用所耗费的时间。我们只要关注哪些系统调用耗费的时间比较长就可以了,用less仔细查看输出文件vim.truss,很快就找到了疑点:
  
  735: 0.000021511 socket(0x2,0x1,0x0) = 4 (0x4)
  735: 0.000014248 setsockopt(0x4,0x6,0x1,0xbfbfe3c8,0x4) = 0 (0x0)
  735: 0.000013688 setsockopt(0x4,0xffff,0x8,0xbfbfe2ec,0x4) = 0 (0x0)
  735: 0.000203657 connect(0x4,{ AF_INET 10.57.18.27:6000 },16) ERR#61 'Connection refused'
  735: 0.000017042 close(4) = 0 (0x0)
  735: 1.009366553 nanosleep(0xbfbfe468,0xbfbfe460) = 0 (0x0)
  735: 0.000019556 socket(0x2,0x1,0x0) = 4 (0x4)
  735: 0.000013409 setsockopt(0x4,0x6,0x1,0xbfbfe3c8,0x4) = 0 (0x0)
  735: 0.000013130 setsockopt(0x4,0xffff,0x8,0xbfbfe2ec,0x4) = 0 (0x0)
  735: 0.000272102 connect(0x4,{ AF_INET 10.57.18.27:6000 },16) ERR#61 'Connection refused'
  735: 0.000015924 close(4) = 0 (0x0)
  735: 1.009338338 nanosleep(0xbfbfe468,0xbfbfe460) = 0 (0x0)
  
  vim试图连接10.57.18.27这台主机的6000端口(第四行的connect()),连接失败后,睡眠一秒钟继续重试(第6行的 nanosleep())。以上片断循环出现了十几次,每次都要耗费一秒多钟的时间,这就是vim明显变慢的原因。可是,你肯定会纳闷:"vim怎么会无缘无故连接其它计算机的6000端口呢?"。问得好,那么请你回想一下6000是什么服务的端口?没错,就是X Server。看来vim是要把输出定向到一个远程X Server,那么Shell中肯定定义了DISPLAY变量,查看.cshrc,果然有这么一行:setenv DISPLAY ${REMOTEHOST}:0,把它注释掉,再重新登录,问题就解决了。
  
  
  案例三:用调试工具掌握软件的工作原理
  
  操作系统:Red Hat Linux 9.0
  用调试工具实时跟踪软件的运行情况不仅是诊断软件"疑难杂症"的有效的手段,也可帮助我们理清软件的"脉络",即快速掌握软件的运行流程和工作原理,不失为一种学习源代码的辅助方法。下面这个案例展现了如何使用strace通过跟踪别的软件来"触发灵感",从而解决软件开发中的难题的。
  大家都知道,在进程内打开一个文件,都有唯一一个文件描述符(fd:file descriptor)与这个文件对应。而本人在开发一个软件过程中遇到这样一个问题:
  已知一个fd,如何获取这个fd所对应文件的完整路径?不管是Linux、FreeBSD或是其它Unix系统都没有提供这样的API,怎么办呢?我们换个角度思考:Unix下有没有什么软件可以获取进程打开了哪些文件?如果你经验足够丰富,很容易想到lsof,使用它既可以知道进程打开了哪些文件,也可以了解一个文件被哪个进程打开。好,我们用一个小程序来试验一下lsof,看它是如何获取进程打开了哪些文件。lsof: 显示进程打开的文件。
  
  /* testlsof.c */
  #include #include #include #include #include
  int main(void)
  {
   open("/tmp/foo", O_CREAT|O_RDONLY); /* 打开文件/tmp/foo */
   sleep(1200); /* 睡眠1200秒,以便进行后续操作 *
   return 0;
  }
  
  将testlsof放入后台运行,其pid为3125。命令lsof -p 3125查看进程3125打开了哪些文件,我们用strace跟踪lsof的运行,输出结果保存在lsof.strace中:
  
  # gcc testlsof.c -o testlsof
  # ./testlsof &
  [1] 3125
  # strace -o lsof.strace lsof -p 3125
  
  我们以"/tmp/foo"为关键字搜索输出文件lsof.strace,结果只有一条:
  
  
  # grep '/tmp/foo' lsof.strace
  readlink("/proc/3125/fd/3", "/tmp/foo", 4096) = 8
  
  原来lsof巧妙的利用了/proc/nnnn/fd/目录(nnnn为pid):Linux内核会为每一个进程在/proc/建立一个以其pid为名的目录用来保存进程的相关信息,而其子目录fd保存的是该进程打开的所有文件的fd。目标离我们很近了。好,我们到/proc/3125/fd/看个究竟:
  
  # cd /proc/3125/fd/
  # ls -l
  total 0
  lrwx------ 1 root root 64 Nov 5 09:50 0 -> /dev/pts/0
  lrwx------ 1 root root 64 Nov 5 09:50 1 -> /dev/pts/0
  lrwx------ 1 root root 64 Nov 5 09:50 2 -> /dev/pts/0
  lr-x------ 1 root root 64 Nov 5 09:50 3 -> /tmp/foo
  # readlink /proc/3125/fd/3
  /tmp/foo
  
  答案已经很明显了:/proc/nnnn/fd/目录下的每一个fd文件都是符号链接,而此链接就指向被该进程打开的一个文件。我们只要用readlink()系统调用就可以获取某个fd对应的文件了,代码如下:
  
  
  #include #include #include #include #include #include
  int get_pathname_from_fd(int fd, char pathname[], int n)
  {
   char buf[1024];
   pid_t pid;
   bzero(buf, 1024);
   pid = getpid();
   snprintf(buf, 1024, "/proc/%i/fd/%i", pid, fd);
   return readlink(buf, pathname, n);
  }
  int main(void)
  {
   int fd;
   char pathname[4096];
   bzero(pathname, 4096);
   fd = open("/tmp/foo", O_CREAT|O_RDONLY);
   get_pathname_from_fd(fd, pathname, 4096);
   printf("fd=%d; pathname=%sn", fd, pathname);
   return 0;
  }
  
  出于安全方面的考虑,在FreeBSD 5 之后系统默认已经不再自动装载proc文件系统,因此,要想使用truss或strace跟踪程序,你必须手工装载proc文件系统:mount -t procfs proc /proc;或者在/etc/fstab中加上一行:
  
  proc /proc procfs rw 0 0

- 作者: chongsoft 2008年11月8日, 星期六 08:48  回复(0) |  引用(0) 加入博采

楼市该不该救
希望政府能关注实体经济,因为金融危机对实体经济的打击非常大.如果在国外,银行都倒闭了,或者出现巨额亏损,哪有什么能力去贷款? 即使有人想投资,供给非常有限,所以需求就会被极大的抑制.这必然会使资金的使用价格提高,反过来会抑制需求.等经济下行了,投资需求就非常小了.在国内,如何引进外资呢? 楼市股市外资肯定会大量的撤出,因为他们都担心而且也需要资金.所以深圳的楼市先降了,北京好一点点,但是因为国内以往紧缩的货币政策,外资的撤退使得开发商不能回笼资金.只好降价.如果楼市暴跌的话,资产的财富价格就会大规模的缩水,人们的经济预期就会更加悲观.谁会花钱买奢侈品呢? 谈何刺激内需呢? 还有,如果楼市暴跌的话, 开发商破产, 坏账会增大, 优质个人客户都成了次级客户, 那时资不抵债, 引发新的一轮的金融危机.对人们的心理预期也是个巨大的打击。谈GDP的增长, 意义不大. 我现在怀疑房地产行业的统计数据,很多人说影响不大, 看和谁比. 和老美比是不大,但是对国内来说也是导致经济下行的一个重要因素.因为房地产曾经是个暴利的行业,如今都关门破产了,对GDP只有负的贡献了,对国家财政收入也是个打击.所以应该救一救.

- 作者: chongsoft 2008年10月18日, 星期六 18:49  回复(0) |  引用(0) 加入博采

中国货币供给理论 M0-M2

M0、M1、M2是货币供应量的范畴。人们一般根据流动性的大小,将货币供应量划分不同的层次加以测量、分析和调控。实践中,各国对M。、M1、M2的定义不尽相同,但都是根据流动性的大小来划分的,M。的流动性最强,M1次之,M2的流动性最差。
我国现阶段也是将货币供应量划分为三个层次,其含义分别是:
M0:流通中现金,即在银行体系以外流通的现金;
M1:狭义货币供应量,即M。+企事业单位活期存款;
M2:广义货币供应量,即M1+企事业单位定期存款+居民储蓄存款。
在这三个层次中,M0与消费变动密切相关,是最活跃的货币;
M1反映居民和企业资金松紧变化,是经济周期波动的先行指标,流动性仅次于M0;
M2流动性偏弱,但反映的是社会总需求的变化和未来通货膨胀的压力状况,通常所说的货币供应量,主要指M2。

看一个例子

中国人民银行昨天公布的数据显示,2008年9月末,广义货币供应量(M2)余额为45.29万亿元,同比增长15.29%,比上月末低0.71个百分点。这是自今年6月份开始M2数据连续第四个月出现放缓,M2增速也创下2005年6月份以来的最低值。狭义货币供应量(M1)的降幅更大。截至9月末,M1余额为15.57万亿元,同比增长9.43%,比上月末低2.05个百分点。

  市场人士认为,上述数据显示经济存在进一步下滑的压力,下一步宏观政策预计将继续放松,除了继续降息降准备金率外,财税部门可能出台减税政策。

  ⊙本报记者 但有为

  兴业银行资金运营中心首席经济学家鲁政委认为,尽管从7月底开始,央行从紧力度在减弱,但M2增速还是在下降,这说明国内经济活跃程度在下降。

  中证券首席宏观分析师诸建芳则认为,M2偏低,但是更加突出的是M1非常低。这说明经济活动正在收缩。原因可能主要有两个:一是担心经济下滑导致银行惜贷;二是企业对实体经济比较悲观,信贷需求放缓。

  而根据国家统计局10日公布的数据,三季度全国企业家信心指数大幅回落,其中房地产业的企业家信心指数跌幅最深。

  虽然9月当月人民币贷款增加3745亿元,同比多增910亿元。但诸建芳认为,去年央行对信贷实行严格的规模控制,年底控制的力度甚至更大,导致去年同期贷款基数很小,所以今年9月份新增贷款达到3745亿元也并不算多。

  谈及下一步的宏观政策走向,市场人士普遍认为,政策将进一步放松。

  近日召开的央行货币政策委员会会议指出,当前要根据国内外形势变化采取灵活审慎的宏观政策,适时适度把握调控的方向、力度和节奏,进一步加强货币政策与财政、产业、外贸、金融监管等政策的协调配合,加快经济结构调整和增长方式转变,着力扩大内需,促进国际收支趋于基本平衡。

  鲁政委认为,要防止经济继续下行,货币政策应进一步放松。他预计今年央行至少会再降一次息,存款准备金率会下调2-3次。此外,央行将会解冻一部分定向央票。他同时认为,由于目前对通胀压力不必担心,上述政策有实施的空间。

  根据目前多家机构发布的预测数据,市场普遍认为9月份CPI仍会进一步下降,同比增幅在4.5%-4.9%之间。

  光大证券首席宏观分析师潘向东认为,由于通胀压力在年底会继续减小,在货币政策方面,首先应该实施的是降息。存款准备金率也应该下调。在财政政策方面,他认为应该减税。

  本月8日,央行决定下调一年期人民币存贷款基准利率各0.27个百分点,同时10月15日起下调存款类金融机构人民币存款准备金率0.5个百分点。此前,央行已于9月15日下调人民币贷款基准利率,及部分中小金融机构的人民币存款准备金率。

- 作者: chongsoft 2008年10月15日, 星期三 10:58  回复(0) |  引用(0) 加入博采

学而时习之 -- 硬盘那点事儿 (转载)

110栏的比赛中,一直以来都是飞人鲍威尔的天下。鲍威尔的100的最好成绩达到了974,这也让他在110栏比赛中占尽先机。自从刘翔横空出世,鲍威尔就再也无缘110栏的金牌了,金牌都被刘翔收入囊中。可刘翔的100最好成绩才103,但为啥又能在110栏中战胜鲍威尔呢?原来,110栏拼的不仅仅是奔跑的速度,还有跨栏的技巧。

 同样,在硬盘的读写效率方面比拼的不仅仅是看谁转的快,还有寻道时间,还有内置缓存。


为了理解硬盘的工作原理,我们先来打开硬盘看看:

 hard disk1

那光洁如银镜的圆盘叫磁碟,它的表面就是存放数据的地方。那细如米粒的尖端上就装着读写数据的部件,叫磁头。一个磁碟的两面都可以记录数据,因此磁碟的正反面都会有一个读写磁头。而一个硬盘内可以安装多个磁碟,也就有多个磁头,磁碟和磁头都是好几层的。看看下面的解剖图就明白了:

 hard disk2

我们再来看看硬盘是如何读写数据的。

在还未给硬盘通电前,磁头总体靠在一个安全的飞机场。这个飞机场要么在磁碟的最内侧,要么在磁碟的最外侧。飞机场部分的表面是不存放数据的,仅用于磁头的起飞和着陆。

磁碟的大部分区域是用来记录数据的,这些区域由一圈一圈的磁道组成,每个磁道又被划分成若干扇区。而磁碟的层叠看起来就像一个圆柱,那些磁道就形成以一层包裹一层的柱面结构,就像大树的年轮一样。而硬盘管理存储空间的方式都是先按柱面来存,再把磁道换到下一个柱面,并非按一个个磁碟来走的。

早期的硬盘采用CHS坐标体系规划硬盘存储空间,内侧磁道和外侧磁道的扇区数都相同。即使外侧磁道比内侧磁道要长得多,但容量只能与内侧磁道相同。硬盘总存储容量实际受内侧磁道的记录密度所限制,这显然是一种浪费。当磁道密度达到第一次极限时,人类发明了LBA的坐标映射,允许外侧磁道划分出更多的扇区,大幅度提升了硬盘容量。

同样,当LBA模式的硬盘容量又抵达距离的极限时,人类又发明了垂直记录技术,这又将大幅提升硬盘容量。所谓垂直记录技术就是把本来在磁碟表面平放的磁记录单元,改成竖着放。就像一个接一个平躺着的人会占据很长的距离,但这些人站起来就可以靠拢,从而缩小占据的距离。因此,垂直记录技术就可以在同样长度的磁道上放置更多的磁记录单元,从而提升硬盘容量。

当硬盘加电时,磁碟便开始加速转动。磁头是按精巧的好空气动力学原理设计出来的战斗机,当它在磁头的表面滑行到一定的速度时就会飞起来,从而浮在磁碟的表面。相信没有哪种飞机能像硬盘磁头那样,以零点几微米的超低空状态保持高速飞行。这样的超低空可以保证磁头能有效地分辨细小的磁记录单元的信号变化。

硬盘最怕什么?震动!在硬盘高速运转时,震动可能导致超低空飞行的磁头碰到磁碟表面,从而划伤磁碟,产生坏道。因此,硬盘运行时绝对禁止震动。

硬盘的转速和磁碟记录密度与磁头读写的速度都成近似的正比关系。转速越快,磁头读取数据就越快;而记录密度越高,相同时间下磁头读取的数据就越多。因此,人们总喜欢购买转速高的硬盘或容量大的硬盘。但除了转速和记录密度之外,其他方面的因素对硬盘的读写效率更加总要。

当硬盘接到读写命令时,磁头首先需要移动到要读写数据的磁道上,这个过程称为寻道。寻道需要时间的,这取决于磁头当前磁道与目标磁道的距离。如果距离远,移动磁头的时间就长,寻道就慢;如果距离很近,移动磁头的时间就短,寻道就快。平均寻道时间是影响硬盘读写效率的最重要指标。

磁头找到需要读取的磁道后,要读取的数据块可能还没有转过来,这也需要等待。平均等待读取数据块的时间称为平均潜伏时间,一般是磁碟旋转周期的一半。由于现在的硬盘转速都很快,因此这个等待时间比起平均寻道时间来说要小得多。

整体读写效率是由硬盘的平均访问时间来衡量的,它是指从硬盘收到访问命令,到完成所有操作并返回最终结果所花费的时间。平均访问时间基本就等于平均寻道时间与平均潜伏时间之和。因为这两个时间指标都与硬盘的机械结构有关,其他电信号切换和指令时间比起机械上所花的时间来说基本可以忽略不计。

由此可见,影响硬盘读写效率的主要是平均寻道时间。如果一个文件在硬盘中扇区太分散,东一块西一段的,那么读取这个文件时,磁头就会嚓嚓嚓地乱跳。由于把大多数的时间都花在寻找不连续的磁道上,读取文件的效率也就大大降低了。同样,当我们的硬盘由于文件的平凡更迭,会自然形成许多不连续的文件和磁盘碎片。这时,系统的运行效率也就大大降低,根源也是寻道时间太多。

为了对硬盘运行原理有一个感性的认识,我们来看看一段录像:(Sorry, Blog is not supportted)



录像中,Copy & Paste 这种交替跨磁道的读写导致了磁头的剧烈振动,这不仅仅会导致读写效率的下降,还会制造噪音,并加速硬盘的损耗。而类似Format那种连续操作时,速度非常快,磁头也运行得很平稳和安静。

现代的硬盘都有内置缓存,这些缓存可提供一项重要的功能:预取。预读就是在硬盘读取完指定扇 区的数据之后、接到系统的下一条指令之前,磁头接着读取相邻的若干扇区的数据并存入缓存中。如果系统接下来所需的数据正好就是相邻扇区的数据,那么便可以 直接从缓存中读取而不用磁头再寻址,从而极大提高数据访问速度。

如果,我们定期对硬盘进行碎片整理,尽量保持文件的连续和有序,将对硬盘带来巨大好处。硬盘在读取这些连续数据时,磁头很少剧烈震动,磁盘运行很安静。因为连续有序的数据文件可以极大减少寻道时间,既能提高读取数据的速度,又可减少硬盘损耗,延长使用寿命。

此外,磁盘内置缓存的预读能力正好符合连续数据的读取。从寻道时间和缓存命中率两项指标来看,连续有序的数据可以极大提升电脑系统的运行效率。在实际生活中,一般的文件都会占用成千上万个扇区的空间,根据缓存预取的原理,如果硬盘中的文件完全没有磁盘碎片的话,那么预取的命中率就可以达到几乎100%,但因为有磁盘碎片,通常预取的命中率只在50%左右。

同样,在数据库应用系统中,我们也应该尽量保持数据存储的连续和有序。尽管数据库会有自己存放数据的格式,操作系统也存在自己的缓存机制。但这些上层的顺序结构和缓存机制,也会多多少少体现在硬盘的磁道和扇区的相邻性上。因为这些结构和机制也是基于硬盘的顺序结构和缓存机制来设计的。因此,在数据库的数据存储中尽量保证其物理连续性,将有助于提高数据库的查询效率。这也是为什么聚集索引要比其他索引快的原因,也是为什么精心设计的主键可以让索引的B树在硬盘上更连续的原因。

如果您读过薛定谔的那本著名的《生命是什么》,您将会明白生命的物理学意义就是让宇宙变得有序。让世界更有序是生命的天性,有智慧的程序员会让这种天性融入自己的程序中的。

预祝刘翔在2008奥运会上跑得更加有序!

原创:李战(leadzen) 2008-6-25
原文:http://www.cnblogs.com/leadzen/archive/2008/06/25/1229413.html

- 作者: chongsoft 2008年09月15日, 星期一 12:44  回复(0) |  引用(0) 加入博采

关于手机时钟的文章

手机中的时钟大致分为逻辑电路主时钟和实时时钟两大类。逻辑电路的主时钟通常有13M26M、和19.5M等;实时时钟一般为32.768KHz。无论是逻辑电路的主时钟还是实时时钟,均是手机正常工作的必要条件,由于手机各厂家设计思路和电路结构不同,主时钟和实时时钟电路若不正常时,反映出的故障现象也不尽相同。

 

一、时钟频率的产生

1 逻辑电路主时钟的产生
大多数GSM手机的主时钟是13MCDMA19.68M,小灵通19.2M);摩托罗拉手机多采用26M,三星手机A系列手机多采用19.5M,经分频后获得13M供逻辑电路。13M作为逻辑电路的主时钟(好比人按照北京时间安排作息),逻辑电路按时序进行有规律的工作。
手机中13M的频率是否准确,决定于AFC电压,AFC电压的产生,是基站根据手机传送的频率信息与网络系统高精度、高稳定的频率鉴相后,把信息传给手机,由CPU处理后产生直流电压,去控制13M的振荡频率,使手机中13M与基站保持严格同步。
13M
产生电路分为纯石英晶振和13M组件两种。石英晶体是与其他电路共同组成振荡产生13M13M组件电路只要加电即可产生13M频率。
在手机电路中,无论纯石英晶体或13M组件电路,均需要电源正常工作输出供电,13M电路才能产生13M输出。

 

2 实时时钟频率的产生

手机中的实时时钟频率基本上都是32.768KHz,是由32.768KHz晶体配合其他电路产生。为了维持手机中时间的连续性, 32.768KHz不能间断工作,关机或去下电池后,由备用电池供电工作(有的手机去下电池一段时间后,开机需再调整时间,是机内没有备用电池或备用电池需要更换)。

二、时钟频率的作用
1
、逻辑电路主时钟的作用
13M
作为逻辑电路的主时钟,是逻辑电路工作的必要条件。开机时需要有足够的幅度(9—15M范围内均可开机)。
开机后,13M作为射频电路的基准频率时钟,完成射频系统共用收发本振频率合成、PLL锁相以及倍频作为基准副载波用于I/Q调制解调。因此,信号对13M的频率要求精度较高(应在12.9999M13.0000M之间,±误差不超过150Hz),只有13M基准频率精确,才能保证收发本振的频率准确,使手机与基站保持正常的通讯,完成基本的收发功能。

2、实时时钟电路的作用

32.768KHz实时时钟的作用一般有两个,一是保持手机中时间的准确性,二是在待机状态下,作为逻辑电路的主时钟(目的是为了节电,待机时13M间隔工作的周期延长,基本处于休眠,逻辑电路主要由32.768KHz作为主时钟)。

由于各厂家设计思路不同,32.768KHz的具体作用也有所不同,如摩托罗拉手机中32.768KHz损坏,直接影响开机;诺基亚、三星、松下、西门子等手机中32.768KHz不正常影响开机和信号。

三、时钟电路的故障

1
、逻辑电路主时钟故障
众所周知,13M出现停振或振荡幅度过小,逻辑电路不工作造成不开机,大部分手机13M不正常的故障现象是开机电流很小(一般在10mA左右)。

逻辑电路正常工作的经典电流是50mA左右,当开机电流小于50mA时,重点检查逻辑电路正常工作的所必要条件电路,如电源、13M、复位、软件电路等。若开机后13M停振,会造成手机自动关机。

如果13M出现频偏较小,使收发本振和混频后的中频以及调制解调出的I/Q基带信号均产生偏离,形成信号时有时无;若13M偏离较大,造成无信号;如13M偏离太远,还会出现死机、定屏、开机困难、自动关机等故障。

检修13M是否正常,可用示波器或频率计测量,正常时示波器可测量到密集正弦波形成的亮带,调低示波器的频率可见到规律的正弦波;频率计可直接读到13M的具体频率数值(若停振什么也测不到)。一般情况下,13M停振或频偏,只要供电正常,多为晶振问题,更换即可。

2、实时时钟故障

32.768KHz不正常时,由于机型不同反映出的故障现象也不同,开机电流比13M主时钟不正常稍大(一般在20mA左右)。
如摩托罗拉手机中的32.768KHz与电源块构成振荡,是作为逻辑电路工作的一个前提条件,如果32.768KHz不工作,逻辑电路就不能工作出现不开机;诺基亚手机中的32.768KHz作为逻辑电路CPU数据传输的时钟,损坏后不开机,拆下后可以开机但无时间显示,若性能不良会引起信号时有时无(信号条逐渐消失);松下、西门子部分手机32.768KHz损坏可以开机,但无时间显示或时间不准;三星部分手机32.768KHz损坏不开机,拆下可以开机但无时间显示或开机后灯灭关机;还有部分手机如夏新A832.768KHz作为CPU的启动时钟,若损坏同样造成不开机。

测量32.768KHz的方法与13M相同,也是用示波器和频率计测亮带和读数,如不起振,通常是备用电池短路或晶体损坏引起,更换即可。

- 作者: chongsoft 2008年09月10日, 星期三 11:55  回复(0) |  引用(0) 加入博采

Device driver bus 三者关系 (非常恰当 转载)

  对Linux驱动理解的深化,我们不得不去探究一下bus,device,driver之间的关系,看看表面背后那只无形的手,今天不想多谈bus,我今天重点谈一下device与driver的关系,因为他们俩的关系很暧昧。很值得探究。为什么这样说呢?如果把device比作人的身躯,那么driver就是人的灵魂,如果把device比作男人,那么driver就是女人,如果身躯离开了灵魂,那它只是一堆烂肉,如果男人离开了女人,那他也将一事无成,因为女人是男人背后最大的驱动力(driver),如果device是西门庆,那driver就是潘金莲。
  那device与driver是如何产生暧昧关系的?这个问题才是我们最关心的,要解答这个问题,那就要问bus了,因为她是媒婆,是牵线人,bus把 device介绍给了driver,device与driver眉来眼去,最终确定了这种暧昧关系,bus是如何牵的线呢?这个今天我们就不去多加追究,对媒婆没有太多兴趣,据可靠情报透露,bus是通过家传的配对大法(match)使device与driver联系了起来,当然我们也会想到一句话:红颜祸水。的确不错,driver抛(probe)的媚眼也是关键。
  好了,说了很多,是不是有点晕,最后整理一下device与driver产生暧昧关系的过程,首先由媒婆牵线(match),后来driver抛个媚眼 (probe),device便拜倒在了driver的石榴裙下,彼此产生了暧昧关系,最后谁也离不开了谁,彼此缺少了对方,就像鱼儿离开了水!

- 作者: chongsoft 2008年08月7日, 星期四 22:54  回复(0) |  引用(0) 加入博采

BCD编码方式 (转)

BCD编码方式

    在数字系统中,各种数据要转换为二进制代码才能进行处理,而人们习惯于使用十进制数,所以在数字系统的输入输出中仍采用十进制数,这样就产生了用四位二进制数表示一位十进制数的方法,这种用于表示十进制数的二进制代码称为二-十进制代码(Binary Coded Decimal),简称为BCD码。它具有二进制数的形式以满足数字系统的要求,又具有十进制的特点(只有十种有效状态)。在某些情况下,计算机也可以对这种形式的数直接进行运算。
    4位二进制码共有16种组合,可从中任取10种组合来表示0~9这10个数。根据不同的选取方法,可以编制出很多种BCD码,如8421码,5421码,2421码,5211码和余3码。下表列出了这几种BCD码,其中的8421 BCD码最为常用。
    常见的BCD码表示有以下几种。
    8421BCD编码
    这是一种使用最广的BCD码,是一种有权码,其各位的权分别是(从最有效高位开始到最低有效位)8,4,2,1。
    例 写出十进数563.97D对应的8421BCD码。
    563.97D=0101 0110 0011 . 1001 01118421BCD
    例 写出8421BCD码1101001.010118421BCD对应的十进制数。
    1101001.010118421BCD=0110 1001 . 0101 10008421BCD=69.58D
    在使用8421BCD码时一定要注意其有效的编码仅十个,即:0000~1001。四位二进制数的其余六个编码1010,1011,1100,1101,1110,1111不是有效编码。
    2421BCD编码
    2421BCD码也是一种有权码,其从高位到低位的权分别为2,4,2,1,其也可以用四位二进制数来表示一位十进制数。其编码规则如下表。
    余3码
    余3码也是一种BCD码,但它是无权码,但由于每一个码对应的8421BCD码之间相差3,故称为余3码,其一般使用较少,故正须作一般性了解,具体的编码如下表。

表 常用BCD编码表

8421码

5421码

2421码

5211码

3码

0

0000

0000

0000

0000

0000

1

0001

0001

0001

0001

0100

2

0010

0010

0010

0100

0101

3

0011

0011

0011

0101

0110

4

0100

0100

0100

0111

0111

5

0101

1000

0101

1000

1000

6

0110

1001

0110

1001

1001

7

0111

1010

0111

1100

1010

8

1000

1011

1110

1101

1011

9

1001

1100

1111

1111

1100

8421

5421

2421

5211

 

非压缩式和压缩式BCD码
    BCD又分为两种,非压缩式和压缩式两种。
    前面这种81存成 “08,01” 是非压缩式,而压缩式会存成 “81h” (直接以十六进制储存)。非压缩的BCD码只有低四位有效,而压缩的BCD码则将高四位也用上了,就是说一个字节有两个BCD码。BCD是用0和1表示十进制,如0000表示0,0001表示1,0010表示2。而压缩的BCD是用00表示0,01表示1,10表示2,110表示3等。
    例:
    1234表示成非压缩的BCD码是00000001000000100000001100000100,也就是0x01020304;而压缩BCD码则表示成0001001000110100,也就是0x1234。
    但压缩的BCD并不固定,可看情况而定,所要的就是用最少的位数表示尽可能多的数。

- 作者: chongsoft 2008年08月5日, 星期二 17:00  回复(0) |  引用(0) 加入博采

二叉树递归与非递归遍历

今天把二叉树写了写,总是有讨厌的事情让你写什么非递归算法,对于非递归类算法问题我已经在前面写过了

一篇文章,借助树分析和栈解决递归问题的非递归实现,今天把二叉树的前序遍历算法也写了出来,后序遍历

要做一些改动(压栈时还需要做标记)。有兴趣读者自己实现吧。

读者可以和前面的文章《通用递归问题的非递归算法》结合起来看。

#include <stdio.h>
#include <stdlib.h>

/**
*  Author: chongsoft
*
*/

// pre-order travelling of a bi-tree

typedef struct bi_tree
{
   int val;
   struct bi_tree *left;
   struct bi_tree *right;
}bi_tree_t;

bi_tree_t *tree;

int val_lst[] = {10,7,9,5,8,1,4,3,0,2,6};


bi_tree_t *gen_bi_tree_node(int val)
{
    bi_tree_t *new_node = (bi_tree_t *)malloc(sizeof(bi_tree_t));
 if(!new_node)
 {
  return NULL; 
 }
 
 new_node->val = val;
 new_node->left = NULL;
 new_node->right = NULL;
 return new_node;
}


// We set up node according to the given list
bi_tree_t *setup_bi_tree(int list[],int len)
{
 int i;
 bi_tree_t *new_tree = NULL;

 printf("list len is %d\n",len);
 

 for(i = 0; i < len; i++)
 {
       bi_tree_t **tmp = &new_tree;
    bi_tree_t *node;
    // create a new node
    node = gen_bi_tree_node(list[i]);

    if(!node)
    {
     fprintf(stderr,"malloc new node error!\n");
     break;
    }

       // put it in the right place
    while(*tmp)
    {
        if(list[i] <= (*tmp)->val)
     {
        tmp = &(*tmp)->left;     
     }
     else
     {
        tmp = &(*tmp)->right;   
     }     
    }
          
    *tmp = node;    
 }
 
 return new_tree;
}

void free_bi_tree(bi_tree_t *sub_tree)
{
    if(!sub_tree)
 {
     return;
 }

 free_bi_tree(sub_tree->left);
 free_bi_tree(sub_tree->right);
    printf("recursive free node %d\n",sub_tree->val);
    free(sub_tree);
}


// Let us see how to recursively travel the tree
void method_recursive(bi_tree_t *sub_tree)
{
    if(!sub_tree)
 {
     return;
 }

 method_recursive(sub_tree->left);
 printf("recursive - node: %d is printed\n",sub_tree->val);
    method_recursive(sub_tree->right);
}

/**
// Also let us see how we can travel it not in recursive way
// Thus We need an extra stack to store the tree node :)
*/
typedef struct tree_stack
{
   int sp;
   bi_tree_t **data;
}tree_stack_t;

tree_stack_t *set_up_tree_stack(int len)
{
 tree_stack_t *new_stack = (tree_stack_t *)malloc(sizeof(tree_stack_t));
 if(!new_stack)
 {
     return NULL;
 }

 new_stack->sp = -1;
 new_stack->data = (bi_tree_t **)malloc(len * sizeof(bi_tree_t *));

 if(!new_stack->data)
 {
     free(new_stack);
  return NULL;
 }
 
 return new_stack;
}


void free_stack(tree_stack_t *stack)
{
    if(stack)
 {
     if(stack->data)
  {
      free(stack->data);
  }
       
  free(stack);
 }
 return;
}


void method_normal(bi_tree_t *sub_tree)
{
    bi_tree_t *tmp;
    tree_stack_t *stack;

 if(!sub_tree)
 {
  fprintf(stdout,"tree is empty!\n");
     return;
 }

 stack = set_up_tree_stack(sizeof(val_lst)/sizeof(int));
    if(!stack)
 {
  fprintf(stderr,"stack is empty!\n");
     return;
 }

 // stack got, then we focus on how to travel tree
    tmp = sub_tree;
   
 do
 {
  if(!tmp)
  {
   // pop stack and print
   bi_tree_t *pop_node = stack->data[stack->sp--];
   fprintf(stdout,"normal pop_node val is %d\n",pop_node->val);
   tmp = pop_node->right;
  } 
  else
  {    
   // push stack
   stack->data[++stack->sp] = tmp;
   tmp = tmp->left;
  }
 }while(stack->sp >= 0);

    free_stack(stack);
}

void normal_free_bi_tree(bi_tree_t *sub_tree)
{
    bi_tree_t *tmp;
    tree_stack_t *stack;

 if(!sub_tree)
 {
  fprintf(stdout,"tree is empty!\n");
     return;
 }

 stack = set_up_tree_stack(sizeof(val_lst)/sizeof(int));
    if(!stack)
 {
  fprintf(stderr,"stack is empty!\n");
     return;
 }
 

 // stack got, then we focus on how to travel tree
    tmp = sub_tree;
 
    do
 {
  if(!tmp)
  {
   // pop stack and print
   bi_tree_t *pop_node = stack->data[stack->sp--];
   fprintf(stdout,"normal free pop_node val is %d\n",pop_node->val);
   tmp = pop_node->right;
   free(pop_node);
        }
  else
  {
   // push stack
   stack->data[++stack->sp] = tmp;
   tmp = tmp->left;
  
  }
 
 
 }while(stack->sp >= 0);

    free_stack(stack);
}


int main(void)
{
 tree = setup_bi_tree(val_lst,sizeof(val_lst)/sizeof(int));
    printf("tree is %p\n",tree);
    method_recursive(tree);
 method_normal(tree);
    free_bi_tree(tree);
    //normal_free_bi_tree(tree);
    return 0;
}

运行结果:

list len is 11
tree is 0x17f45010
recursive - node: 0 is printed
recursive - node: 1 is printed
recursive - node: 2 is printed
recursive - node: 3 is printed
recursive - node: 4 is printed
recursive - node: 5 is printedbr />recursive - node: 6 is printed
recursive - node: 7 is printed
recursive - node: 8 is printed
recursive - node: 9 is printed
recursive - node: 10 is printed
normal pop_node val is 0
normal pop_node val is 1
normal pop_node val is 2
normal pop_node val is 3
normal pop_node val is 4
normal pop_node val is 5
normal pop_node val is 6
normal pop_node val is 7
normal pop_node val is 8
normal pop_node val is 9
normal pop_node val is 10
recursive free node 0
recursive free node 2
recursive free node 3
recursive free node 4
recursive free node 1
recursive free node 6
recursive free node 5
recursive free node 8
recursive free node 9
recursive free node 7
recursive free node 10

- 作者: chongsoft 2008年07月15日, 星期二 18:29  回复(0) |  引用(0) 加入博采

GRUB入门教程 (转)
GRUB入门教程

 


==============================
目录:
1. 教程提示
2. GRUB 基础
3. 安装 GRUB
4. 使用 GRUB
5. GRUB 内幕
6. 总结
==============================

第一章 教程提示

我应该学习这门教程吗?

本教程向您显示如何安装和使用 GRUB (Grand Unified Boot Loader)。就像 LILO 一样,GRUB 允许引导 Linux 系统,它负责装入和引导内核。但与 LILO 不同,GRUB 的功能非常多,更易于使用,更可靠和灵活,而且非常小巧。

如果已经有些熟悉 LILO,并了解了磁盘分区的基本知识,那么您就掌握了学习本教程应具备的预备知识。通过学习本教程和安装 GRUB 之后,您将改进 Linux 系统的可靠性和可用性。

如果只是想尝试 GRUB,可以学习本教程的前半部分,并制作 GRUB 引导盘,然后练习使用它来引导系统。这样做以后,您将学会在紧急情况下如何使用 GRUB 来引导系统。

但是,如果想更深入地体验 GRUB,可以学完整个教程,它将为您演示如何将 GRUB 设置成缺省引导装入器。

第二章 GRUB 基础

什么是 GRUB?

GRUB 是引导装入器 -- 它负责装入内核并引导 Linux 系统。GRUB 还可以引导其它操作系统,如 FreeBSD、NetBSD、OpenBSD、GNU HURD 和 DOS,以及 Windows 95、98、NT 和 2000。尽管引导操作系统看上去是件平凡且琐碎的任务,但它实际上很重要。如果引导装入器不能很好地完成工作或者不具有弹性,那么就可能锁住系统,而无法引导计算机。另外,好的引导装入器可以给您灵活性,让您可以在计算机上安装多个操作系统,而不必处理不必要的麻烦。

GRUB 很棒

幸好,GRUB 是一个很棒的引导装入器。它有许多功能,可以使引导过程变得非常可靠。例如,它可以直接从 FAT、minix、FFS、ext2 或 ReiserFS 分区读取 Linux 内核。这就意味着无论怎样它总能找到内核。另外,GRUB 有一个特殊的交互式控制台方式,可以让您手工装入内核并选择引导分区。这个功能是无价的:假设 GRUB 菜单配置不正确,但仍可以引导系统。哦,对了 -- GRUB 还有一个彩色引导菜单。我们只是刚开始。

为什么使用 GRUB?

您也许会奇怪,为什么全世界都需要 GRUB -- 毕竟,Linux 世界在很长一段时间里一直使用 LILO 引导装入器,而且它可以让上百万的 Linux 用户引导系统。是的,的确是这样,LILO 很有效。但是,LILO 的维修率很高,而且很不灵活。与其花很多时间来描述 GRUB 的优点,还不如演示如何创建自己的 GRUB 引导盘以及如何使用它来引导系统。然后,我将说明 GRUB 的一些很“酷”的技术细节,并指导您完成将 GRUB 安装到 MBR(主引导记录)的过程,以使它成为缺省引导装入器。

如果您有点胆小,不必害怕。可以学习本教程的前半部分,创建 GRUB 引导盘,尝试使用 GRUB 而不必弄乱现有的引导装入器。或者,可以用其安全的“驻留”方式来熟悉 GRUB。那么,让我们立即开始吧。

第三章 安装 GRUB

下载 GRUB

要开始探究 GRUB 的精妙之处,首先需要下载、编译和安装它。但不要害怕 -- 根本不会修改您的引导记录 -- 我们只是要编译和安装 GRUB,就像其它程序一样,在此过程中我们可以创建 GRUB 引导盘。请不要担心;在修改引导过程之前,我会告诉您。

现在开始。访问
并下载可以找到的最新版本的 GRUB tar 压缩包。我编写本教程时,最新的 tar 压缩包是 grub-0.5.96.1.tar.gz。下载了最新版本后,就可以安装了。

安装步骤

这里是从 tar 压缩包安装 GRUB 所需输入的命令。我将在 /tmp 中编译源文件,并将所有部分都安装到硬盘的 /usr 目录下。从 root 用户,输入以下命令:

# cd /tmp
# tar xzvf /path/to/archive/here/grub-0.5.96.1.tar.gz
# cd grub-0.5.96.1
# ./configure --prefix=/usr
# make
# make install

现在已经安装了 GRUB,准备开始使用它。

制作引导盘

要制作引导盘,需执行一些简单的步骤。首先,在新的软盘上创建 ext2 文件系统。然后,将其安装,并将一些 GRUB 文件复制到该文件系统,最后运行 "grub" 程序,它将负责设置软盘的引导扇区。准备好了吗?

制作引导盘,第二部分

好,将一张空盘插入 1.44MB 软驱,输入:

# mke2fs /dev/fd0
创建了 ext2 文件系统后,需要安装该文件系统:

# mount /dev/fd0 /mnt/floppy
现在,需要创建一些目录,并将一些关键文件(原先安装 GRUB 时已安装了这些文件)复制到软盘:

# mkdir /mnt/floppy/boot
# mkdir /mnt/floppy/boot/grub
# cp /usr/share/grub/i386-pc/stage1 /mnt/floppy/boot/grub
# cp /usr/share/grub/i386-pc/stage2 /mnt/floppy/boot/grub

只需要再有一个步骤,就能得到可用的引导盘。

制作引导盘,第三部分

解压、编译和安装 GRUB 源 tar压缩包时,会将程序 grub 放到 /usr/sbin 中。该程序非常有趣并值得注意,因为它实际上是 GRUB 引导装入器的半功能性版本。是的,尽管 Linux 已经启动并正在运行,您仍可以运行 GRUB 并执行某些任务,而且其界面与使用 GRUB 引导盘或将 GRUB 安装到硬盘 MBR 时看到的界面完全相同。

这是有趣的设计策略,现在该使用驻留版本的 GRUB 来设置引导盘的引导扇区了。从 root 用户,输入 "grub"。GRUB 控制台将启动,显示如下:

GRUB version 0.5.96.1 (640K lower / 3072K upper memory)

[ Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists the possible
completions of a device/filename. ]

grub>

欢迎使用 GRUB 控制台。现在,研究命令。

制作引导盘,第四部分

在 grub> 提示符处,输入:

grub> root (fd0)
grub> setup (fd0)
grub> quit

现在,引导盘完成了。在继续下一步骤之前,在看一下刚才输入的命令。第一个 "root" 命令告诉 GRUB 到哪里查找辅助文件 stage1 和 stage2。缺省情况下,GRUB 会在指定的分区或磁盘上的 /boot/grub 目录中进行查找。在安装引导盘时,也就是几分钟以前,我们已将这些文件复制到正确的位置。接着,输入了 setup 命令,它告诉 GRUB 将引导装入器安装到软盘的引导记录上;我们将在以后详细讨论这一过程。然后退出。现在,已经制作好引导盘,可以开始使用 GRUB 了。

第四章 使用 GRUB

准备

使用 GRUB 引导系统之前,需要知道一些信息。首先,应知道哪个分区保存了 Linux 内核,以及 root 文件系统的分区名称。然后,应查看现有 LILO 配置来寻找需要传递给内核的变量,如 "mem=128M"一旦获取了这些信息,就可以开始了。

启动 GRUB

要启动 GRUB,需要关闭系统并退出引导盘。如果由于某些原因而不能立即关闭系统(比如上班时在部门的服务器上测试 GRUB),那么只要在提示中输入 "grub" 并继续操作。所有程序的运行情况都不会改变,只是您不能执行引导(因为 Linux 正在运行)。

首次接触

装入引导盘时,在屏幕顶部将出现一条消息,告诉您正在装入第一阶段和第二阶段。几秒后,将会出现一个熟悉的屏幕,显示如下:

GRUB version 0.5.96.1 (640K lower / 3072K upper memory)

[ Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists the possible
completions of a device/filename. ]

grub>

可以看到,这些内容与在 Linux 中以驻留方式运行 GRUB 时出现的消息完全相同 -- 只不过现在我们是使用 GRUB 来引导 Linux。

"root"

在 Linux 中,当谈到 "root" 文件系统时,通常是指主 Linux 分区。但是,GRUB 有它自己的 root 分区定义。GRUB 的 root 分区是保存 Linux 内核的分区。这可能是您的正式 root 文件系统,也可能不是。例如,在 Gentoo Linux 中,有一个单独的小分区专用于保存 Linux 内核与引导信息。大多数情况下,我们不安装这个分区,这样在系统意外崩溃或重新引导时,就不会把它弄乱。

"root",第二部分

这些,我们讨论的是 GRUB,需要指定 GRUB 的 root 分区。进入 root 分区时,GRUB 将把这个分区安装成只读型,这样就可以从该分区中装入 Linux 内核。GRUB 的一个很“酷”的功能是它可以读取本机的 FAT、FFS、minix、ext2 和 ReiserFS 分区,我们很快就会讨论这个功能。但现在,让我们输入 root 分区。在提示中输入 root,但不要按 Enter 键:

grub> root (

现在,按一次 Tab 键。如果系统中有多个硬盘,GRUB 将显示可能完成的列表,从 "hd0" 开始。如果只有一个硬盘,GRUB 将插入 "hd0,"。如果有多个硬盘,继续进行,在 ("hd2") 中输入名称并在名称后紧跟着输入逗号,但不要按 Enter 键。部分完成的 root 命令看起来如下:

grub> root (hd0,

"root",第三部分

现在,继续操作,再按一次 Tab 键。GRUB 将显示特定硬盘上所有分区的列表,以及它们的文件系统类型。在我的系统中,按 Tab 键时得到以下列表:

grub> root (hd0, (tab)
Possible partitions are:
Partition num: 0, Filesystem type is ext2fs, partition type 0x83
Partition num: 1, Filesystem type unknown, partition type 0x82
Partition num: 2, Filesystem type unknown, partition type 0x7
Partition num: 4, Filesystem type is reiserfs, partition type 0x83
Partition num: 5, Filesystem type is reiserfs, partition type 0x83

如您所见,GRUB 的交互式硬盘和分区名称实现功能非常有条理。这些,只需要好好理解 GRUB 新奇的硬盘和分区命名语法,然后就可以继续操作了。

GRUB 命名约定

到目前为止,您可能会感到一点困惑,因为 GRUB 所使用的硬盘/分区命名约定与 Linux 使用的命名约定不同。在 Linux 中,第一个硬盘的第五个分区称作 "hda5"。而 GRUB 把这个分区称作 "(hd0,4)"。GRUB 对硬盘和分区的编号都是从 0 开始计算。另外,硬盘和分区都用逗号分隔,整个表达式用括号括起。现在,回来看一下 GRUB 提示,可以发现如果要引导 Linux 硬盘 hda5,应输入 "root (hd0,4)"。如果已经明白了 GRUB 硬盘/分区命名,您也许要调整当前 root 命令行,以使它指向保存 Linux 内核的分区。按以下格式输完命令,然后按 Enter 键:

grub> root (hd0,4) (hit enter)
Filesystem type is reiserfs, partition type 0x83

装入内核

现在已安装了 root 文件系统,到装入内核的时候了。在 GRUB 提示中,依次输入 "kernel"、空格、到内核的路径、空格、内核参数,如 root 参数(GRUB 将自动插入适当的 "mem=" 参数)。我在我的系统中输入:

grub> kernel /boot/bz2.4 root=/dev/hda5
[Linux-bzImage, setup=0x1200, size=0xe1a30]

请留意 "root=" 内核参数,它非常重要。它应该指向保存 root 文件系统的 Linux 分区。您也许要写下到目前为止输入的命令,这样在教程后面讲述如何创建 GRUB 引导菜单时,就可以迅速找到它们。

Root,内核,引导!

您已经安装了 root 文件系统并装入了内核。现在,可以引导了。只要输入 "boot",Linux 引导过程就将开始。

第五章 GRUB 内幕

重新调查引导软盘

如果一切正常,就可以使用使用 GRUB 引导盘来引导当前 Linux 发行版。如您所见,GRUB 是功能非常强大的引导装入器,它让您可以随意动态配置以进行引导。我将向您介绍如何创建 GRUB 引导菜单,这样就可以从菜单中进行 OS 选择,而不是输入三行命令来引导 Linux。但在动手之前,现在是深入了解 GRUB 幕后是如何工作的好时机。我将说明引导盘引导过程的工作原理,这样您就可以对 GRUB 有一个更好的评价和了解。

两阶段过程

要制作引导软盘,需要做两件事 -- 将文件复制到软盘的 ext2 文件系统的 /boot/grub 目录中,运行 GRUB 的安装程序。运行 GRUB 安装程序时,GRUB 将 "stage 1" 装入器安装到软盘的引导记录中。它还将 stage 1 装入器配置成从 ext2 文件系统直接装入 stage2。通常,GRUB 通过在包含 stage2 数据的软盘上创建一列块来完成此操作,这样 stage1 装入 stage2 时不必知道 ext2 文件系统的任何情况。

但是,大多数情况下,GRUB 在安装完 stage1 之后,会立即将 stage1.5 装入器安装到引导记录中。这个特殊的 stage1.5 允许无需使用原始块列表就能从 ext2 文件系统装入 stage2,却要更灵活的标准基于路径的方法。GRUB 理解文件系统结构的这一能力使 GRUB 比 LILO 更强壮。例如,如果正好在整理引导盘文件系统的碎片,stage1 就可以找到 stage2(归功于 ext2 stage1.5)。而 LILO 就不能完成此项操作。因为 LILO 只能依赖于映射文件,每次更新内核或在磁盘上物理移动数据时,即使不更改路径,也需要重新运行它。

阶段 1、1.5 和 2

您也许会想知道:如果使用 FAT 而不是 ext2 文件系统创建引导盘,GRUB 是否可以工作。是的,它可以工作,因为在输入 "setup (fd0)" 时,GRUB 会安装与 root 文件系统类型匹配的 stage1.5。即使没有没有空间可以安装 stage1.5,GRUB 仍可以通过追溯到更原始的块列表,来装入 stage2。

搜索和恢复

在继续讨论之前,先研究一个与引导软盘相关的实用提示。由于 GRUB 的交互式性质,它为恢复软盘生成了一个很好的引导装入器。但是,如果将好的内核复制到引导盘上,那它就更好了。那样,即使硬盘上的内核坏了或者被意外删除了,也可以追溯到引导盘内核,并启动和运行系统。要将备用内核复制到引导盘,执行以下操作:

# mount /dev/fd0 /mnt/floppy
# cp /path/to/bzImage /mnt/floppy/boot
# umount /dev/fd0

现在,软盘已包含备用内核,可以在 GRUB 中使用它来引导 Linux 发行版,操作如下:

grub> root (fd0)
grub> kernel /boot/bzImage root=/dev/hda5 (将 /dev/hda5 更改成想要引导的分区名称)
grub> boot

硬盘引导

好,现在如何将 GRUB 安装到硬盘上?这个过程几乎与引导盘安装过程一样。首先,需要决定哪个硬盘分区将成为 root GRUB 分区。在这个分区上,创建 /boot/grub 目录,并将 stage1 和 stage2 文件从 /usr/share/grub/i386-pc 复制到该目录中。可以通过重新引导系统并使用引导盘,或者使用驻留版本的 GRUB 来执行后一步操作。在这两种情况下,启动 GRUB,并用 root 命令指定 root 分区。例如,如果将 stage1 和 stage2 文件复制到 hda5 的 /boot/grub 目录中,应输入 "root (hd0,4)"。现在,只差一步。

硬盘引导,续

接着,决定在哪里安装 GRUB -- 在硬盘的 MBR,或者如果与 GRUB 一起使用另一个“主”引导装入器,则安装在特定分区的引导记录中。如果安装到 MBR,则可以指定整个磁盘而不必指定分区,如下(对于 hda):

grub> setup (hd0)

如果要将 GRUB 安装到 /dev/hda5 的引导记录中,应输入:

grub> setup (hd0,4)

现在,已安装 GRUB。引导系统时,应该立即以 GRUB 的控制台方式结束(如果安装到 MBR)。现在,应创建引导菜单,这样就不必在每次引导系统时都输入那些命令。

引导菜单

要创建菜单,只需在 /boot/grub 中创建一个简单的文本文件 menu.lst。如果将它放在正确位置,它将在 root GRUB 驱动器的 stage1 和 stage2 文件的旁边。这里是一个样本 menu.lst 文件,可以将它作为一个您的菜单的基础:

default 0
timeout 30
color white/blue blue/green

title=Boot Linux
root (hd0,4)
kernel /boot/bzImage root=/dev/hda5

title=Boot Linux using initrd
root (hd0,5)
kernel /boot/bzImage root=/dev/loop0 init=/initdisk.gz
initrd /initdisk.gz

title=Windows NT
root (hd0,3)
chainloader +1

我将在以下的屏面中说明 menu.lst 格式。

理解引导菜单

引导菜单很容易理解。前三行设置缺省菜单项(项目号 0,第一个)、设置超时值(30 秒),并选择整个单的一些颜色。

接着的三行配置 "Boot Linux" 菜单项。要创建手工引导命令系列之外的菜单项,只要添加一行 "title=" 作为第一行,并从最后一行中除去 "boot" 命令(GRUB 会自动添加这个命令)。

接着的四行显示了如何使用 GRUB 来引导 initrd(初始 root 磁盘),如果您愿意的话。现在,讨论最后三行……

链式装入器

这里是示例 menu.lst 的最后三行……

title=Windows NT
root (hd0,3)
chainloader +1

这里,我添加了一项来引导 Windows NT。要完成此操作,GRUB 使用了“链式装入器”。链式装入器从分区 (hd0,3) 的引导记录中装入 NT 自己的引导装入器,然后引导它。这就是这种技术叫做链式装入的原因 -- 它创建了一个从引导装入器到另一个的链。这种链式装入技术可以用于引导任何版本的 DOS 或 Windows。
 
第六章 总结

GRUB 的弹性

GRUB 最好的优点之一就是其强健的设计 -- 在不断使用它时请别忘了这点。如果更新内核或更改它在磁盘上的位置,不必重新安装 GRUB。事实上,如有必要,只要更新 menu.lst 文件即可,一切将保持正常。

只有少数情况下,才需要将 GRUB 引导装入器重新安装到引导记录。首先,如果更改 GRUB root 分区的分区类型(例如,从 ext2 改成 ReiserFS),则需要重新安装。或者,如果更新 /boot/grub 中的 stage1 和 stage2 文件,由于它们来自更新版本的 GRUB,很有可能要重新安装引导装入器。其它情况下,可以不必理睬!

优秀的 GRUB 参考资料

我们在这里只是介绍了 GRUB 的一部分。例如,可以使用 GRUB 来执行网络引导,引导 BSD 文件系统,或更多操作。另外,GRUB 有许多配置和安全性命令也很有用。如需所有 GRUB 功能的完整描述,请阅读 GRUB 出色的 GNU 文档。只要在 bash 提示中输入 "info grub" 就可以阅读该文档。

希望您喜欢本教程,喜欢 GRUB (Grand Unified Boot Loader) 的强大功能和灵活性!

ftp://alpha.gnu.org/gnu/grub/

- 作者: chongsoft 2008年06月3日, 星期二 13:49  回复(0) |  引用(0) 加入博采

EABI overview
还有一篇解释由来的文章,说得全面一些。我侧重的是内核的变动。

Posted - 11 Jan 2007 : 13:55:58
--------------------------------------------------------------------------------

What is EABI?

GNU EABI is a new application binary interface (ABI) for Linux. It is part of a new family of ABI's from ARM?Ltd. known in the arm-linux community as EABI (or sometimes Embedded ABI).

How do I use EABI?

To use EABI, you'll need:
A linux kernel that supports EABI. All BitsyG5 kernels support EABI. We also have an experimental kernel for several of our other boards that is configured with EABI support enabled. You can boot these kernels from an ATA flash card or on-board flash just as you would our regular kernels.

An EABI root file system based on ADS' new Debian port which is available for download here.
You can install this root on an NFS server or harddrive, just as you would our regular full Debian distribution.
The root file system includes an arm-linux-gnueabi toolchain which you can use to recompile your own source code.

Why switch to EABI?

According to Debian Wiki the new EABI:
Allows mixing softfloat and hardfloat code.
Uses a more efficient syscall convention.

Will be more compatible with future tools.
Furthermore, the GCC default for EABI will be to use softfloat instructions for floating point arithmetic.

Traditionally, GCC used hardfloat FPA [1] instructions in arm-linux software. Unfortunately, most ARM processors lack support for FPA and instead rely on the kernel for floating point emulation (FPE). They do this through illegal instruction faults which are rather inefficient. Emulating floating point instructions using softfloat is significantly faster than the standard NWFPE used in most arm-linux kernels and appreciably faster than the FastFPE we use in our kernels.

Like most root file systems for ARM computers, Debian uses GCC's default for floating point operations. Prior to the introduction of EABI, the only way to use softfloat was to recompile the entire root file system with softfloat enabled. Now, with an EABI root file system, softfloat instructions will be used by default and you'll be able to mix hard and soft float executables.

In the absence of FPA hardware, the switch to softfloat alone will offer appreciable performance improvement. Additionaly, if your system has some non-FPA floating point hardware, you can recompile critical software with the appropriate hardfloat instructions for an even greater improvement in performance and still be able to run it alongside software that uses softfloat.

Besides FPA, what other intruction sets are there ?

We have or will have systems that use VFP (Vector Floating Points), Maverick Crunch (Cirrus Logic), and iWMMXt (Intel) instructions. (The iWMMXt instructions are actually integer SIMD instructions but their opcodes overlap those for FPA).


[1] FPA is a specific kind of floating point acceleration. Many ARM processors have floating point acceleration, just not the FPA variety.

- 作者: chongsoft 2008年04月13日, 星期日 11:41  回复(0) |  引用(0) 加入博采

Linux 2.6高版本中的系统调用方式


下面的代码大家可以在entry-common.S中找到。

在2.6.21中,认真研究大家会发现,你回避不了这样一个概念,EABI是什么东西?

内核里面谈EABI,OABI,其实相对于系统调用的方式,当然我们所的系统限于arm系统。
EABI (Extended ABI),说的是这样的一种新的系统调用方式

mov r7, #num
swi  0x0

原来的系统调用方式是这样,
swi (#num | 0x900000)   (0x900000是个magic值)

也就是说原来的调用方式(Old ABI)是通过跟随在swi指令中的调用号来进行的,现在的是根据r7中的值。

现在看两个宏,一个是
CONFIG_OABI_COMPAT    意思是说和old ABI兼容

另一个是
CONFIG_AEABI          意思是说指定现在的方式为EABI

这两个宏可以同时配置,也可以都不配,也可以配置任何一种。

我说一下内核是怎么处理这一问题的。
我们知道,sys_call_table 在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针就是系统调用函数的指针,如(sys_open).系统调用是根据一个调用号(通常就是表的索引)找到实际该调用内核哪个函数,然后运行该函数完成的。

首先,对于old ABI,内核给出的处理是给它建立一个单独的system call table,叫sys_oabi_call_table,这样,兼容方式下就会有两个system call table, 以old ABI方式的系统调用会执行old_syscall_table表中的系统调用函数,EABI方式的系统调用会用sys_call_table中的函数指针。

配置无外乎以下4中

第一 两个宏都配置 行为就是上面说的那样

第二 只配置CONFIG_OABI_COMPAT , 那么以old ABI方式调用的会用sys_oabi_call_table,以EABI方式调用的   用sys_call_table,和1实质相同,只是情况1更加明确。

第三 只配置CONFIG_AEABI    系统中不存在 sys_oabi_call_table, 对old ABI方式调用不兼容。只能  以EABI方式调用,用sys_call_table       

第四 两个都没有配置  系统默认会只允许old ABI方式,但是不存在old_syscall_table,最终会通过sys_call_table 完成函数调用

可以参考下面的代码
对我们的项目比较有用。


.align 5
ENTRY(vector_swi)
 sub sp, sp, #S_FRAME_SIZE
 stmia sp, {r0 - r12}   @ Calling r0 - r12
 add r8, sp, #S_PC
 stmdb r8, {sp, lr}^   @ Calling sp, lr
 mrs r8, spsr   @ called from non-FIQ mode, so ok.
 str lr, [sp, #S_PC]   @ Save calling PC
 str r8, [sp, #S_PSR]  @ Save CPSR
 str r0, [sp, #S_OLD_R0]  @ Save OLD_R0
 zero_fp

 /*
  * Get the system call number.
  */

#if defined(CONFIG_OABI_COMPAT)

 /*
  * If we have CONFIG_OABI_COMPAT then we need to look at the swi
  * value to determine if it is an EABI or an old ABI call.
  */
#ifdef CONFIG_ARM_THUMB
 tst r8, #PSR_T_BIT
 movne r10, #0    @ no thumb OABI emulation
 ldreq r10, [lr, #-4]   @ get SWI instruction
#else
 ldr r10, [lr, #-4]   @ get SWI instruction
  A710( and ip, r10, #0x0f000000  @ check for SWI  )
  A710( teq ip, #0x0f000000      )
  A710( bne .Larm710bug      )
#endif

#elif defined(CONFIG_AEABI)

 /*
  * Pure EABI user space always put syscall number into scno (r7).
  */
  A710( ldr ip, [lr, #-4]   @ get SWI instruction )
  A710( and ip, ip, #0x0f000000  @ check for SWI  )
  A710( teq ip, #0x0f000000      )
  A710( bne .Larm710bug      )

#elif defined(CONFIG_ARM_THUMB)

 /* Legacy ABI only, possibly thumb mode. */
 tst r8, #PSR_T_BIT   @ this is SPSR from save_user_regs
 addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in
 ldreq scno, [lr, #-4]

#else

 /* Legacy ABI only. */
 ldr scno, [lr, #-4]   @ get SWI instruction
  A710( and ip, scno, #0x0f000000  @ check for SWI  )
  A710( teq ip, #0x0f000000      )
  A710( bne .Larm710bug      )

#endif

#ifdef CONFIG_ALIGNMENT_TRAP
 ldr ip, __cr_alignment
 ldr ip, [ip]
 mcr p15, 0, ip, c1, c0  @ update control register
#endif
 enable_irq

 get_thread_info tsk
 adr tbl, sys_call_table  @ load syscall table pointer
 ldr ip, [tsk, #TI_FLAGS]  @ check for syscall tracing

#if defined(CONFIG_OABI_COMPAT)
 /*
  * If the swi argument is zero, this is an EABI call and we do nothing.
  *
  * If this is an old ABI call, get the syscall number into scno and
  * get the old ABI syscall table address.
  */
 bics r10, r10, #0xff000000
 eorne scno, r10, #__NR_OABI_SYSCALL_BASE
 ldrne tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
 bic scno, scno, #0xff000000  @ mask off SWI op-code
 eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
#endif

 stmdb sp!, {r4, r5}   @ push fifth and sixth args
 tst ip, #_TIF_SYSCALL_TRACE  @ are we tracing syscalls?
 bne __sys_trace

 cmp scno, #NR_syscalls  @ check upper syscall limit
 adr lr, ret_fast_syscall  @ return address
 ldrcc pc, [tbl, scno, lsl #2]  @ call sys_* routine

 add r1, sp, #S_OFF
2: mov why, #0    @ no longer a real syscall
 cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
 eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back
 bcs arm_syscall
 b sys_ni_syscall   @ not private func

 /*
  * This is the really slow path.  We're going to be doing
  * context switches, and waiting for our parent to respond.
  */
__sys_trace:
 mov r2, scno
 add r1, sp, #S_OFF
 mov r0, #0    @ trace entry [IP = 0]
 bl syscall_trace

 adr lr, __sys_trace_return  @ return address
 mov scno, r0   @ syscall number (possibly new)
 add r1, sp, #S_R0 + S_OFF  @ pointer to regs
 cmp scno, #NR_syscalls  @ check upper syscall limit
 ldmccia r1, {r0 - r3}   @ have to reload r0 - r3
 ldrcc pc, [tbl, scno, lsl #2]  @ call sys_* routine

- 作者: chongsoft 2008年04月11日, 星期五 16:20  回复(2) |  引用(0) 加入博采

arm 中软件浮点库中对除0运算的处理原理


Li Zheng: __div0
/opt2/montavista050511/foundation/host/srcmontavista/BUILD/gcc-

3.3.1/objdir/gcc/../../gcc/config/arm/lib1funcs.asm:644
Li Zheng: 那就是说除0是不能在报告中准确显示代码的位置
Zhang Chong: 似乎是编译器会把除0的位置编译成一段特殊的代码
Li Zheng: 好像是编译器发现了除0这个代码
Zhang Chong: 是编译器的浮点库
Zhang Chong: 静态连接的
Zhang Chong: 你清楚了吧
Zhang Chong: 看看上面那段汇编
Zhang Chong: 在test15中找到的
Zhang Chong: 编译器会对除法生成特殊的代码
Zhang Chong: 本来就是该提供浮点库 有些运算器不支持除法的 必须软件实现
Zhang Chong: 这个意思就是softfp
Zhang Chong: 我刚写的一段代码
Zhang Chong: 编出来的
Zhang Chong:  
    int i = 0;
    int j = 3 / i;
    printf("j = %d\n",j);
Zhang Chong:   
    83ec:       e3a00003        mov     r0, #3  ; 0x3
    83f0:       e51b1010        ldr     r1, [fp, -#16]   //取出i (0)
    83f4:       eb000006        bl      8414 <__divsi3>
    83f8:       e1a03000        mov     r3, r0

Zhang Chong: 00008414 <__divsi3>:
    8414:       e020c001        eor     ip, r0, r1
    8418:       e3a03001        mov     r3, #1  ; 0x1
    841c:       e3a02000        mov     r2, #0  ; 0x0
    8420:       e3510000        cmp     r1, #0  ; 0x0
    8424:       42611000        rsbmi   r1, r1, #0      ; 0x0
    8428:       0a000021        beq     84b4 <Ldiv0>
    842c:       e3500000        cmp     r0, #0  ; 0x0
    8430:       42600000        rsbmi   r0, r0, #0      ; 0x0
    8434:       e1500001        cmp     r0, r1
    8438:       3a000019        bcc     84a4 <Lgot_result>

Zhang Chong: Ldiv0  -> __div0
Zhang Chong:
Zhang Chong: 0000ee84 <__div0>:
    ee84:       e92d4002        stmdb   sp!, {r1, lr}
    ee88:       ef900014        swi     0x00900014
    ee8c:       e3700ffa        cmn     r0, #1000       ; 0x3e8
    ee90:       28bd8002        ldmcsia sp!, {r1, pc}
    ee94:       e3a01008        mov     r1, #8  ; 0x8
    ee98:       ef900025        swi     0x00900025
    ee9c:       e8bd4002        ldmia   sp!, {r1, lr}
    eea0:       e12fff1e        bx      lr
Zhang Chong:   mov     r1, #8  ; 0x8  的意思是SIGFPE
Zhang Chong:
/* 35 */   .long   sys_ni_syscall      /* was sys_ftime */
         .long   sys_sync
         .long   sys_kill
Zhang Chong: swi     0x00900025 -> sys_kill
Zhang Chong: swi     0x00900014 -> sys_getpid

- 作者: chongsoft 2008年03月18日, 星期二 18:51  回复(0) |  引用(0) 加入博采

Bash中对变量的操作

Bash中对变量的操作
本文出自:http://www.ytht.org 作者:chenhao (2001-10-29 07:00:00)

1.条件变量替换:
  Bash Shell可以进行变量的条件替换,既只有某种条件发生时才进行替换,替换
条件放在{}中.
  (1) ${value:-word}
      当变量未定义或者值为空时,返回值为word的内容,否则返回变量的值.
  (2) ${value:=word}
      与前者类似,只是若变量未定义或者值为空时,在返回word的值的同时将
      word赋值给value
  (3) ${value:?message}
      若变量以赋值的话,正常替换.否则将消息message送到标准错误输出(若
      此替换出现在Shell程序中,那么该程序将终止运行)
  (4) ${value:+word}
      若变量以赋值的话,其值才用word替换,否则不进行任何替换
  (5) ${value:offset}
      ${value:offset:length}
      从变量中提取子串,这里offset和length可以是算术表达式.
  (6) ${#value}
      变量的字符个数
  (7) ${value#pattern}
      ${value##pattern}
      去掉value中与pattern相匹配的部分,条件是value的开头与pattern相匹配
      #与##的区别在于一个是最短匹配模式,一个是最长匹配模式.
  (8) ${value%pattern}
      ${value%%pattern}
      于(7)类似,只是是从value的尾部于pattrn相匹配,%与%%的区别与#与##一样
  (9) ${value/pattern/string}
      ${value//pattern/string}
      进行变量内容的替换,把与pattern匹配的部分替换为string的内容,/与//的区
      别与上同

注意:上述条件变量替换中,除(2)外,其余均不影响变量本身的值


2.变量的算术运算
  在Bash Shell中,只能进行两个整数间的运算,其结果仍为整数.要进行算术
运算,需要使用let命令,语法为:
  let expr
  expr是一个包含项和操作符的表达式,项可以是一个变量或是一个整数常数,
当使用整数常数时,其默认为十进制整数,用户可以用radio#number来指定其它
形式的整数,其中radio定义了整数是几进制表示的,number是该整数的值.若
radio>10,那么数字字符可从0-9和A-Z.
  在表达式中支持的操作符及其含义为:
  +,-,*,/,%     加,减,乘,除,取模
  >>,<<,&,^,|   左移,右移,位与,位异或,位或
  ?:            三元运算符.与C语言中的定义一致
  ~             取补码
  !,>=,<=,>,<,==,!=,&&,||
  =,+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=
  表达式式中也可以使用括号.括号或运算优先级的定义与一般计算机语言中的
相同.
  let命令具有返回值.当计算结果(若有多个表达式时,以最后一个为准)为0时,
返回值为1,否则为0.
  当表达式中含有shell的特殊字符(如|)时,需要用引用符('或")将其引用起来.
  使用let时还需要注意的时,对于let x+y这样的式子,shell虽然计算了x+y的值
但却将结果丢弃,若不想这样,可以使用let sum=x+y将x+y的结果保存在变量sum中
  另外还可以使用((和))操作符取代let命令,而且这样的话,还可以省去对算术
表达式的引用,如果想返回表达式的值,则需用$(())的格式.
 
 

- 作者: chongsoft 2008年02月19日, 星期二 12:14  回复(0) |  引用(0) 加入博采

Bash中对变量的操作

Bash中对变量的操作
本文出自:http://www.ytht.org 作者:chenhao (2001-10-29 07:00:00)

1.条件变量替换:
  Bash Shell可以进行变量的条件替换,既只有某种条件发生时才进行替换,替换
条件放在{}中.
  (1) ${value:-word}
      当变量未定义或者值为空时,返回值为word的内容,否则返回变量的值.
  (2) ${value:=word}
      与前者类似,只是若变量未定义或者值为空时,在返回word的值的同时将
      word赋值给value
  (3) ${value:?message}
      若变量以赋值的话,正常替换.否则将消息message送到标准错误输出(若
      此替换出现在Shell程序中,那么该程序将终止运行)
  (4) ${value:+word}
      若变量以赋值的话,其值才用word替换,否则不进行任何替换
  (5) ${value:offset}
      ${value:offset:length}
      从变量中提取子串,这里offset和length可以是算术表达式.
  (6) ${#value}
      变量的字符个数
  (7) ${value#pattern}
      ${value##pattern}
      去掉value中与pattern相匹配的部分,条件是value的开头与pattern相匹配
      #与##的区别在于一个是最短匹配模式,一个是最长匹配模式.
  (8) ${value%pattern}
      ${value%%pattern}
      于(7)类似,只是是从value的尾部于pattern相匹配,%与%%的区别与#与##一样
  (9) ${value/pattern/string}
      ${value//pattern/string}
      进行变量内容的替换,把与pattern匹配的部分替换为string的内容,/与//的区
      别与上同

注意:上述条件变量替换中,除(2)外,其余均不影响变量本身的值


2.变量的算术运算
  在Bash Shell中,只能进行两个整数间的运算,其结果仍为整数.要进行算术
运算,需要使用let命令,语法为:
  let expr
  expr是一个包含项和操作符的表达式,项可以是一个变量或是一个整数常数,
当使用整数常数时,其默认为十进制整数,用户可以用radio#number来指定其它
形式的整数,其中radio定义了整数是几进制表示的,number是该整数的值.若
radio>10,那么数字字符可从0-9和A-Z.
  在表达式中支持的操作符及其含义为:
  +,-,*,/,%     加,减,乘,除,取模
  >>,<<,&,^,|   左移,右移,位与,位异或,位或
  ?:            三元运算符.与C语言中的定义一致
  ~             取补码
  !,>=,<=,>,<,==,!=,&&,||
  =,+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=
  表达式式中也可以使用括号.括号或运算优先级的定义与一般计算机语言中的
相同.
  let命令具有返回值.当计算结果(若有多个表达式时,以最后一个为准)为0时,
返回值为1,否则为0.
  当表达式中含有shell的特殊字符(如|)时,需要用引用符('或")将其引用起来.
  使用let时还需要注意的时,对于let x+y这样的式子,shell虽然计算了x+y的值
但却将结果丢弃,若不想这样,可以使用let sum=x+y将x+y的结果保存在变量sum中
  另外还可以使用((和))操作符取代let命令,而且这样的话,还可以省去对算术
表达式的引用,如果想返回表达式的值,则需用$(())的格式.
 
 

- 作者: chongsoft 2008年02月19日, 星期二 12:12  回复(0) |  引用(0) 加入博采

一定要记住这个

// const char *a == char const *a !=  char  * const a

const int *A;        //const修饰指向的对象,A可变,A指向的对象不可变

int const *A;       //const修饰指向的对象,A可变,A指向的对象不可变

int *const A;       //const修饰指针A,     A不可变,A指向的对象可变

const int *const A;  //指针A和A指向的对象都不可变

- 作者: chongsoft 2007年12月24日, 星期一 16:53  回复(0) |  引用(0) 加入博采

漏极开路的分析 (zhuan)
漏极开路的分析
  A:我们先来说说集电极开路输出的结构。集电极开路输出的结构如图1所示,右边的那个三极管集电极什么都不接,所以叫做集电极开路(左边的三极管为反相之用,使输入为“0”时,输出也为“0”)。对于图1,当左端的输入为“0”时,前面的三极管截止(即集电极C跟发射极E之间相当于断开),所以5V电源通过1K电阻加到右边的三极管上,右边的三极管导通(即相当于一个开关闭合);当左端的输入为“1”时,前面的三极管导通,而后面的三极管截止(相当于开关断开)。

我们将图1简化成图2的样子。图2中的开关受软件控制,“1”时断开,“0”时闭合。很明显可以看出,当开关闭合时,输出直接接地,所以输出电平为0。而当开关断开时,则输出端悬空了,即高阻态。这时电平状态未知,如果后面一个电阻负载(即使很轻的负载)到地,那么输出端的电平就被这个负载拉到低电平了,所以这个电路是不能输出高电平的。

再看图三。图三中那个1K的电阻即是上拉电阻。如果开关闭合,则有电流从1K电阻及开关上流过,但由于开关闭和时电阻为0(方便我们的讨论,实际情况中开关电阻不为0,另外对于三极管还存在饱和压降),所以在开关上的电压为0,即输出电平为0。如果开关断开,则由于开关电阻为无穷大(同上,不考虑实际中的漏电流),所以流过的电流为0,因此在1K电阻上的压降也为0,所以输出端的电压就是5V了,这样就能输出高电平了。但是这个输出的内阻是比较大的(即1KΩ),如果接一个电阻为R的负载,通过分压计算,就可以算得最后的输出电压为5*R/(R+1000)伏,即5/(1+1000/R)伏。所以,如果要达到一定的电压的话,R就不能太小。如果R真的太小,而导致输出电压不够的话,那我们只有通过减小那个1K的上拉电阻来增加驱动能力。但是,上拉电阻又不能取得太小,因为当开关闭合时,将产生电流,由于开关能流过的电流是有限的,因此限制了上拉电阻的取值,另外还需要考虑到,当输出低电平时,负载可能还会给提供一部分电流从开关流过,因此要综合这些电流考虑来选择合适的上拉电阻。

如果我们将一个读数据用的输入端接在输出端,这样就是一个IO口了(51IO口就是这样的结构,其中P0口内部不带上拉,而其它三个口带内部上拉),当我们要使用输入功能时,只要将输出口设置为1即可,这样就相当于那个开关断开,而对于P0口来说,就是高阻态了。

对于漏极开路(OD)输出,跟集电极开路输出是十分类似的。将上面的三极管换成场效应管即可。这样集电极就变成了漏极,OC就变成了OD,原理分析是一样的。

 

另一种输出结构是推挽输出。推挽输出的结构就是把上面的上拉电阻也换成一个开关,当要输出高电平时,上面的开关通,下面的开关断;而要输出低电平时,则刚好相反。比起OC或者OD来说,这样的推挽结构高、低电平驱动能力都很强。如果两个输出不同电平的输出口接在一起的话,就会产生很大的电流,有可能将输出口烧坏。而上面说的OCOD输出则不会有这样的情况,因为上拉电阻提供的电流比较小。如果是推挽输出的要设置为高阻态时,则两个开关必须同时断开(或者在输出口上使用一个传输门),这样可作为输入状态,AVR单片机的一些IO口就是这种结构。

300)this.width=300" />

- 作者: chongsoft 2007年12月12日, 星期三 18:40  回复(0) |  引用(0) 加入博采

关于JTAG写得比较明白的文章
到底什么是JTAG呢?
JTAG(Joint Test Action Group)联合测试行动小组)是一种国际标准测试协议(IEEE 1149.1兼容),主要用于芯片内部测试。现在多数的高级器件都支持JTAG协议,如DSP、FPGA器件等。标准的JTAG接口是4线:TMS、 TCK、TDI、TDO,分别为模式选择、时钟、数据输入和数据输出线。
JTAG最初是用来对芯片进行测试的,基本原理是在器件内部定义一个TAP(Test Access Port&#0;测试访问口)通过专用的JTAG测试工具对进行内部节点进行测试。JTAG测试允许多个器件通过JTAG接口串联在一起,形成一个JTAG链,能实现对各个器件分别测试。现在,JTAG接口还常用于实现ISP(In-System rogrammable&#0;在线编程),对FLASH等器件进行编程。
JTAG编程方式是在线编程,传统生产流程中先对芯片进行预编程现再装到板上因此而改变,简化的流程为先固定器件到电路板上,再用JTAG编程,从而大大加快工程进度。JTAG接口可对PSD芯片内部的所有部件进行编程
 
JTAG的一些说明

通常所说的JTAG大致分两类,一类用于测试芯片的电气特性,检测芯片是否有问题;一类用于Debug;一般支持JTAG的CPU内都包含了这两个模块。
一个含有JTAG Debug接口模块的CPU,只要时钟正常,就可以通过JTAG接口访问CPU的内部寄存器和挂在CPU总线上的设备,如FLASH,RAM,SOC(比如4510B,44Box,AT91M系列)内置模块的寄存器,象UART,Timers,GPIO等等的寄存器。

上面说的只是JTAG接口所具备的能力,要使用这些功能,还需要软件的配合,具体实现的功能则由具体的软件决定。
例如下载程序到RAM功能。了解SOC的都知道,要使用外接的RAM,需要参照SOC DataSheet的寄存器说明,设置RAM的基地址,总线宽度,访问速度等等。有的SOC则还需要Remap,才能正常工作。运行Firmware时,这些设置由Firmware的初始化程序完成。但如果使用JTAG接口,相关的寄存器可能还处在上电值,甚至时错误值,RAM不能正常工作,所以下载必然要失败。要正常使用,先要想办法设置RAM。在ADW中,可以在Console窗口通过Let 命令设置,在AXD中可以在Console窗口通过Set命令设置。
下面是一个设置AT91M40800的命令序列,关闭中断,设置CS0-CS3, 并进行Remap,适用于AXD(ADS带的Debug)
setmem 0xfffff124,0xFFFFFFFF,32 ---关闭所有中断
setmem 0xffe00000,0x0100253d,32 ---设置CS0
setmem 0xffe00004,0x02002021,32 ---设置CS1
setmem 0xffe00008,0x0300253d,32 ---设置CS2
setmem 0xffe0000C,0x0400253d,32 ---设置CS3
setmem 0xffe00020,1,32 ---Remap
如果要在ADW(SDT带的DEBUG)中使用,则要改为:
let 0xfffff124=0xFFFFFFFF ---关闭所有中断
let 0xffe00000=0x0100253d ---设置CS0
let 0xffe00004=0x02002021 ---设置CS1
let 0xffe00008=0x0300253d ---设置CS2
let 0xffe0000C=0x0400253d ---设置CS3
let 0xffe00020=1 ---Remap
为了方便使用,可以将上述命令保存为一个文件config.ini, 在Console窗口输入 ob config.ini 即可执行。
使用其他debug,大体类似,只是命令和命令的格式不同。


设置RAM时,设置的寄存器以及寄存器的值必须和要运行程序的设置一致。一般编译生成的目标文件是ELF格式,或类似的格式,包含有目标码运行地址,运行地址在Link时候确定。Debug下载程序时根据ELF文件中的地址信息下载程序到指定的地址。如果在把RAM的基地址设置为0x10000000, 而在编译的时候指定Firmware的开始地址在0x02000000, 下载的时候,目标码将被下载到0x02000000,显然下载会失败。

通过JTAG下载程序前应关闭所有中断,这一点和Firmware初始化时关闭中断的原因相同。在使用JTAG接口的时候,各中断的使能未知,尤其是 FLASH里有可执行码的情况,可能会有一些中断被使能。使用JTAG下载完代码,要执行时,有可能因为未完成初始化就产生了中断,导致程序异常。所以,需要先关闭中断,一般通过设置SOC的中断控制寄存器完成。

使用JTAG写Flash。在理论上,通过JTAG可以访问CPU总线上的所有设备,所以应该可以写FLASH,但是FLASH写入方式和RAM大不相同,需要特殊的命令,而且不同的FLASH擦除,编程命令不同,而且块的大小,数量也不同,很难提供这一项功能。所以一般Debug不提供写Flash功能,或者仅支持少量几种Flash。

目前就我知道的,针对 ARM,只有FlashPGM这个软件提供写FLASH功能,但使用也非常麻烦。AXD,ADW都不提供写FLASH功能。我写Flash的方法时是,自己写一个简单的程序,专门用于写目标板的FLASH,利用JTAG接口,下载到目标板,再把要烧写的目标码装成BIN格式,也下到目标板(地址和烧 FLASH的程序的地址不同),然后运行已经下载的烧FLASH的程序。使用这种方式,比起FlashPGM的写Flash,速度似乎要快一些。

关于简单JTAG电缆。
目前有各种各样简单JTAG电缆,其实只是一个电平转换电路,同时还起到保护作用。JTAG的逻辑则由运行在PC上的软件实现,所以在理论上,任何一个简单 JTAG电缆,都可以支持各种应用软件,如Debug等。我就曾使用同一个JTAG电缆写Xilinx CPLD,AXD/ADW调试程序。关键再于软件的支持,大多数软件都不提供设定功能,因而只能支持某种JTAG电缆。

关于简单JTAG电缆的速度。
JTAG 是串行接口,使用打印口的简单JTAG电缆,利用的是打印口的输出带锁存的特点,使用软件通过I/O产生JTAG时序。由JTAG标准决定,通过JTAG 写/读一个字节要一系列的操作,根据我的分析,使用简单JTAG电缆,利用打印口,通过JTAG输出一个字节到目标板,平均需要43个打印口I/O, 在我机器上(P4 1.7G),每秒大约可进行660K次 I/O 操作,所以下载速度大约在660K/43, 约等于15K Byte/S. 对于其他机器,I/O速度大致相同,一般在600K ~ 800K.

关于如何提高JTAG下载速度。
很明显,使用简单JTAG电缆无法提高速度。要提高速度,大致有两种办法,
1。使用嵌入式系统提供JTAG接口,嵌入式系统和微机之间通过USB/Ethernet相连,这要求使用MCU。
2。使用CPLD/FPGA提供JTAG接口,CPLD/FPGA和微机之间使用EPP接口(一般微机打印口都支持EPP模式),EPP接口完成微机和CPLD/FPGA之间的数据传输,CPLD/FPGA完成JTAG时序。

这两种方法本人都实现过。第一个方法可以达到比较高的速度,实测超过了200KByte/S(注意:是Byte,不是Bit);但是相对来说,硬件复杂,制造相对复杂。第二种相对来说,下载速度要慢一些,最快时达到96KByte/S,但电路简单,制造方便,而且速度可以满足需要。第二种方案还有一个缺点,由于进行I/O操作时,CPU不会被释放,因此在下载程序时,微机CPU显得很繁忙。
总的来说,本人认为,对于个人爱好者来说,第二种方法更可取

- 作者: chongsoft 2007年12月1日, 星期六 18:31  回复(0) |  引用(0) 加入博采

Linux 异常处理很少有人去涉及的精彩技术细节
我想可以肯定的是,异常处理和中断处理和一般的执行模式不同。不管是用户态还是核心态(usr/svc),不管操作系统的应用运行是一态还是两态,进入异常处理肯定要进行模式切换,这个是由硬件决定的。那么我们知道,sp,lr,pc在不同模式下一般不是一套东西。如果切换后想返回,必须保存现场。所以切换后,用切换后模式的sp (如sp data abort)进行压栈操作可否?可以共用一个栈。
 
进一步的研究可以参考
arch/arm/kernel/entry-armv.S

776 /*
777  * Vector stubs.
778  *
779  * This code is copied to 0xffff0200 so we can use branches in the
780  * vectors, rather than ldr's.  Note that this code must not
781  * exceed 0x300 bytes.
782  *
783  * Common stub entry macro:
784  *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
785  *
786  * SP points to a minimal amount of processor-private memory, the address
787  * of which is copied into r0 for the mode specific abort handler.

788  */
789     .macro  vector_stub, name, correction=0
 
792 vector_\name:
793     .if \correction
794     sub lr, lr, #\correction   //各个模式进入时对lr的调整,切换时硬件会改掉以前的值,做减法得到以前的值
795     .endif
796    
797     @
798     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
799     @ (parent CPSR)
800     @ 
801     stmia   sp, {r0, lr}        @ save r0, lr
802     mrs lr, spsr
803     str lr, [sp, #8]        @ save spsr

804    
。。。
 
vector_stub是一个宏,也是中断向量的地址。可以参考一下。其实主要的思想就是用一个模式相关的一块私有内存保存上一个模式的lr和cpsr, 这样就能正确返回到以前的位置。这个模式私有的位置我觉得可能在初始化的时候指定。仅供参考,呵呵。
我上面提到的话有许多细节,可以参考下面的代码,不过我过几天会写一个关于异常处理的文章,把来龙去脉分析精彩。
775
776 /*
777  * Vector stubs.
778  *
779  * This code is copied to 0xffff0200 so we can use branches in the
780  * vectors, rather than ldr's.  Note that this code must not
781  * exceed 0x300 bytes.
782  *
783  * Common stub entry macro:
784  *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
785  *
786  * SP points to a minimal amount of processor-private memory, the address
787  * of which is copied into r0 for the mode specific abort handler.
788  */
789     .macro  vector_stub, name, correction=0
790     .align  5
791
792 vector_\name:
793     .if \correction
794     sub lr, lr, #\correction
795     .endif
796
797     @
798     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
799     @ (parent CPSR)
800     @
801     stmia   sp, {r0, lr}        @ save r0, lr
802     mrs lr, spsr
803     str lr, [sp, #8]        @ save spsr
804
805     @
806     @ Prepare for SVC32 mode.  IRQs remain disabled.
807     @
808     mrs r0, cpsr
809     bic r0, r0, #MODE_MASK
810     orr r0, r0, #SVC_MODE
811     msr spsr_cxsf, r0
812
813     @
814     @ the branch table must immediately follow this code
815     @
816     mov r0, sp817     and lr, lr, #0x0f
818     ldr lr, [pc, lr, lsl #2]
819     movs    pc, lr          @ branch to handler in SVC mode
820     .endm
821
822     .globl  __stubs_start
823 __stubs_start:
824 /*
825  * Interrupt dispatcher
826  */
827     vector_stub irq, 4
828
829     .long   __irq_usr           @  0  (USR_26 / USR_32)
830     .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
831     .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
832     .long   __irq_svc           @  3  (SVC_26 / SVC_32)
833     .long   __irq_invalid           @  4
834     .long   __irq_invalid           @  5
835     .long   __irq_invalid           @  6
836     .long   __irq_invalid           @  7
837     .long   __irq_invalid           @  8
838     .long   __irq_invalid           @  9
839     .long   __irq_invalid           @  a
840     .long   __irq_invalid           @  b
841     .long   __irq_invalid           @  c
842     .long   __irq_invalid           @  d
843     .long   __irq_invalid           @  e
844     .long   __irq_invalid           @  f
845
846 /*
847  * Data abort dispatcher
848  * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
849  */
850     vector_stub dabt, 8
851
852     .long   __dabt_usr          @  0  (USR_26 / USR_32)
853     .long   __dabt_invalid          @  1  (FIQ_26 / FIQ_32)
854     .long   __dabt_invalid          @  2  (IRQ_26 / IRQ_32)
855     .long   __dabt_svc          @  3  (SVC_26 / SVC_32)
856     .long   __dabt_invalid          @  4
857     .long   __dabt_invalid          @  5
858     .long   __dabt_invalid          @  6
859     .long   __dabt_invalid          @  7
860     .long   __dabt_invalid          @  8
861     .long   __dabt_invalid          @  9
862     .long   __dabt_invalid          @  a
863     .long   __dabt_invalid          @  b
864     .long   __dabt_invalid          @  c
865     .long   __dabt_invalid          @  d
866     .long   __dabt_invalid          @  e
867     .long   __dabt_invalid          @  f
868
869 /*
870  * Prefetch abort dispatcher
871  * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
872  */
873     vector_stub pabt, 4
874
875     .long   __pabt_usr          @  0 (USR_26 / USR_32)
876     .long   __pabt_invalid          @  1 (FIQ_26 / FIQ_32)
877     .long   __pabt_invalid          @  2 (IRQ_26 / IRQ_32)
878     .long   __pabt_svc          @  3 (SVC_26 / SVC_32)
879     .long   __pabt_invalid          @  4
880     .long   __pabt_invalid          @  5
881     .long   __pabt_invalid          @  6
882     .long   __pabt_invalid          @  7
883     .long   __pabt_invalid          @  8
884     .long   __pabt_invalid          @  9
885     .long   __pabt_invalid          @  a
886     .long   __pabt_invalid          @  b
887     .long   __pabt_invalid          @  c
888     .long   __pabt_invalid          @  d
889     .long   __pabt_invalid          @  e
890     .long   __pabt_invalid          @  f
891
892 /*
893  * Undef instr entry dispatcher
894  * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
895  */
896     vector_stub und
897
898     .long   __und_usr           @  0 (USR_26 / USR_32)
899     .long   __und_invalid           @  1 (FIQ_26 / FIQ_32)
900     .long   __und_invalid           @  2 (IRQ_26 / IRQ_32)
901  &nsp;  .long   __und_svc           @  3 (SVC_26 / SVC_32)
902     .long   __und_invalid           @  4
903     .long   __und_invalid           @  5
904     .long   __und_invalid           @  6
905     .long   __und_invalid           @  7
906     .long   __und_invalid           @  8
907     .long   __und_invalid           @  9
908     .long   __und_invalid           @  a
909     .long   __und_invalid           @  b
910     .long   __und_invalid           @  c
911     .long   __und_invalid           @  d
912     .long   __und_invalid           @  e
913     .long   __und_invalid           @  f
941 /*
942  * We group all the following data together to optimise
943  * for CPUs with separate I & D caches.
944  */
945     .align  5
946
947 .LCvswi:
948     .word   vector_swi
949
950     .globl  __stubs_end
951 __stubs_end:
952
953     .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start
954
955     .globl  __vectors_start
956 __vectors_start:
957     swi SYS_ERROR0
958     b   vector_und + stubs_offset
959     ldr pc, .LCvswi + stubs_offset
960     b   vector_pabt + stubs_offset
961     b   vector_dabt + stubs_offset
962     b   vector_addrexcptn + stubs_offset
963     b   vector_irq + stubs_offset
964     b   vector_fiq + stubs_offset
965
966     .globl  __vectors_end
967 __vectors_end:

in arch/arm/traps.c, trap_init()
/*
687      * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
688      * into the vector page, mapped at 0xffff0000, and ensure these
689      * are visible to the instruction stream.
690      */
691     memcpy((void *)0xffff0000, __vectors_start, __vectors_end - __vectors_start);
692     memcpy((void *)0xffff0200, __stubs_start, __stubs_end - __stubs_start);
693     memcpy((void *)0xffff1000 - kuser_sz, __kuser_helper_start, kuser_sz);
694

arch/arm/kernel/setup.c, setup_arch()->paging_init()
setup_arch()->cpu_init()
 95 struct stack {
 96     u32 irq[3];
 97     u32 abt[3];
 98     u32 und[3];
 99 } ____cacheline_aligned;
100
101 static struct stack stacks[NR_CPUS];
331 void cpu_init(void)
332 {  
333     unsigned int cpu = smp_processor_id();
334     struct stack *stk = &stacks[cpu];
335
336     if (cpu >= NR_CPUS) {
337         printk(KERN_CRIT "CPU%u: bad primary CPU number\n", cpu);
338         BUG();
339     }
340
341     dump_cpu_info(cpu);
342
343     /*
344      * setup stacks for re-entrant exception handlers
345      */
346     __asm__ (
347     "msr    cpsr_c, %1\n\t"
348     "add    sp, %0, %2\n\t"
349     "msr    cpsr_c, %3\n\t"
350     "add    sp, %0, %4\n\t"
351     "msr    cpsr_c, %5\n\t"
352     "add    sp, %0, %6\n\t"
353     "msr    cpsr_c, %7"
354         :
355         : "r" (stk),
356           "I" (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
357           "I" (offsetof(struct stack, irq[0])),
358           "I" (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
359           "I" (offsetof(struct stack, abt[0])),
360           "I" (PSR_F_BIT | PSR_I_BIT | UND_MODE),
361           "I" (offsetof(struct stack, und[0])),
362           "I" (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
363         : "r14");
364 }
include/asm-arm/mach/map.h
 12 struct map_desc {
 13     unsigned long virtual;
 14     unsigned long physical;
 15     unsigned long length;
 16     unsigned int type;
 17 }; 
arch/arm/init.c
420 void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
421 {
422     void *zero_page;
423     int node;
424
425     bootmem_init(mi);
426
427     memcpy(&meminfo, mi, sizeof(meminfo));
428
429     /*
430      * allocate the zero page.  Note that we count on this going ok.
431      */
432     zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
433
434     /*
435      * initiaise the page tables.
436      */
437     memtable_init(mi);
438     if (mdesc->map_io)
439         mdesc->map_io();
440     local_flush_tlb_all();
............
void __init memtable_init(struct meminfo *mi)
618 {
619     struct map_desc *init_maps, *p, *q;
620     unsigned long address = 0;
621     int i;
622    
623     build_mem_type_table();
624
625     init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);
626    
627 #ifdef CONFIG_XIP_KERNEL
628     p->physical   = CONFIG_XIP_PHYS_ADDR & PMD_MASK;
629     p->virtual    = (unsigned long)&_stext & PMD_MASK;
630     p->length     = ((unsigned long)&_etext - p->virtual + ~PMD_MASK) & PMD_MASK;
631     p->type       = MT_ROM;
632     p ++;
633 #endif
634        
635     for (i = 0; i < mi->nr_banks; i++) {
636         if (mi->bank[i].size == 0)
637             continue;
638        
639         p->physical   = mi->bank[i].start;
640         p->virtual    = __phys_to_virt(p->physical);
641         p->length     = mi->bank[i].size;
642         p->type       = MT_MEMORY;
643         p ++;
644     }      
645            
646 #ifdef FLUSH_BASE
647     p->physical   = FLUSH_BASE_PHYS;
648     p->virtual    = FLUSH_BASE;
649     p->length     = PGDIR_SIZE;
650     p->type       = MT_CACHECLEAN;
651     p ++;
652 #endif
653
654 #ifdef FLUSH_BASE_MINICACHE
655     p->physical   = FLUSH_BASE_PHYS + PGDIR_SIZE;
656     p->virtual    = FLUSH_BASE_MINICACHE;
657     p->length     = PGDIR_SIZE;
658     p->type       = MT_MINICLEAN;
659     p ++;
660 #endif
661
662     /*
663      * Go through the initial mappings, but clear out any
664      * pgdir entries that are not in the description.
665      */
666     q = init_maps;
667     do {
668         if (address < q->virtual || q == p) {
669             clear_mapping(address);
670             address += PGDIR_SIZE;
671         } else {
672             create_mapping(q);
673
674             address = q->virtual + q->length;
675             address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;
676
677             q ++;
678         }
679     } while (address != 0);
680
681     /*
682      * Create a mapping for the machine vectors at the high-vectors
683      * location (0xffff0000).  If we aren't using high-vectors, also
684      * create a mapping at the low-vectors virtual address.
685      */
686     init_maps->physical   = virt_to_phys(init_maps);
687     init_maps->virtual    = 0xffff0000;
688     init_maps->length     = PAGE_SIZE;
689     init_maps->type       = MT_HIGH_VECTORS;
690     create_mapping(init_maps);
691
692     if (!vectors_high()) {
693         init_maps->virtual = 0;
694         init_maps->type = MT_LOW_VECTORS;
695         create_mapping(init_maps);
696     }
697
698     flush_cache_all();
699     local_flush_tlb_all();
700
701     top_pmd = pmd_off_k(0xffff0000);
702 }

- 作者: chongsoft 2007年11月27日, 星期二 18:11  回复(0) |  引用(0) 加入博采

gdb 的几点注意

线程有自己的寄存器,运行时堆栈或许还会有私有内存。  
gdb提供了以下供调试多线程的进程的功能:  
*   自动通告新线程。  
*   \ "thread   THREADNO\ ",一个用来在线程之间切换的命令。  
*   \ "info   threads\ ",一个用来查询存线程的命令。  
*   \ "thread   apply   [THREADNO]   [ALL]   ARGS\ ",一个用来向线程提供命令的命令。  
*   线程有关的断点设置。  
注意:这些特性不是在所有gdb版本都能使用,归根结底要看操作系统是否支持。  
如果你的gdb不支持这些命令,会显示出错信息:  
(gdb)   info   threads  
(gdb)   thread   1  
Thread   ID   1   not   known.   Use   the   \ "info   threads\ "   command   to  
see   the   IDs   of   currently   known   threads.  
gdb的线程级调试功能允许你观察你程序运行中所有的线程,但无论什么时候  
gdb控制,总有一个“当前”线程。调试命令对“当前”进程起作用。  
一旦gdb发现了你程序中的一个新的线程,它会自动显示有关此线程的系统信  
息。比如:  
[New   process   35   thread   27]  
不过格式和操作系统有关。  
为了调试的目的,gdb自己设置线程号。  
`info   threads\ "  
显示进程中所有的线程的概要信息。gdb按顺序显示:  
1.线程号(gdb设置)  
2.目标系统的线程标识。  
3.此线程的当前堆栈。  
一前面打\ "*\ "的线程表示是当前线程。  
例如:  
(gdb)   info   threads  
3   process   35   thread   27   0x34e5   in   sigpause   ()  
2   process   35   thread   23   0x34e5   in   sigpause   ()  
*   1   process   35   thread   13   main   (argc=1,   argv=0x7ffffff8)  
at   threadtest.c:68  
`thread   THREADNO\ "  
把线程号为THREADNO的线程设为当前线程。命令行参数THREADNO是gdb内定的  
线程号。你可以用\ "info   threads\ "命令来查看gdb内设置的线程号。gdb显示该线程  
的系统定义的标识号和线程对应的堆栈。比如:  

(gdb)   thread   2  
[Switching   to   process   35   thread   23]  
0x34e5   in   sigpause   ()  
\ "Switching后的内容取决于你的操作系统对线程标识的定义。  

`thread   apply   [THREADNO]   [ALL]   ARGS\ "  
此命令让你对一个以上的线程发出相同的命令\ "ARGS\ ",[THREADNO]的含义同上。  
如果你要向你进程中的所有的线程发出命令使用[ALL]选项。  
无论gdb何时中断了你的程序(因为一个断点或是一个信号),它自动选择信号或  
断点发生的线程为当前线程。gdb将用一个格式为\ "[Switching   to   SYSTAG]\ "的消息  
来向你报告。  

打开linux调试宏开关生成core文件
1./etc/profile
ulimit   -S   -c   0
改为:  
ulimit   -c   unlimited  
2./etc/init.d/functions
ulimit   -S   -c   0
改为:  
ulimit   -c   unlimited  
打开后如出错既会出现Segmentation   fault   (core   dumped)
这时可   打开gdb调试
gdb   ./am   ./core.3426
以下调试方法与VC基本一致

- 作者: chongsoft 2007年11月14日, 星期三 15:39  回复(0) |  引用(0) 加入博采

使用 GDB 调试多进程程序 (zhuan)
 

级别: 中级

田 强 (tianq@cn.ibm.com), 软件工程师, IBM中国软件开发中心

2007 年 7 月 30 日

GDB 是 linux 系统上常用的调试工具,本文介绍了使用 GDB 调试多进程程序的几种方法,并对各种方法进行比较。

GDB 是 linux 系统上常用的 c/c++ 调试工具,功能十分强大。对于较为复杂的系统,比如多进程系统,如何使用 GDB 调试呢?考虑下面这个三进程系统:


进程
进程

Proc2 是 Proc1 的子进程,Proc3 又是 Proc2 的子进程。如何使用 GDB 调试 proc2 或者 proc3 呢?

实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实我们可以利用GDB的特点或者其他一些辅助手段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。

接下来我们详细介绍几种方法,分别是 follow-fork-mode 方法,attach 子进程方法和 GDB wrapper 方法。

follow-fork-mode

在2.5.60版Linux内核及以后,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。

follow-fork-mode的用法为:

set follow-fork-mode [parent|child]

  • parent: fork之后继续调试父进程,子进程不受影响。
  • child: fork之后调试子进程,父进程不受影响。

因此如果需要调试子进程,在启动gdb后:

(gdb) set follow-fork-mode child

并在子进程代码设置断点。

此外还有detach-on-fork参数,指示GDB在fork之后是否断开(detach)某个进程的调试,或者都交由GDB控制:

set detach-on-fork [on|off]

  • on: 断开调试follow-fork-mode指定的进程。
  • off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态。

注意,最好使用GDB 6.6或以上版本,如果你使用的是GDB6.4,就只有follow-fork-mode模式。

follow-fork-mode/detach-on-fork的使用还是比较简单的,但由于其系统内核/gdb版本限制,我们只能在符合要求的系统上才能使用。而且,由于follow-fork-mode的调试必然是从父进程开始的,对于fork多次,以至于出现孙进程或曾孙进程的系统,例如上图3进程系统,调试起来并不方便。

Attach子进程

众所周知,GDB有附着(attach)到正在运行的进程的功能,即attach <pid>命令。因此我们可以利用该命令attach到子进程然后进行调试。

例如我们要调试某个进程RIM_Oracle_Agent.9i,首先得到该进程的pid

[root@tivf09 tianq]# ps -ef|grep RIM_Oracle_Agent.9inobody    6722  6721  0 05:57 ?        00:00:00 RIM_Oracle_Agent.9iroot      7541 27816  0 06:10 pts/3    00:00:00 grep -i rim_oracle_agent.9i

通过pstree可以看到,这是一个三进程系统,oserv是RIM_Oracle_prog的父进程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父进程。

[root@tivf09 root]# pstree -H 6722


通过 pstree 察看进程
通过 pstree 察看进程

启动GDB,attach到该进程


用 GDB 连接进程
用 GDB 连接进程

现在就可以调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪里了。有没有办法解决呢?

一个办法是,在要调试的子进程初始代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码可以继续执行下去。

至于这段代码所采用的条件,看你的偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。以文件为例,其形式可以如下:

void debug_wait(char *tag_file){    while(1)    {        if (tag_file存在)            睡眠一段时间;        else            break;    }}

当attach到进程后,在该段代码之后设上断点,再把该文件删除就OK了。当然你也可以采用其他的条件或形式,只要这个条件可以设置/检测即可。

Attach进程方法还是很方便的,它能够应付各种各样复杂的进程系统,比如孙子/曾孙进程,比如守护进程(daemon process),唯一需要的就是加入一小段代码。

GDB wrapper

很多时候,父进程 fork 出子进程,子进程会紧接着调用 exec族函数来执行新的代码。对于这种情况,我们也可以使用gdb wrapper 方法。它的优点是不用添加额外代码。

其基本原理是以gdb调用待执行代码作为一个新的整体来被exec函数执行,使得待执行代码始终处于gdb的控制中,这样我们自然能够调试该子进程代码。

还是上面那个例子,RIM_Oracle_prog fork出子进程后将紧接着执行RIM_Oracle_Agent.9i的二进制代码文件。我们将该文件重命名为RIM_Oracle_Agent.9i.binary,并新建一个名为RIM_Oracle_Agent.9i的shell脚本文件,其内容如下:

[root@tivf09 bin]# mv RIM_Oracle_Agent.9i RIM_Oracle_Agent.9i.binary[root@tivf09 bin]# cat RIM_Oracle_Agent.9i#!/bin/shgdb RIM_Oracle_Agent.binary

当fork的子进程执行名为RIM_Oracle_Agent.9i的文件时,gdb会被首先启动,使得要调试的代码处于gdb控制之下。

新的问题来了。子进程是在gdb的控制下了,但还是不能调试:如何与gdb交互呢?我们必须以某种方式启动gdb,以便能在某个窗口/终端与gdb交互。具体来说,可以使用xterm生成这个窗口。

xterm是X window系统下的模拟终端程序。比如我们在Linux桌面环境GNOME中敲入xterm命令:


xterm
xterm

就会跳出一个终端窗口:


终端
终端

如果你是在一台远程linux服务器上调试,那么可以使用VNC(Virtual Network Computing) viewer从本地机器连接到服务器上使用xterm。在此之前,需要在你的本地机器上安装VNC viewer,在服务器上安装并启动VNC server。大多数linux发行版都预装了vnc-server软件包,所以我们可以直接运行vncserver命令。注意,第一次运行vncserver时会提示输入密码,用作VNC viewer从客户端连接时的密码。可以在VNC server机器上使用vncpasswd命令修改密码。

[root@tivf09 root]# vncserver New 'tivf09:1 (root)' desktop is tivf09:1Starting applications specified in /root/.vnc/xstartupLog file is /root/.vnc/tivf09:1.log[root@tivf09 root]#[root@tivf09 root]# ps -ef|grep -i vncroot     19609     1  0 Jun05 ?        00:08:46 Xvnc :1 -desktop tivf09:1 (root)   -httpd /usr/share/vnc/classes -auth /root/.Xauthority -geometry 1024x768   -depth 16 -rfbwait 30000 -rfbauth /root/.vnc/passwd -rfbport 5901 -pnroot     19627     1  0 Jun05 ?        00:00:00 vncconfig -iconicroot     12714 10599  0 01:23 pts/0    00:00:00 grep -i vnc[root@tivf09 root]#

Vncserver是一个Perl脚本,用来启动Xvnc(X VNC server)。X client应用,比如xterm,VNC viewer都是和它通信的。如上所示,我们可以使用的DISPLAY值为tivf09:1。现在就可以从本地机器使用VNC viewer连接过去:


VNC viewer:输入服务器
VNC viewer:输入服务器

输入密码:


VNC viewer:输入密码
VNC viewer:输入密码

登录成功,界面和服务器本地桌面上一样:


VNC viewer
VNC viewer

下面我们来修改RIM_Oracle_Agent.9i脚本,使它看起来像下面这样:

#!/bin/shexport DISPLAY=tivf09:1.0; xterm -e gdb RIM_Oracle_Agent.binary

如果你的程序在exec的时候还传入了参数,可以改成:

#!/bin/shexport DISPLAY=tivf09:1.0; xterm -e gdb --args RIM_Oracle_Agent.binary $@ 

最后加上执行权限

[root@tivf09 bin]# chmod 755 RIM_Oracle_Agent.9i

现在就可以调试了。运行启动子进程的程序:

[root@tivf09 root]# wrimtest -l 9i_linuxResource Type  : RIMResource Label : 9i_linuxHost Name      : tivf09User Name      : mdstatusVendor         : OracleDatabase       : rimDatabase Home  : /data/oracle9i/920Server ID      : rimInstance Home  : Instance Name  : Opening Regular Session...

程序停住了。从VNC viewer中可以看到,一个新的gdb xterm窗口在服务器端打开了


gdb xterm 窗口
gdb xterm窗口

[root@tivf09 root]# ps -ef|grep gdbnobody   24312 24311  0 04:30 ?        00:00:00 xterm -e gdb RIM_Oracle_Agent.binarynobody   24314 24312  0 04:30 pts/2    00:00:00 gdb RIM_Oracle_Agent.binaryroot     24326 10599  0 04:30 pts/0    00:00:00 grep gdb

运行的正是要调试的程序。设置好断点,开始调试吧!

注意,下面的错误一般是权限的问题,使用 xhost 命令来修改权限:


xterm 错误
xterm 错误

[oot@tivf09 bin]# export DISPLAY=tivf09:1.0[root@tivf09 bin]# xhost +access control disabled, clients can connect from any host

xhost + 禁止了访问控制,从任何机器都可以连接过来。考虑到安全问题,你也可以使用xhost + <你的机器名>。

小结

上述三种方法各有特点和优劣,因此适应于不同的场合和环境:

  • follow-fork-mode方法:方便易用,对系统内核和GDB版本有限制,适合于较为简单的多进程系统
  • attach子进程方法:灵活强大,但需要添加额外代码,适合于各种复杂情况,特别是守护进程
  • GDB wrapper方法:专用于fork+exec模式,不用添加额外代码,但需要X环境支持(xterm/VNC)。


参考资料

- 作者: chongsoft 2007年09月17日, 星期一 14:51  回复(0) |  引用(0) 加入博采

转一篇关于arm mmu的文章,很有启发

浅析armlinux-setup_arch()->create_mapping()函数5-2-2

文章来源:http:
//gliethttp.cublog.cn

建议首先参考《浅析armlinux2_4_19启动程序[head-armv.s文件][http://gliethttp.cublog.cn]
//----------------------------------------
//1.arch/arm/mm/Mm-armv.c->create_mapping()
static void __init create_mapping(struct map_desc *md)
{
    unsigned long virt, length;
    int prot_sect, prot_pte;
    long off;
    if (md->prot_read && md->prot_write &&
     !md->cacheable && !md->bufferable) {
//提示设置不合理
        printk(KERN_WARNING "Security risk: creating user "
         "accessible mapping for 0x%08lx at 0x%08lx\n",
         md->physical, md->virtual);
    }
    if (md->virtual != vectors_base() && md->virtual < PAGE_OFFSET) {
//仅仅能创建0xffff0000开始的用于中断向量的虚拟地址映射页目录
//请参见《浅析arm-linux中断vector向量表的建立流程》
        printk(KERN_WARNING "MM: not creating mapping for "
         "0x%08lx at 0x%08lx in user region\n",
         md->physical, md->virtual);
    }
//页表属性值
    prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
         (md->prot_read ? L_PTE_USER : 0) |
         (md->prot_write ? L_PTE_WRITE : 0) |
         (md->cacheable ? L_PTE_CACHEABLE : 0) |
         (md->bufferable ? L_PTE_BUFFERABLE : 0);
//页目录属性值
    prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md->domain) |
         (md->prot_read ? PMD_SECT_AP_READ : 0) |
         (md->prot_write ? PMD_SECT_AP_WRITE : 0) |
         (md->cacheable ? PMD_SECT_CACHEABLE : 0) |
         (md->bufferable ? PMD_SECT_BUFFERABLE : 0);
    virt = md->virtual;
    off = md->physical - virt;
    length = md->length;
    while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {
//如果虚拟地址virt或者其对应的物理地址(virt + off),只要有一个非1M页目录对齐,
//那么首先调整成1M页目录对齐
//将virt和其对应的物理地址(virt + off)建立映射,如果没有设置页目录,那么alloc_init_page
//将自动设置
        alloc_init_page(virt, virt + off, md->domain, prot_pte);
        virt += PAGE_SIZE;
        length -= PAGE_SIZE;
    }
    while (length >= PGDIR_SIZE) {
//还有至少1M的空间需要映射,那么可以使用alloc_init_section进行快速1M大小的节映射
        alloc_init_section(virt, virt + off, prot_sect);
        virt += PGDIR_SIZE;
        length -= PGDIR_SIZE;
    }
    while (length >= PAGE_SIZE) {
//经过1M节映射之后,仍然还有一些余项,那么继续完成4k页表映射[gliethttp]
        alloc_init_page(virt, virt + off, md->domain, prot_pte);
        virt += PAGE_SIZE;
        length -= PAGE_SIZE;
    }
}
//----------------------------------------
//2.rch/arm/mm/Mm-armv.c->alloc_init_page
static inline void
alloc_init_page(unsigned long virt, unsigned long phys, int domain, int prot)
{
    pmd_t *pmdp;
    pte_t *ptep;
//获取中间级目录入口,对于2级小页映射,pmdp就等于一级页目录入口,即:pmdp = &swapper_pg_dir[i]
    pmdp = pmd_offset(pgd_offset_k(virt), virt);
//对于虚拟映射的原理问题,请参考《linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析》
    if (pmd_none(*pmdp)) {
//如果1级页目录dir,是空的,那么创建之
//其中PTRS_PER_PTE=256项二级页表项
//所以首先申请2*256*4=2k空间,之所以要多申请出1K空间,是因为需要存储相应页表项对应的kernel信息
//详细说明见下面的分析.[gliethttp]
        pte_t *ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));
        ptep += PTRS_PER_PTE;//ptep指向页表项存储区的末端
//将swapper_pg_dir[i]=__mk_pmd(ptep, PMD_TYPE_TABLE | PMD_DOMAIN(domain)),
//一级页目录项,装入数值__mk_pmd(ptep, PMD_TYPE_TABLE| PMD_DOMAIN(domain))
        set_pmd(pmdp, __mk_pmd(ptep, PMD_TYPE_TABLE | PMD_DOMAIN(domain)));
    }
    ptep = pte_offset(pmdp, virt);//找到virt虚拟地址对应的pte页表项入口
    //ptep指向第256+(pte_t*)ptr项,即:1k~2k空间
    set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, __pgprot(prot)));
}
static inline pmd_t __mk_pmd(pte_t *ptep, unsigned long prot)
{
    unsigned long pte_ptr = (unsigned long)ptep;
    pmd_t pmd;
    pte_ptr -= PTRS_PER_PTE * sizeof(void *);//之前的pte_ptr指向的是页表项存储区的末端
    pmd_val(pmd) = __virt_to_phys(pte_ptr) | prot;
    return pmd;
}
//include/asm-arm/Pgtable.h有如下定义
#define pte_offset(dir, addr) ((pte_t *)pmd_page(*(dir)) + __pte_offset(addr))
#define __pte_offset(addr) (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)
)
#define pfn_pte(pfn,prot) (__pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot)))
//include/asm-arm/proc-armv/Pgtable.h有如下定义
static inline unsigned long pmd_page(pmd_t pmd)
{
    unsigned long ptr;
    ptr = pmd_val(pmd) & ~(PTRS_PER_PTE * sizeof(void *) - 1);
    ptr += PTRS_PER_PTE * sizeof(void *);//指向第256+(pte_t*)ptr项,即:1k~2k空间[gliethttp]
    return __phys_to_virt(ptr);
}
//include/asm-arm/proc-armv/Pgtable.h有如下定义
#define set_pte(pmdp,pmd) cpu_set_pte(ptep,pte)
//include/asm-arm/cpu-single.h中有如下定义
#define cpu_set_pte cpu_fn(CPU_NAME,_set_pte)
翻译之后
#define cpu_set_pte cpu_arm920_set_pte
相应的
#define cpu_set_pgd cpu_arm920_set_pgd
#define cpu_set_pmd cpu_arm920_set_pmd
#define cpu_set_pte cpu_arm920_set_pte
以上三个函数位于arch/arm/mm/proc-arm920.S
    .align    5
ENTRY(cpu_arm920_set_pte)
    str    r1, [r0], #-1024//2*1k空间,0~1k被使用,且r0处于1k~2k之间,那么r0=r0-1k,就位于0~1k空间
    eor    r1, r1, #LPTE_PRESENT | LPTE_YOUNG | LPTE_WRITE | LPTE_DIRTY//异或
    bic    r2, r1, #0xff0
    bic    r2, r2, #3
    orr    r2, r2, #HPTE_TYPE_SMALL
    tst    r1, #LPTE_USER | LPTE_EXEC @ User or Exec?
    orrne  r2, r2, #HPTE_AP_READ
    tst    r1, #LPTE_WRITE | LPTE_DIRTY @ Write and Dirty?
    orreq  r2, r2, #HPTE_AP_WRITE
    tst    r1, #LPTE_PRESENT | LPTE_YOUNG @ Present and Young?
    movne  r2, #0
    str    r2, [r0]//对该pte在0~1k相应的页表项赋值,我想0~1k空间是有kernel使用,和硬件mmu无关
    mov    r0, r0
    mcr    p15, 0, r0, c7, c10, 1         @ clean D entry
    mcr    p15, 0, r0, c7, c10, 4         @ drain WB
    mov    pc, lr
//所以可以看出《linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析》中的pte是
//一个示意图,并不是实际的物理图,实际的物理图应该如下:
       |->pte-256->存储pte+000对应的kernel信息
       |->pte-255->存储pte+001对应的kernel信息
       ...
       |->pte-002->存储pte+254对应的kernel信息
       |->pte-001->存储pte+255对应的kernel信息
dir+0000->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k


       |->pte-256->存储pte+000对应的kernel信息
       |->pte-255->存储pte+001对应的kernel信息
       ...
       |->pte-002->存储pte+254对应的kernel信息
       |->pte-001->存储pte+255对应的kernel信息
dir+0001->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k

       ...


       |->pte-256->存储pte+000对应的kernel信息
       |->pte-255->存储pte+001对应的kernel信息
       ...
       |->pte-002->存储pte+254对应的kernel信息
       |->pte-001->存储pte+255对应的kernel信息
dir+4094->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k

       |->pte-256->存储pte+000对应的kernel信息
       |->pte-255->存储pte+001对应的kernel信息
       ...
       |->pte-002->存储pte+254对应的kernel信息
       |->pte-001->存储pte+255对应的kernel信息
dir+4095->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k

- 作者: chongsoft 2007年09月6日, 星期四 17:39  回复(0) |  引用(0) 加入博采

C语言运算符优先级顺口溜
C语言运算符优先级顺口溜
2007-07-16 19:26


醋坛酸味灌
味落跳福豆

共44个运算符

醋-初等,4个: ( ) [ ] -> 指向结构体成员 . 结构体成员
坛-单目,9个: ! ~ ++ -- -负号 (类型) *指针 &取地址 sizeof长度
酸-算术,5个: * / % + -减
味-位移,2个: << >>
灌-关系,6个: < <= > >= == 等于 != 不等于
味-位逻,3个: & 按位与 ^ 按位异或 | 按位或
落-逻辑,2个: && 逻辑与 || 逻辑或
跳-条件,1个,三目: ? :
福-赋值,11个: = += -= *= /= %= >>= <<= &= ^= |=
豆-逗号,1个: ,

结合方向自右向左的只有三类:赋值、单目和三目
同一优先级的运算顺序由结合方向决定

不过我觉得没有必要去记这个,不清楚的用括号就行了。

- 作者: chongsoft 2007年08月28日, 星期二 16:42  回复(0) |  引用(0) 加入博采

核心态页映射到用户空间实验

以下代码展示了如何将核心态内存映射到用户空间。并对常见的错误加以解释。

/*allocate mapping page in kernel space and write the code*/
void *uft_kernel_alloc_mapping_page()
{
    void *map_page;
    int cnt = 0;
   
    map_page = vmalloc(PAGE_SIZE);
    MV_KINFO_SHOW("vmalloc OK, addr = 0x%lx\n",map_page);

    /*sample: we write 'a' in the page*/
    for(cnt = 0;cnt < PAGE_SIZE;cnt++)
    {
        *(char *)((unsigned long)map_page + cnt) = 'a';
    }
   
    return map_page;
}

/*have the physical address of the page*/
void *uft_get_mapping_page_phys(unsigned long *prot)
{
    unsigned long map_page;
    unsigned long phys_addr = 0;
    pgd_t *pgd;
    pmd_t *pmd;
    pte_t *pte;   
   
    map_page = (unsigned long)uft_kernel_alloc_mapping_page();

    if(map_page == 0)
    {
        return NULL;
    }

    /*accessing page tables*/
    pgd = pgd_offset_k(map_page);

    /*
     * The "pgd_xxx()" functions here are trivial for a folded two-level
     * setup: the pgd is never bad, and a pmd always exists (as it's folded
     * into the pgd entry)
     */
    if(!pgd_none(*pgd))
    {
        /*pgd is OK*/
        pmd = pmd_offset(pgd,map_page);
        if(!pmd_none(*pmd))
        {
            pte = pte_offset(pmd,map_page);           
            if(pte_present(*pte))
            {
                /*pte is now valid and in memory*/
                //phys_addr = pte_pfn(*pte);
                phys_addr =  pte_val(*pte) >> PAGE_SHIFT;
            }
        } 
    }  /*end if(!pgd_none(*pgd))*/

    if(!phys_addr)
    {
        MV_KINFO_POS("map page physical address error!\n");
        return NULL;
    }

    /*Now we have got pfn, NOTE that pfn is page frame number*/
    phys_addr <<= PAGE_SHIFT;
    MV_KINFO_SHOW("map page physical address is 0x%lx\n",phys_addr);

    /*protect the page from writing and make it executable*/
    //pte_mkexec(*pte);
    //pte_wrprotect(*pte);

    *prot = pte_val(*pte) & (PAGE_SIZE - 1);
    MV_KINFO_SHOW("prot = 0x%lx",pte_val(*pte));

    //ok
    return (void *)phys_addr;
}

#define UFT_MAPPED_BASE  (0x90000000UL)

/*remap the page to user space*/
void uft_remap_mapping_page()
{
    unsigned long phys_addr;
    unsigned long vma_addr;
    unsigned long size;
    unsigned long prot_val;
    pgprot_t prot;
    struct page *page;

    struct mm_struct *mm;
    struct vm_area_struct * vma;
    struct vm_area_struct * vma_new;


    /*we fetch this physical address and remap it to process user space*/
    phys_addr = (unsigned long)uft_get_mapping_page_phys(&prot_val);

    if(phys_addr == 0)
    {
        return;
    }
   
    /*find a suitable vma*/
    vma_addr = PAGE_ALIGN(UFT_MAPPED_BASE);
    size = PAGE_SIZE;
    mm = current->mm;

    /* Look up the first VMA which satisfies  vma_addr < vm_end,  NULL if none. */
    vma = find_vma(mm,vma_addr); 

    if(vma)
    {
        MV_KINFO_SHOW("find_vma vma zone [0x%lx, 0x%lx) \n",vma->vm_start,vma->vm_end);
    }
   
    while(vma)
    {    
        if(vma_addr + size <= vma->vm_start)
        {
            /*right region found*/
            goto region_found;
        }
        else
        {       
            vma_addr = vma->vm_end;
            /*move next*/
            vma = vma->vm_next;
        }
    }
   
    MV_KINFO_POS("find_vma failed, vma_addr = 0x%lx\n",vma_addr);
    return;

region_found:

    MV_KINFO_SHOW("good region is [0x%lx,0x%lx) \n",vma_addr,vma_add + size);


    /*insert new vma to process user space*/
    //void insert_vm_struct(struct mm_struct * mm, struct vm_area_struct * vma)
    vma_new = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
    memset(vma_new,0,sizeof(struct vm_area_struct));
   
    if (!vma_new)
    {
        MV_KINFO_POS("allocate vma_new failed!\n");
    }

    prot.pgprot = _L_PTE_DEFAULT | _L_PTE_READ | L_PTE_WRITE | L_PTE_DIRTY;
    vma_new->vm_mm = current->mm;
    vma_new->vm_start = vma_addr;
    vma_new->vm_end = vma_addr + size;
    vma_new->vm_pgoff = 0;
    vma_new->vm_page_prot = prot;
    vma_new->vm_flags = VM_READ | VM_WRITE; //| VM_EXEC; //VM_SHARED?;

    /*should set to VM_RESERVED, prevent vma from swapping out*/
    vma_new->vm_flags |= VM_RESERVED;
   
    vma_new->vm_file  = NULL;

    insert_vm_struct(mm,vma_new);

    MV_KINFO_SHOW("insert ok!\n");

    //page = pfn_to_page(phys_addr >> PAGE_SHIFT);
    page = mem_map + (phys_addr >> PAGE_SHIFT) - (PHYS_OFFSET >> PAGE_SHIFT);
   
    /*MUST set page a reserved page, mem_map_reserve(p)*/
    set_bit(PG_reserved, &((page)->flags));

    /*remap real page*/
    /*  Note: this is only safe if the mm semaphore is held when called. */
    //int remap_page_range(unsigned long from, phys_addr_t phys_addr, unsigned long size, pgprot_t prot)

    if(remap_page_range(vma_addr,phys_addr,size,vma_new->vm_page_prot))
    {
        MV_KINFO_POS("remap not OK!\n");
    }

    MV_KINFO_SHOW("remap OK!\n");

    return;  
}

实验结果:

做以下几个实验:
1. zero page 实验, only insert vma and read,not remap to user space
2. mem_map_reserve(p), and see if can visit, it must suceed I think.
3. vm_flags set to VM_RESERVED

2 成功,所以必须标记为PG_reserved
3 成功

1. 映射到0页,如果设定vma_new->vm_flags没有VM_READ,则crash在user space. 下面的一部分会说明原因

static inline int handle_pte_fault(struct mm_struct *mm,
        struct vm_area_struct * vma, unsigned long address,
        int write_access, pte_t * pte)

{
        pte_t entry;

        entry = *pte;
        if (!pte_present(entry))
        {
                /*
                 * If it truly wasn't present, we know that kswapd
                 * and the PTE updates will not touch it later. So
                 * drop the lock.
                 */            
                if(address == 0x90000000)
                {      
                    printk("test: pte_fault, pte not present, pte_none = %d, write_access = %d\n",pte_none(entry),write_access);
                                       
                    printk("test: pte_val = 0x%x\n",pte_val(entry));
                }
                                               
                if (pte_none(entry))
                        return do_no_page(mm, vma, address, write_access, pte);
                return do_swap_page(mm, vma, address, pte, entry, write_access);
        }

 。。。。。。
 }

do_no_page calls do_anonymous_page

static int do_no_page(struct mm_struct * mm, struct vm_area_struct * vma,
        unsigned long address, int write_access, pte_t *page_table)
{
        struct page * new_page;
        pte_t entry;

         /*call do_anonymous page*/
         if(address == 0x90000000)
         {
               printk("call do_anonymouns_page first ...");
         }

        if (!vma->vm_ops || !vma->vm_ops->nopage)
        {
                /*call do_anonymous page*/
                if(address == 0x90000000)
                {
                        printk("call do_anonymouns_page ...");
                }

                return do_anonymous_page(mm, vma, page_table, write_access, address);
        }
 。。。
 }


/*
 * We are called with the MM semaphore and page_table_lock
 * spinlock held to protect against concurrent faults in
 * multithreaded programs.
 */
static int do_anonymous_page(struct mm_struct * mm, struct vm_area_struct * vma, pte_t *page_table, int write_access, unsigned long
addr)
{
        pte_t entry;

        if(addr == 0x90000000)
        {
 &nbp;              /* Read-only mapping of ZERO_PAGE. */
                printk("test: mapping 0 page, write_access = %d\n",write_access);
        }

        entry = pte_wrprotect(mk_pte(ZERO_PAGE(addr), vma->vm_page_prot));

        /* ..except if it's a write access */
        if (write_access) {
                struct page *page;

                /* Allocate our own private page. */
                spin_unlock(&mm->page_table_lock);

                page = alloc_page(GFP_HIGHUSER);
                if(!page)
                {
                        printk("write access, allocate page error!\n");
                }

                if (!page)
                        goto no_mem;
                clear_user_highpage(page, addr);

                spin_lock(&mm->page_table_lock);
                if (!pte_none(*page_table)) {
                        page_cache_release(page);
                        spin_unlock(&mm->page_table_lock);
                        return 1;
                }
                mm->rss++;
                flush_page_to_ram(page);
                entry = pte_mkwrite(pte_mkdirty(mk_pte(page, vma->vm_page_prot)));
                if(addr == 0x90000000)
                {
                        printk("test: do_anonymous_page, pte is makeing write and dirty!\n");
                }

                lru_cache_add(page);
                mark_page_accessed(page);
        }

        set_pte(page_table, entry);

        /* No need to invalidate - it was non-present before */
        update_mmu_cache(vma, addr, entry);
        spin_unlock(&mm->page_table_lock);
        return 1;       /* Minor fault */

no_mem:
        return -1;
}

test: pte_fault, pte not present, pte_none = 1, write_access = 0
test: pte_val = 0x0
call do_anonymouns_page first ...
test: mapping 0 page, write_access = 0

--------------------------------------------------------------------------------------------------------
/*
 * On 32-bit processors, we define "mode" to be zero when reading,
 * non-zero when writing.  This now ties up nicely with the polarity
 * of the 26-bit machines, and also means that we avoid the horrible
 * gcc code for "int val = !other_val;".
 */
#define DO_COW(m)  (m)
#define READ_FAULT(m)  (!(m))

注意:如果需要写的时候,如果你不将要写得页设置为L_PTE_DIRTY,那么会进入异常,异常中会设置为dirty,然后
返回,进行写操作

static inline int handle_pte_fault(struct mm_struct *mm,
        struct vm_area_struct * vma, unsigned long address,
        int write_access, pte_t * pte)
{
        pte_t entry;

        entry = *pte;
        if (!pte_present(entry))
        {
                /*
                 * If it truly wasn't present, we know that kswapd
                 * and the PTE updates will not touch it later. So
                 * drop the lock.
                 */
                if(address == 0x90000000)
                {
                    printk("test: pte_fault, pte not present, write_access = %d\n",write_access);
                }

                if (pte_none(entry))
                        return do_no_page(mm, vma, address, write_access, pte);
                return do_swap_page(mm, vma, address, pte, entry, write_access);
        }

        if (write_access)
        {
                if(address == 0x90000000)
                {
                    printk("test: pte_fault, pte_write is %d write_access = %d\n",pte_write(entry),write_access);
                }

                /*#define pte_write(pte)        (pte_val(pte) & L_PTE_WRITE)*/
  /*处理进程fork时的写时拷贝*/
                if (!pte_write(entry))
                        return do_wp_page(mm, vma, address, pte, entry);
  
  /*make dirty*/
                entry = pte_mkdirty(entry);
        }

 /*make young*/
        entry = pte_mkyoung(entry);
        establish_pte(vma, address, pte, entry);
        spin_unlock(&mm->page_table_lock);
        return 1;
}

结果:
test:error_code = -1
test: pte_fault, pte_write is 32, write_access = -1

btw, 如果发生写异常,我们不设置vm_flags为VM_WRITE,会发生crash.

static int
__do_page_fault(struct mm_struct *mm, unsigned long addr, int error_code,
  struct task_struct *tsk)
{
    ...
     /*
  * Ok, we have a good vm_area for this
  * memory access, so we can handle it.
  */
good_area:
 if (READ_FAULT(error_code)) /* read? */
  mask = VM_READ|VM_EXEC;
 else
  mask = VM_WRITE;

 fault = -1; /* bad access type */
 if (!(vma->vm_flags & mask))
  goto out;
  
}

--------------------------------------------------------------------------------------------------------
vm_flags是一个更高一级的保护标志,一般访问内存的时候不会去理睬,作用是虚存管理单位标志。但是异常的时候
do_pte_fault会用到,进行具体处理。

00008000-00009000 r-xp 00000000 00:0f 329404     /tmp/a/test
00010000-00011000 rw-p 00000000 00:0f 329404     /tmp/a/test
40000000-40014000 r-xp 00000000 61:02 1722072    /lib/ld-linux.so.2
40014000-40015000 rw-p 00000000 00:00 0
4001b000-4001d000 rw-p 00013000 61:02 1722072    /lib/ld-linux.so.2
4001d000-40133000 r-xp 00000000 61:02 1800588    /lib/libc.so.6
40133000-40135000 ---p 00116000 61:02 1800588    /lib/libc.so.6
40135000-4013f000 rw-p 00110000 61:02 1800588    /lib/libc.so.6
4013f000-40142000 rw-p 00000000 00:00 0
90000000-90001000 rw-p 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0

--------------------------------------------------------------------------------------------------------
附加实验 验证一下high_memory和num_phypages得值,可使用skyeye
打印一下meminfo的信息 find_memend_and_nodes(struct meminfo *mi, struct node_info *np)
然后观察meminfo的初始化过程

high_memory = 0xc4000000  __pa(high_memory) = high_memory - PAGE_OFFSET + PHYS_OFFSET;
0xc4000000 - 0xc0000000U + 0x30000000UL = 0x34000000UL

num_physpages = 0x4000  //16384K  64M
high_memory = (void *)__va(meminfo.end);
max_mapnr   = virt_to_page(high_memory) - mem_map;

看来这三者是一致的,meminfo.end = 0x34000000UL, 是物理内存的末端。

这是对实际的meminfo结构test的结果:
test: start = 0x30000000, end = 0x34000000, size = 0x4000000
test: node = 0
test: meminfo.end = 0x34000000
test: nr_banks = 1
On node 0 totalpages: 16384
zone(0): 16384 pages.
zone(1): 0 pages.
zone(2): 0 pages.

- 作者: chongsoft 2007年07月17日, 星期二 12:53  回复(0) |  引用(0) 加入博采

DPM in Linux Kernel

一. DPM 概念 Dynamic Power Management

二. Basic Concept of DPM

<include/linux/dpm.h>

操作点:
/* internal representation of an operating point */
struct dpm_opt {
 char   *name;          /* name */
 struct list_head list;  /* all installed op points */
 dpm_md_pp_t             pp[DPM_PP_NBR]; /* initialization params */
 struct dpm_md_opt md_opt;         /* machine dependent part */
 int   constrained; /* is this opt constrained? */
 struct dpm_stats        stats;          /* statistics */
};

/* Instances of this structure define valid Innovator operating points for DPM.
   Voltages are represented in mV, and frequencies are represented in KHz. */

struct dpm_md_opt {
  unsigned int v;         /* Target voltage in mV */
 unsigned int dpll; /* in KHz */
 unsigned int cpu; /* CPU frequency in KHz */
 unsigned int tc; /* in KHz */
 unsigned int per; /* in KHz */
 unsigned int dsp; /* in KHz */
 unsigned int dspmmu; /* in KHz */
 unsigned int lcd; /* in KHz */
 struct dpm_regs regs;   /* Register values */
};

dpm操做点结构,里面包含了大约16个参数dpm_md_pp_t  pp[DPM_PP_NBR];
dpm_md_pp_t是个int型

类和策略:
* internal representation of a class of op points (to be mapped to an
 * operating state */
struct dpm_class {
 char   *name;          /* name */
 struct list_head list;  /* all installed classes */
 unsigned  nops;  /* nbr ops in this class */
 struct dpm_opt  **ops;  /* the ops in this class */
 struct dpm_opt  *opt;  /* the selected op point */
 struct dpm_stats        stats;          /* statistics */
};


/* internal representation of an installed power policy */
struct dpm_policy {
 char   *name;          /* name */
 struct list_head list;  /* all installed policies */
 struct dpm_class *classes[DPM_STATES]; /* the classes */
 struct dpm_stats        stats;          /* statistics */
};


可以看到操作点,类和策略在系统中以链的形式维护
/* curently installed policies, classes and operating points */
extern struct list_head  dpm_policies;
extern struct list_head  dpm_classes;
extern struct list_head  dpm_opts;

三. Interfaces of DPM

3.1  User Space Interface

<include/linux/dpm.h>
/* single system call to invoke all functions. the "params" argument can be
 * NULL for DPM_INIT, DPM_TERMINATE, DPM_DISABLE, and DPM_ENABLE */
 
#if defined(__KERNEL__)
extern int sys_dpm(int func, struct dpm_param *params);
#else
#define sys_dpm(FUNC, PARAMS) \
 syscall(__NR_sys_dpm, FUNC, PARAMS)
#endif

/* function names for sys_dpm system call interface */
typedef enum {
 DPM_INIT,  /* initialize the DPM */
 DPM_TERMINATE,  /* terminate the DPM */
 DPM_DISABLE,  /* temporarily disable the DPM */
 DPM_ENABLE,  /* re-enable the disabled DPM */
 DPM_CREATE_OPT,  /* create an operating point */
 DPM_CREATE_CLASS, /* create a class of operating points */
 DPM_CREATE_POLICY, /* create a policy */
 DPM_DESTROY_POLICY, /* destroy a policy */
 DPM_SET_POLICY,  /* set the active policy */
 DPM_GET_POLICY,  /* get the name of the active policy */
 DPM_GET_ALL_POLICIES, /* get the names of all active policies */
 DPM_GET_CLASSES, /* get the names of all active policy */
 DPM_SET_TASK_STATE, /* set a task-specific operating state */
 DPM_GET_TASK_STATE, /* get a task's task-specific oper state */
 /* get statistics */
 DPM_GET_POLICY_STATS,
 DPM_GET_CLASS_STATS,
 DPM_GET_OPT_STATS,
 DPM_GET_OS_STATS,
 /* debug */
 DPM_DISPLAY_POLICY,
 DPM_SET_STATE,  /* set a (non-scheduling-based) op state */
} dpm_func_t;

asmlinkage int sys_dpm(int func, struct dpm_param *params);

3.2  Interface for Drivers

然后看看dpm为内核和用户态应用程序提供的接口:
<include/linux/dpm.h>
/*
 * kernel's operating state interface
 */

/* set operating state */
EXPORT_SYMBOL(dpm_set_os);
void dpm_set_os(dpm_state_t state);

EXPORT_SYMBOL(dpm_init);
EXPORT_SYMBOL(dpm_terminate);
EXPORT_SYMBOL(dpm_disable);
EXPORT_SYMBOL(dpm_enable);
EXPORT_SYMBOL(dpm_create_opt);
EXPORT_SYMBOL(dpm_create_class);
EXPORT_SYMBOL(dpm_create_policy);
EXPORT_SYMBOL(dpm_destroy_policy);
EXPORT_SYMBOL(dpm_set_policy);
/* ?? Needed in kernel mode ?? EXPORT_SYMBOL(dpm_get_policy);*/
EXPORT_SYMBOL(dpm_set_task_state);
EXPORT_SYMBOL(dpm_get_task_state);

/*****************************************************************************
 * Suspend/Resume DPM
 * The current operating point is saved and restored. This
 * interface is designed to be used by system suspend/resume code, to safely
 * save/restore the DPM operating point across a system power-down, where the
 * firmware may resume the system at a random operating point.  This does not
 * require DPM to be enabled. Note that DPM remains locked across the
 * suspend/resume.
 *****************************************************************************/
 int dpm_suspend(void)
 int dpm_resume(void)

3.3  proc fs interface

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * /proc/driver/dpm/cmd (Write-Only)
 *
 * Writing a string to this file is equivalent to issuing a DPM command.
 * Currently only one command per "write" is allowed, and there is a maximum on
 * the number of tokens that will be accepted (PAGE_SIZE / sizeof(char *)).
 * DPM can be initialized by a linewise copy of a configuration file to this
 * /proc file.
 *
 * DPM Control
 * -----------
 *
 * init          : dpm_init()
 * enable        : dpm_enable()
 * disable       : dpm_disable()
 * terminate     : dpm_terminate()
 *
 * Policy Control
 * --------------
 *
 * set_policy <policy>          : Set the policy by name
 * set_task_state <pid> <state> : Set the task state for a given pid, 0 = self
 *
 * Policy Creation
 * ---------------
 *
 * create_opt <name> <pp0> ... <ppn>
 *     Create a named operating point from DPM_PP_NBR paramaters.  All
 *     parameters must be  given. Parameter order and meaning are machine
 *     dependent.
 *
 * create_class <name> <opt0> [ ... <optn> ]
 *     Create a named class from 1 or more named operating points.  All
 *     operating points must be defined before the call.
 *
 * create_policy <name> <class0> ... <classn>
 *     Create a named policy from DPM_STATES class names.  All classes must be
 *     defined before the call.  The order is machine dependent.
 *
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

四. DPM states

那么dpm的状态如何定义的呢?

 machine dependent operating state

 An operating state is a cpu execution state that (has implications for)(有关,牵涉) power
 management. The DPM will select operating points (操作点)based largely on the
 current operating state.
 
 *
 * DPM_STATES is the number of supported operating states. Valid operating
 * states are from 0 to DPM_STATES-1 but when setting an operating state the
 * kernel should only specify a state from the set of "base states" and should
 * do so by name.  During the context switch the new operating state is simply
 * extracted from current->dpm_state.
 *
 * task states:
 *
 * APIs that reference task states use the range -(DPM_TASK_STATE_LIMIT + 1)
 * through +DPM_TASK_STATE_LIMIT.  This value is added to DPM_TASK_STATE to
 * obtain the downward or upward adjusted task state value. The
 * -(DPM_TASK_STATE_LIMIT + 1) value is interpreted specially, and equates to
 * DPM_NO_STATE.
 *
 * Tasks inherit their task operating states across calls to
 * fork(). DPM_TASK_STATE is the default operating state for all tasks, and is
 * inherited from init.  Tasks can change (or have changed) their tasks states
 * using the DPM_SET_TASK_STATE variant of the sys_dpm() system call.  */


<include/asm-arm/arch-omap24xx/omap2420_dpm.h>

#define DPM_NO_STATE        -1 , 这个状态是不受电源管理的状态,有些任务根本不受dpm的托管。

以下三个是dpm的基本状态
/*states below are dpm base states, dm基本状态*/
#define DPM_RELOCK_STATE     0  //??

#define DPM_IDLE_TASK_STATE  1
#define DPM_IDLE_STATE       2
#define DPM_SLEEP_STATE      3


#define DPM_BASE_STATES      4  , 定义了dpm的基本状态的数量,只有四种。


以下几个定义了dpm task state的状态,这些state和task相关。可以通过sys_dpm(DPM_SET_TASK_STATE)来设定。

#define DPM_TASK_STATE_LIMIT 4 ,定义了dpm的task operating state的最大数量

/*DPM_TASK_STATE is the 缺省状态 for all tasks, and is
 * inherited from init. */
#define DPM_TASK_STATE       (DPM_BASE_STATES + DPM_TASK_STATE_LIMIT)

/*number of supported operating states*/
#define DPM_STATES           (DPM_TASK_STATE + DPM_TASK_STATE_LIMIT + 1) ,定义了整个dpm operating state的数量。

#define DPM_TASK_STATES      (DPM_STATES - DPM_BASE_STATES)

#define DPM_STATE_NAMES                  \
{ "relock", "idle-task", "idle", "sleep",\
  "task-4", "task-3", "task-2", "task-1",\
  "task",                                \
  "task+1", "task+2", "task+3", "task+4" \
}

sys_dpm() -> u_dpm_set_task_state() -> dpm_set_task_state() -> dpm_set_os()

在看一个设定task state的操作:

static int u_dpm_set_task_state(pid_t pid, dpm_state_t task_state)
{
 return dpm_set_task_state(pid, task_state);
}

/*****************************************************************************
 * set a task state
 *****************************************************************************/

int
dpm_set_task_state(pid_t pid, dpm_state_t task_state)
{
 struct task_struct *p;

      /*task_state值的合法性检查*/
 if (task_state == -(DPM_TASK_STATE_LIMIT + 1))
  task_state = DPM_NO_STATE;
 else if (abs(task_state) > DPM_TASK_STATE_LIMIT)
      {
  dpm_trace(DPM_TRACE_SET_TASK_STATE, pid, task_state, -EINVAL);
  return -EINVAL;
 }
      else
      {  
            /*task_state值得调整,DPM_TASK_STATE是一个默认值
            设定的值比默认值或大或小,这样相当于加了
            个基准值而已-(DPM_TASK_STATE_LIMIT + 1) < task_state <= DPM_TASK_STATE_LIMIT*/
            task_state += DPM_TASK_STATE;
      }

     
 read_lock(&tasklist_lock);

 if (pid == 0)
  p = current;
 else
  p = find_task_by_pid(pid);

 if (!p) {
  read_unlock(&tasklist_lock);
  dpm_trace(DPM_TRACE_SET_TASK_STATE, pid, task_state, -ENOENT);
  return -ENOENT;
 }

 p->dpm_state = task_state;
 read_unlock(&tasklist_lock);

 dpm_trace(DPM_TRACE_SET_TASK_STATE, pid, task_state, 0);
 
 /*如果是当前进程,马上切换状态*/
 if (pid == 0)
  dpm_set_os(p->dpm_state);

 return 0;
}

以下的低层调用  dpm_set_os() -> dpm_resync() -> dpm_set_opt_async() -> dpm_md_set_opt() -> dpm_omap24xx_set_opt()
dpm_omap24xx_set_opt() 是一个与体系结构相关的set opt函数,在初始化的时候被注册。下面还会分析。

以下更低层的的调用最终会调用到omap24xx_fscaler(),定义在<arch/arm/mach-omap24xx/omap24xx_dpm.c>中,
这个函数做实际的事情,根据设定好的操作点设定cpu和相关硬件的状态,完成了dpm状态的切换。比如:

//= POWER MODES =//
#define PRCM_OFF       0
#define PRCM_ON        1
#define PRCM_RETENTION 2
#define PRCM_SLEEP     3
#define PRCM_STANDBY   4
#define PRCM_DORMANT   5

static void omap24xx_fscaler(struct dpm_regs *regs) -> omap24xx_pm_suspend(PRCM_STANDBY) ...

Just a piece of code:

 if(dpm_fscaler_flags & DPM_FSCALER_ANY_SLEEPMODE) {
  /* omap24xx_pm_suspend() handles voltage scaling */
  DPRINT("\nsuspending devices\n\n");
  device_suspend(0, SUSPEND_POWER_DOWN);

  if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_DEVICEON) {
   omap24xx_pm_suspend(PRCM_ON);
  }
  else if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_STANDBY){
   omap24xx_pm_suspend(PRCM_STANDBY);
  }
  else if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_SLEEP){
   omap24xx_pm_suspend(PRCM_SLEEP);
  }
  else if(dpm_fscaler_flags & DPM_FSCALER_SLEEP_RETENTION){
   omap24xx_pm_suspend(PRCM_RETENTION);
  }

   /* Here when we wake up. */

  /* Power on devices again */
  device_resume(RESUME_POWER_ON);

五. Kernel supported on DPM

5.1  task_struct

在task_struct中,添加了对dpm的支持:

/* dynamic power management */
 int     dpm_state;

这个状态就设定为上述提到的dpm task struct task状态之一。用dpm_set_task_state()来进行状态的切换。
子进程继承父进程的dpm状态,

另外,在支持dpm的kernel中,有许多类似的结构都添加了对dpm的支持。例如:

struct device_driver {
 char   * name;
 struct bus_type  * bus;
 struct device_class * devclass;

 struct semaphore unload_sem;
 struct kobject  kobj;
 struct list_head bus_list;
 struct list_head class_list;
 struct list_head devices;

 int (*probe) (struct device * dev);
 int  (*remove) (struct device * dev);
 void (*shutdown) (struct device * dev);
 int (*suspend) (struct device * dev, u32 state, u32 level);
 int (*resume) (struct device * dev, u32 level);
#if 1 /* linux-pm */
 int (*scale) (struct bus_op_point * op, u32 level);
#endif /* linux-pm */
};

整个dpm架构在Linux kernel中以device driver的形式存在,提供了支持应用程序和内核及其他driver的接口。

六. DPM Initializing
 
了解了这些知识,看看dpm的初始化:
<drivers/dpm.c>
一看这个目录就知道了这个可能是个driver,至少是个内核模块,至于怎么编译就不管了,可以静态编译到内核中.所以你肯定会去找module_init拉.
#ifdef MODULE
module_init(dpm_init_module);
module_exit(dpm_exit_module);
#else
__initcall(dpm_init_module);
#endif

看见了初始化函数是dpm_init_module:
int dpm_init_module(void)
{
#ifndef CONFIG_MACH_OMAP1610_STBPM
 /* ensure syscall slot is available */
 if (sys_call_table[sys_dpm_nr] != sys_call_table[0])
  return 1;

 /* install our syscall */
 sys_call_table[sys_dpm_nr] = sys_dpm;

#ifdef CONFIG_PROC_FS
 {
  void dpm_proc_init(void);
  dpm_proc_init();
 }
#endif

 dpm_md_init();

 trace("DPM is nw installed\n");
#endif /* CONFIG_MACH_OMAP1610_STBPM */
 return 0;

}

(1) 注册自己的系统调用 sys_dpm,作为用户态应用程序的接口
(2) 调用dpm_md_init()完成自己的初始化.

static inline void dpm_md_init(void)
{
        if (dpm_md.init)
                dpm_md.init();
}

<arch/arm/mach-omap24xx/omap24xx_dpm.c>
int __init dpm_omap24xx_init(void)
{
 printk("OMAP24XX Dynamic Power Management\n");

      /*在这个地方连接cpu相关的初始化函数*/
 dpm_md.init  = NULL;
 dpm_md.init_opt  = dpm_omap24xx_init_opt;
 dpm_md.set_opt  = dpm_omap24xx_set_opt;
 dpm_md.get_opt  = dpm_omap24xx_get_opt;
 dpm_md.validate_opt = dpm_omap24xx_validate_opt;
 dpm_md.idle_set_parms = NULL;
 dpm_md.cleanup  = dpm_omap24xx_cleanup;

#ifdef MAYBE
 /* +++ necessary? */
 dpm_omap24xx_board_setup();
#endif
#ifdef LATER
 dpm_bd.init();
#endif

 omap24xx_clk_init();
 omap24xx_vcs_init();

 return 0;
}


七. DPM idle and DPM suspend

说一下idle:

<arch/arm/mach-omap24xx/omap2_dpm.c>

int __init
dpm_omap_init(void)
{
 printk("TI OMAP Dynamic Power Management.\n");

 dpm_md.init_opt  = dpm_omap_init_opt;
 dpm_md.set_opt  = dpm_omap_set_opt;
 dpm_md.get_opt  = dpm_omap_get_opt;
 dpm_md.validate_opt = dpm_omap_validate_opt;
 dpm_md.idle_set_parms = NULL;
 dpm_md.cleanup  = dpm_omap_cleanup;

 return 0;
}

__initcall(dpm_omap_init);

发现了对硬件的实际操作基本都在<arch/arm/mach-omap24xx/pm.c>中定义。

static int __init omap_pm_init(void)
{
 unsigned char addr, data;
 u32 yy;
 u32 zz;

 printk("Power Management for TI OMAP.\n");
 pm_idle = dpm_idle;

 memcpy((void *)ISRAM_PROGRAM, omap2420_cpu_suspend,
        omap2420_cpu_suspend_sz);


 addr = 0x55; /* IOSTATE2 */
 Spi1_read(SPI1_CS1, 1, &addr, &data);

 if((data & 0x04) == 0x04){ /* BRB? */
  pm_BRB_mode = 1;
 }
 else {
  pm_BRB_mode = 0;
 }

 /* GPIO wakeup settings */
 __raw_writel(0x0002A000, OMAP2420_GPIO1_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO1_SETWKUENA */
 __raw_writel(0x0000000C, OMAP2420_GPIO2_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO2_SETWKUENA */
 __raw_writel(0x00004010, OMAP2420_GPIO3_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO3_SETWKUENA */
 __raw_writel(0x00100020, OMAP2420_GPIO4_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO4_SETWKUENA */
 __raw_writel(0x00000000, OMAP2420_GPIO5_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO5_SETWKUENA */

#ifdef CONFIG_ICE_PATCH_PM
 if(pm_BRB_mode)
 {
  request_irq(INT_GPIO_121,gpio121_dummy_handler,SA_INTERRUPT,"gpio121_dummy",NULL);
  __REG32(0x49012034) |= (1<<25) ;
  __REG32(0x49012040) &= ~(1<<25);
  __REG32(0x49012044) |= (1<<25) ;
  __REG32(0x4901201C) |= (1<<25) ;
  __REG32(0x49012048) |= (1<<25) ;
  __raw_writel(0x02100020, OMAP2420_GPIO4_BASE + OMAP2420_GPIO_SET_WKUENA);/* GPIO4_SETWKUENA */
 }
#endif
 if(stm_dev_sts[DRV_UART1] == STAT_OFF) {
  /* disable UART1 function clock */
  CM_FCLKEN1_CORE &= ~(1<<21); /* CM_FCLKEN1_CORE:EN_UART1 */
  CM_ICLKEN1_CORE &= ~(1<<21); /* CM_ICLKEN1_CORE:EN_UART1 */
 }

 yy = __REG32(0x6D000060) & 0x0000FF00; /* SDRC_DLLA_CTRL(0x6D000060) 0x0000yy00 */
 zz = __REG32(0x6D000068) & 0x0000FF00; /* SDRC_DLLB_CTRL(0x6D000068) 0x0000zz00 */
 *((u32 *)(ISRAM_PROGRAM + omap2420_val_SDRC_DLLA_CTRL_yy_offset)) = yy;
 *((u32 *)(ISRAM_PROGRAM + omap2420_val_SDRC_DLLB_CTRL_zz_offset)) = zz;

 pm_loops_per_jiffy = loops_per_jiffy;

 return 0;
}
__initcall(omap_pm_init);

可以看到很多都是对硬件设备的直接操作。

那么既然这里把pm_idle初始化成了dpm_idle(),所以dpm_idle就是实际的dpm idle state的处理函数。

我们看在linux中何时会处理dpm idle state,也就是说硬件驱动层和内核的接口。
<arch/arm/kernel/process.c>,定义了这个接口。
void (*pm_power_off)(void);
void (*pm_idle)(void);

EXPORT_SYMBOL(pm_idle); pm_idle也会被kernel export出来,做为驱动程序的接口

/*
 * The idle thread.  We try to conserve power, while trying to keep
 * overall latency low.  The architecture specific idle is passed
 * a value to indicate the level of "idleness" of the system.
 */

void cpu_idle(void)
{
 /* endless idle loop with no priority at all */

 while (1) {
  while (!need_resched()) {
   void (*idle)(void) = pm_idle;
   if (!idle)
    idle = default_idle;
   leds_event(led_idle_start);
   dpm_set_os(DPM_IDLE_STATE);
   idle();
   dpm_set_os(DPM_IDLE_TASK_STATE);
   leds_event(led_idle_end);
  }
  schedule();
#ifndef CONFIG_NO_PGT_CACHE
  check_pgt_cache();
#endif
 }
}

最关键的几句:
dpm_set_os(DPM_IDLE_STATE);
idle();  (由上述分析,实际会调用dpm_idle())
dpm_set_os(DPM_IDLE_TASK_STATE);


看看dpm_idle里面到底做了什么:
/* If DPM is currently disabled here we simply do the standard
   idle wait.

   If we're not actually in DPM_IDLE_TASK_STATE, we need to go back and get
   into this state.  This could happen in rare instances - an interrupt between
   dpm_set_os() and the critical section.

   If we are not yet at the idle-task operating point, or if there is no
   difference between idle-task and idle, we can enter/exit the idle state
   quickly since it's only for statistical purposes.  This is also true if for
   some reason we can't get the DPM lock, since obviously an asynchronous event
   is going to have to occur to clear the lock, and this event is going to take
   us out of idle.

   Otherwise the full idle shutdown is done. */

dpm_idle() -> full_idle()-> omap24xx_pm_idle()

这个是NEC dpm中做实际事情的最内层的调用函数:
void omap24xx_pm_idle(void)
{
 TRACE_PROCESS(TRACE_EV_PROCESS_IDLE_BEGIN, 0, 0);

 if(dpm_enabled) {

  if(status_idle_judge_func() == STM_IDLE) {

   device_suspend(0, SUSPEND_POWER_DOWN);

   local_irq_disable();
   local_fiq_disable();

   if( OMAP_SLEEP_ENABLE == do_sleep_setup() ) {
    int dummy = PRCM_SLEEP;
    omap24xx_pm_suspend(dummy);
   }

   local