2008年9月26日星期五

ARM Linux kernel2.6静态memory mapping

基于ARM的SOC,很多设备寄存器的访问是需要直接静态映射的。而这部分工作一般都是在体系结构初始化时,调用iotable_init()完成。
它的其中一个参数是:
struct map_desc {
unsigned long virtual;
unsigned long pfn;
unsigned long length;
unsigned int type;
};
virtual就是想要映射的虚拟地址,pfn是物理地址,我要强调的是它不是point function,而是Page Frame Number,因此这个物理地址要切换成成页号。

2008年9月22日星期一

unattended mode reference count

PowerPolicyNotify(PPN_UNATTENDEDMODE, TRUE);
PowerPolicyNotify(PPN_UNATTENDEDMODE, FALSE);
这一对函数的作用,相当于
EnterCriticalSection()
LeaveCriticalSection()
这样类似的成对使用的函数。
实际power manager维护着一个global的reference count,当call PowerPolicyNotify(PPN_UNATTENDEDMODE, TRUE)时,reference count++。
当PowerPolicyNotify(PPN_UNATTENDEDMODE, FALSE),reference count--。

如果万一我们call他们时没有对称,那么系统会怎样?

Power manager参考reference count,如果不等于0,那么他会调度决定进入unattended mode。但是并不是说reference count不等于零,power manager就会让系统永远处于unattended mode,而是会有等待一个超时,如果超时,那么系统还是会进入sleep。同时,这个reference count会在进入sleep后清零,也就是"以前所有的不对称都既往不咎"。

2008年9月18日星期四

Windows Mobile 睡死之谜

Windows Mobile很容易出现"睡死过去"的现象,排除驱动程序的代码问题,那么还是可能"睡死过去"。
今天做了一个实验来模拟在suspend最后那一刻,对文件操作。结果,机器整个hang。

FILE *logFile;
FileSystemPowerFunction(FSNOTIFY_POWER_OFF);

logFile = _tfopen(TEXT("hello.txt"), TEXT("w"));

if (logFile != NULL)
{
_ftprintf(logFile, TEXT("%ld"), 12 );
fclose(logFile);
}

FileSystemPowerFunction(FSNOTIFY_POWER_OFF);是power manager在睡前最后几步必做的事情。而下面的写操作是模拟另一个线程在这个时刻后做写文件的操作。结果就是死亡。
这样的结构在实际开发中很容易形成,要特别小心。

IOCTL_POWER_SET, power manager不为你负责

IOCTL_POWER_SET是驱动程序接收power manager指令的接口。你不要以为power manager会保证每次call你的IOCTL_POWER_SET会保证race condition不发生。换句话说就是IOCTL_POWER_SET可能会被同时call到。
今天我就遇到这样的情况,一个是power manager正常的设置bkl1:到D0,结果另外一个驱动调用了
DevicePowerNotify(_T("bkl1:"), D0,POWER_NAME);
结果我偶然发现Backlight的IOCTL_POWER_SET被同时call到,导致race condition的发生。最后就用Critical Section解决了。

2008年9月12日星期五

Linux对S3c2443的支持还不完善

我发现目前最新的Linux kernel 2.6.26.5对三星S3c2443 soc的支持是非常不完善的。我想大概是各位kernel hackers拿到s3c2443的机器太少了。大致浏览了一下代码,发现以下问题:

  1. main clock的读寄存器并计算是有问题的
  2. 由于第1条,导致Uartclock无法正常设置,Uart无法正常工作
  3. s3c2443已经支持4 Uart channelkernel source code目前只支持3
  4. TFT LCD controller完全没有支持

这些问题部分我已经和Ben Dooks联系,有望在2.6.28 release时,对s3c2443 soc的基本功能进行全面的支持。


display和backlight之间不能不说的事情

我们在windows mobile5/6上写display和backlight驱动程序时候,经常可能会遇到"白屏"的现象。
Display关闭,backlight还是打开,这就是"白屏"。一般情况下,Display和backlight是两个独立的驱动程序,那么看来在关闭屏幕和背光这两个驱动程序是有一定顺序的。
我们先考虑关灯的情况,需要按如下顺序,不可颠倒,颠倒就是"白屏":
1. backlight off 2. display off
再考虑开灯的情况,需要按如下顺序,不可颠倒,颠倒就是"白屏":
1. display on 2. backlight on
从上面可以看出,开灯和关灯动作的顺序是相反的。如果从供电的角度来看,display和backlight有着父子关系,即display是backlight的父亲。display有电,backlight才可能有电。
可是Windows Mobile的power manager在睡觉和唤醒时,对设备驱动的操作是线性的。比如先开display,再开backlight,那么必然也是先关display,再关backlight。因此靠power manager来解决白屏问题是不可能了,需要我们自己处理。
而且实际上由于display和backlight的IClass不同,使得他们位于power manager的两个不同的设备管理列表里,导致backlight的流设备列表一定先被操作到,然后才是display。这样一来和上面提到的关灯的顺序是符合的。开灯的顺序相反!

解决方案一
把backlight放在display驱动中去做,这样简单很多,很多变量可以共享

解决方案二
Backlight和display仍然是两个驱动。那么我们的重点是反转开灯的顺序。可以选择在backlight驱动程序处理IOCTL_POWER_SET的D0时,给display驱动的电源状态设置一个floor(请参考 http://cpuwolf.blogspot.com/2008/09/window-mobile-power-managementdevice.html),先调用SetPowerRequirement(pszDevice, D0, POWER_NAME, NULL, 0);再开启硬件背光。这个结论Microsoft的文档也有,但是她绝对不会告诉你为什么。

2008年9月11日星期四

charge icon

用3ds max做了3张充电的图标,尺寸是128x84,如果有需要原图的,直接email我,我这里是bmp原图

揭秘window mobile power management关于device power state的管理

hi, I am cpuwolf。今天我就由深入浅的帮你揭开mobile power manager(也就是pm.dll)是如何调度设备的power state。先分析power manager的内部结构,再从API的角度帮你理解power management API的不同。他们是:
DevicePowerNotify()
SetDevicePower()
SetPowerRequirement()
ReleasePowerRequirement()
这几个函数,如果你不听我讲,光想通过看看microsoft的官方文档来理解,那是不可能的!信不信由你。

重要的数据结构

power manager为每一个被管理的设备维护着一个数据结构,它的定义简化后如下:
// this structure describes a power-manageable device
typedef struct _DeviceState_tag {
。。。
CEDEVICE_POWER_STATE curDx; // current official power state (not necessarily supported by the device)
CEDEVICE_POWER_STATE floorDx; // minimum device power state, or PwrDeviceUnspecified
CEDEVICE_POWER_STATE ceilingDx; // maximum device power state, or PwrDeviceUnspecified
CEDEVICE_POWER_STATE setDx; // power state if explicitly set, or PwrDeviceUnspecified
CEDEVICE_POWER_STATE lastReqDx; // last state requested by the device
CEDEVICE_POWER_STATE actualDx; // current actual device power state
CEDEVICE_POWER_STATE pendingDx; // Pending DX for updating
DWORD dwNumPending; // Number of Pending for updating.
。。。
} DEVICE_STATE, *PDEVICE_STATE;
居然有这么多的device power state来影响最后的一个power state的结果。也就是说power manager是个调度中心,当它最后决定某个设备最终该是什么power state(D0/D1/D2/D3/D4)的时候,要参考上面这些成员变量。所以我们第一件事是要搞清楚power manager的调度原则。

power manager的调度原则

设备的电源状态总共有D0,D1,D2,D3,D4 (D0〈D1〈D2〈D3〈D4)。ceilingDx与floorDx听名字就知道是天花板和地板的意思(ceilingDx〈floorDx),人是生活在天花板和地板之间的空间的。在电源管理里面,意思就是天花板和地板之间的电源状态为有效状态。
例如:
ceilingDx=D1
floorDx=D3
那么D0,D4就是power manager不用考虑的状态,D1,D2,D3就是有效的电源状态。


setDx是最厉害的一个状态,它默认是PwrDeviceUnspecified,只要它不是PwrDeviceUnspecified,那么这个设备的最终状态就等于setDx。
那么setDx是谁设置的呢?
从名字我们一猜就知道,那就是SetDevicePower()。也就是说,只要这个函数一出马,那么不管系统当前是什么状态,或者这个设备是什么状态,这个对应的设备会立即切换到你指定的状态。因此,call这个函数的时候一定要三思而后行。白屏现象就很有可能是他造成的。
例如:
SetDevicePower(_T("BKL1:"),POWER_NAME,D4);
这句话就好像在说:“power manager,我命令你把BKL1:变成D4!!”

如果setDx是PwrDeviceUnspecified,那么power manager就开始考虑lastReqDx。lastReqDx的气势就要虚弱很多,也正如microsoft的文档所说,如果你想改变一个设备的系统状态,同时还想争得power manager的同意,不想太强制,那么call DevicePowerNotify()是再合适不过的了。
例如:
DevicePowerNotify(_T("BKL1:"),D4,POWER_NAME);
这句话就好像在说:“power manager,你看我现在把BKL1:变成D4如何?!”
power manager接到这样的请求,它也是需要掂量一下的,那么它的原则是什么呢?只要是有效状态就可以啦!也就是说ceilingDx〈 lastReqDx〈floorDx。
如果ceilingDx=D1,lastReqDx=D0,那么设备最终也只能是D1。
再如floorDx=D3,astReqDx=D4,那么设备最终也只能是D3。
如果 ceilingDx=D1,floorDx=D3,你请求D2,那么设备最终就是D2。

以上原则很简单吧!可是你有没有注意到我没有提到ceilingDx与floorDx是如何确定的。说到这个就不得不说说SetPowerRequirement()和
例如:
SetPowerRequirement(_T("BKL1:"),D1,POWER_NAME,NULL,0);
好像再说在说:“power manager,帮我把BKL1:变成D1”。你觉得呢?当然不是!
其实你的真正意思是在说:“power manager,至少帮我把BKL1:变成D1”。
然后power manager问你,“D0可以么?(背光更亮一点可以么?)”。
然后你会说,“当然也可以。”
从这个对话中,我们可以知道SetPowerRequirement()实际在设置我们的地板-floorDx。至少是floorDx,不能更低了。floorDx的默认值是D4,因此如果你不去call这个函数,那么就没什么限制。

至于ceilingDx
注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\[ on | BacklightOff | Unattended | xxx ]的下面你可以定义:
"bkl1:"=D2
这样的定义,就好像你在规定:“系统在on的时候,bkl1:最高是D2”。所以bkl1:如果是D3,那么也是可以的。这实际就是在定义设备电源状态的天花板-ceilingDx。同时,如果你没有特别为某个设备指定,那么这个设备的ceilingDx就是用注册表项default的定义值。

综上,floorDx,ceilingDx,setDx,lastReqDx这四个值是power manager调度时要参考的重要参数。

设置设备的电源状态

curDx,actualDx是一对,意思非常接近。
curDx是power manager经过调度算法,最后决定的,该设备的电源状态。可是这个状态,此设备不一定支持。设备声明自己所支持的所有状态是通过IOCTL_POWER_CAPABILITIES做到的。因此万一不支持还需要经过mapping,那么mapping的结果就是actualDx,这个结果就直接通过IOCTL_POWER_SET下达给设备驱动。mapping的过程当然就要参考IOCTL_POWER_CAPABILITIES了。

pendingDx和dwNumPending是用于处理竞争问题设计的计数器。

2008年9月1日星期一

linux kernel 2.6.27-rc5三天前release

linux kernel stable 2.6.27-rc5三天前release。和以往一样,这次release对大多数来说并没有什么特别。但是对我来讲,这版kernel有着特殊的意义。
因为它包含了我第一次成功向Linus Torvalds提交的补丁。从上大学到现在7年了,我终于有能力参于linux内核开发了。这一天我等了很久。虽然我这次的修改非常简单,但是至少能说明我可以开始融入这个开源内核的大家庭。同时我也相信自己会在日后提交更多的修改。
这种感觉就像小学一年级第一次受老师表扬,等到老师认可的快乐!
在这个submit patch的过程中,可以感觉到这个跨世界范围的大项目的运营过程。看他们是怎么集合大家的智慧,在如此多人参与的项目中,他们是如何把控代码的质量。这些都值得我去学习。

http://www.kernel.org/pub/linux/kernel/v2.6/testing/ChangeLog-2.6.27-rc5

2008年8月4日星期一

where to place ARM linux kernel

Uncompressed Kernel Image
在ARM体系中,下面这条是定理
ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)
PAGE_OFFSET + TEXT_OFFSET定义了内核image的起始虚拟地址。ARM中定义PAGE_OFFSET为0xC000,0000,很明显这是虚地址。arch\arm下的makefile中定义
TEXT_OFFSET := $(textofs-y)
textofs-y的默认值定义如下
textofs-y := 0x00008000
ZRELADDR是对应的物理地址,它在makefile.boot中定义为
zreladdr-y := 0x10008000
这里以OMAP1为例,这个地址就是DRAM的所在的区域
由此可见,这里就是一次MMU mapping,0x10008000==>0xC0008000,那么我们的kernel就应该放在物理地址0x10008000 上,这些都是默认设置我们没必要修改。
如果只是要得到Uncompressed kernel image,就是用make Image命令build kernel,上面的常识就足够了。
Compressed Kernel Image
但是如果你使用make zImage命令build kernel,那么你一定想得到一个Compressed kernel image,这样kernel体积小,节省flash空间.
zImage有两部分组成,前段是自解压代码,后段是被压缩过的kernel image。因此自解压代码也是需要一个放置地址的。
它的起始地址是TEXT_START,TEXT_START默认为0。这段代码是地址无关的,所以可以被安排在任何地址上,解压后的地址由上面的zreladdr决定,这里就是0x10008000。
解压前后内存可能重叠
为了避免内存块重叠,linux建议我们最好这样安排:
1.解压缩后的image起始地址,在压缩image的后面
2.解压缩后的image的结束地址,在压缩image的前面
对于这两条,我觉得第一条更容易做到
解压缩后的image大小如何计算
linux kernel image是用gzip压缩的,压缩基本比例为3:1,那么在计算解压缩后的image大小时,用压缩image大小 x 4计算比较安全。
跳转到kernel
call_kernel: bl cache_clean_flush
bl cache_off
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mov pc, r4 @ call kernel
这是跳转到解压缩的image的动作。这里可以看出ARM linux规定的向kernel的参数传递规则。
r0为0
r1为architecture的识别号
r2是指针,指向atags参数表
然后跳转到uncompressed image的起始地址上。这里r7,r8寄存器在开始就被备份过,这里做一次恢复。

2008年7月30日星期三

Sharing Hardware Registers: When to replace multiple writers with a shared resource driver.

Sharing Hardware Registers: When to replace multiple writers with a shared resource driver.
Posted by Jeremy Cooke
翻译自
http://blogs.msdn.com/ce_base/archive/2007/03/29/sharing-hardware-registers-when-to-replace-multiple-writers-with-a-shared-resource-driver.aspx

Hi, I am cpuwolf。这篇来自微软的建议,写的是关于多个驱动共享同一个硬件的问题。这里给出了很好的建议。我尝试用尽可能贴切的专业词语翻译,有什么不对的地方,请指出。
当设计复杂的系统时,偶尔会必需协调对某些的共享资源的访问。一般这些同步问题都由操作系统所提供的例如critical section或mutex等来解决 。这些技术在它是一个简单的共享资源,并且较少的对它的访问时,是无可挑剔的。但对于复杂的资源访问多个threads ,进程,驱动程序,或多个应用程序,新的规范可能更为合适。一个很好的共享资源的驱动程序的例子,就是GPIO驱动程序。每个驱动可能共用一个GPIO模块的寄存器,或者共同读-修改-写,每个驱动如果不同步,那么就会产生问题,
比起直接访问共享资源,把这些访问操作抽象到一个独立的驱动程序去做,可能会更好。所有会访问共享资源的家伙,可以采用尽量少的open这个驱动或者较少使用它的接口。同时,这个驱动程序在内部去处理将同步问题和处理任何可能出现的错误。调用者可以不用再担心同步问题,因为所有这些麻烦都已经交给了别的驱动。
通过使用这一技术,程序员,往往能够避免使用critical section,否则这样的结构会充斥整个原本的代码。现在每一次对共享资源的访问,现在只需通过一个用户设计的API,这些API抽象了访问动作,并且增加程序的可读性。
共享资源的驱动程序也应该预处理和检查错误的输入,以及很好的处理共享资源的错误。由于错误码现在是集中处理的,所有程序员大可认为共享资源已经被很好的保护。这是team开发时的很重要的一点。
例子学习
这个嵌入式系统具有多通道ADC转换器,并且每个通道都有相应的设备使用。多个不相干的驱动程序都试图访问各自的通道。软件可以将hardware所有通道的转换结果放在一块内存中。如果多个线程请求ADC转换,同时没有适当的同步 那么这里就潜藏着一个污染到另一个的情况。
在这特别的系统里有和ADC转换相关的battery驱动程序, USB驱动程序,以及系统的背光驱动程序。battery驱动程序是要关心当前的电压、电池的温度、以及充电电压和充电电流。 USB驱动程序需要监控USB vbus的电压,而背光驱动器需监测环境光线与光电二极管。由于这些功能是在自己的驱动中处理,所有共享资源的访问,都必须以一个更先进的方式控制。以下图1描绘了每个驱动程序都要存取硬件的ADC 。
模拟向数字转换器,将被抽象,并使用一个专门的驱动程序。驱动程序允许多个writer使用所有通道的转换功能,同时返回结果放在一个用户指定的内存中(见图2下文) 。
这个驱动暴露出一个函数,其中包括A / D转换功能并返回结果。如下:
enum ADChannels
{
AD_CHANNEL_BAT_VOLTAGE = 0,

AD_CHANNEL_BAT_CHG_VOLTAGE,
AD_CHANNEL_BAT_CHG_CURRENT,

AD_CHANNEL_BAT_TEMP,
AD_CHANNEL_USB_VBUS,
AD_CHANNEL_PHOTODIODE,
TOTAL_AD_CHANNELS

};


BOOL PerformADConversion(USHORT *pOutBuffer, DWORD OutSize);
在这里pOutBuffer 指向用户的buffer,在函数返回的大小必须等于TOTAL_AD_CHANNELS。必须注意,当向用户数据区拷贝数据时,建议使用类似CeSafeCopyMemory 的函数来处理,它可以处理非法内存的问题。
该PerformADConversion 函数可以实例化一个critical section同步使用,可以看出在图3 :

在这个设计中,所有硬件访问都被保护在一个集中的代码区,只使用仅仅一个critical section。系统性能得以提升,因为使用了轻量级的critical section,而是一个缓慢的同步对象(如mutex) 。程序员现在可以进一步优化系统性能,每个驱动程序可以同时进行。
虽然使用一个专门的驱动程序来完成这些事情有很多的好处,但是,事实上,这种做法可能会稍微有点慢。因为通过标准的Windows CE的函数访问驱动程序是有一些开销。因此,最好的方法是建立一种结构是驱动的访问开销最小。但是无论怎样,使用这种方法的收益是远远大于这里的开销的。

挖掘WinCE 5系统调用过程

int shellcode[] =
{
0xE59F0014, // ldr r0, [pc, #20]
0xE59F4014, // ldr r4, [pc, #20]
0xE3A01000, // mov r1, #0
0xE3A02000, // mov r2, #0
0xE3A03000, // mov r3, #0
0xE1A0E00F, // mov lr, pc
0xE1A0F004, // mov pc, r4
0x0101003C, // IOCTL_HAL_REBOOT
0xF000FE74, // trap address of KernelIoControl
};
这是黑客常用的缓冲攻击代码形式,这里的32为常数,都是ARM机器指令。
我就要想你展示的不是如何攻击WinCE内核,而是看看WinCE的系统调用是如何实现的。WinCE一个常用的API,KernelIoControl,它的实现体实在内核NK.exe,而function caller只是一个trusted的user级别的application。
你可以想象,这样的API的实现,必然要经历CPU从user processer mode切换到supervisor processer mode。
BOOL KernelIoControl(
DWORD dwIoControlCode,
LPVOID lpInBuf,
DWORD nInBufSize,
LPVOID lpOutBuf,
DWORD nOutBufSize,
LPDWORD lpBytesReturned
);
KernelIoControl超过了4个参数,那么在参数传递时,超过的部分使用堆栈完成的。IOCTL_HAL_REBOOT的参数都没什么用,所以这个我们忽略,传0就可以了,因此r1,r2,r3赋值0.
IOCTL_HAL_REBOOT的数值必须放在寄存器r0。
上面的代码中r4的内容会变为0xF000FE74,代码最后就是跳转到0xF000FE74,你可以看到这个地址很大,在整个内存空间的高地址。同时这个地址是经过编码得到,公式是:
0xf0010000-(256*apiset+apinr)*4
对于KernelIoControl,apiset是0,apinr是99。0xF000FE74是这样得到的。
当代码跳转的这样一个高地址时,会引发prefetch abort,这样exception会被内核 ,也就是NK.exe抓到。然后再对这个出错地址进行解析,就可以知道应用程序想访问那个system cal。
综上,WinCE并不是靠SWI这样的软中断实现system call,而是prefetch abort。

2008年7月24日星期四

WinCE5 kernel高地址都放了什么东西


typedef struct ARM_HIGH {
ulong firstPT[4096]; // 0xFFFD0000: 1st level page table
char reserved2[0x20000-0x4000];

char exVectors[0x400]; // 0xFFFF0000: exception vectors
char reserved3[0x2400-0x400];

char intrStack[0x400]; // 0xFFFF2400: interrupt stack
char reserved4[0x4900-0x2800];

char abortStack[0x700]; // 0xFFFF4900: abort stack
char reserved5[0x6800-0x5000];

char fiqStack[0x100]; // 0xFFFF6800: FIQ stack
char reserved6[0xC000-0x6900];

char kStack[0x800]; // 0xFFFFC000: kernel stack
struct KDataStruct kdata; // 0xFFFFC800: kernel data page
} ARM_HIGH;

Post-Processing flash.dio


windows mobile最终生成image通常叫做flash.bin。请记住其实是flash.dio,这个文件是一个真正的数据镜像,flash.bin是flash.dio在后期处理,也就是post-processing阶段生产的。
Flash.bin中会包含NAND flash每个sector info的附加信息,并且以block的大小分段。其实也就是把flash.dio中的原始数据加以分段,打包。

值得注意的是微软上面提到:
In addition, NandPostProc.exe also truncates TFAT by 4 blocks, and then adds two compaction blocks into IMGFS and into TFAT. These compaction blocks provide wear–leveling, and help lengthen the life of the flash hardware.

Flash.bin中imgfs分区和tfat分区会被加上gap,反正我是没理解微软的用意。他这样一改,会导致开头的MBR中的分区表信息和这里对不上,因为两个分区后移了。除非这4块区域被标记为未使用,那么查找分区时会自然向后查找,否则就会出错。下面是我用KITL抓到的错误:
Loaded 'cecompr.dll', no matching symbolic information found.
2928 PID:a7a9f512 TID:e7a7a85e ERROR: IMGFS!CVolume::LoadCompressionEngine: unable to load decompressor type "yyyyyyyyyyyyyyyy" from dll "CECOMPR.DLL"... expect failures!!!
Unloaded symbols for 'cecompr.dll'

这样的错误,我花了1个多月,才发现就是上面4 blocks导致。最后我临时换了方案,改烧flash.dio就OK。
所以如果你在porting platform时,遇到这样的问题,先参考一下我的经历。

2007年11月28日星期三

DRAM precharge power down mode

我们知道 DRAM中的记忆体,或者说电容,打开新行的操作就是precharge(预充电)。预充电可以通过命令控制,也可以通过辅助设定让芯片在每次读写操作之后自动进行预充电。而从预充电到真正发送行有效命令需要一些时间

DRAM中prechar

2007年10月25日星期四

UART hardware flow control

串口,RS232UART这些概念总是让人头晕。如果你也有同感,那么就对了。因为串行通讯这一块从来就没有统一过,连接的方式和协议更是百家争鸣。

不过现在用于嵌入式领域的UART通讯基本是统一的。一般只用rx,tx,cts,rts这几个信号。下图是一个典型的SOC连接蓝牙芯片的连接。其中包含了发送和接受的流控制机制。


2007年7月29日星期日

操作系统内核的中神奇的代码

最近free download一个Linux kernel 2.6.22。由于工作的原因,大概有2年没有碰触Linux了,这些天又有生活无目标的感觉。花了2年时间才大概弄清楚WinCE整体的结构,基本能够理解微软的设计思想,也尝试看WinCE5的内核代码,但是有时候会很不爽,一个函数跟着跟着就丢失了。尤其是GWES,我最想知道的部分,完全没有代码。内核代码很多类似的部分,其中几乎都是C语言构成,C语言的组织基本架构就是函数,我们平时写应用程序时,也经常用函数,可是内核中的函数有时充满了玄机,有的是不归路,有的切换,有的是交错运行,对了,此时让我突然想起硬件文境切换代码,有空大家一定要看看绝对长见识。不过今天不看那些东西,我随便读,代码到哪里就说到哪里。看内核启动时,初始化终端的函数

void __init console_init(void)

{

initcall_t *call;

/* Setup the default TTY line discipline. */

(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

/*

* set up the console device so that later boot sequences can

* inform about problems etc..

*/

call = __con_initcall_start;

while (call <>

(*call)();

call++;

}

}

__con_initcall_start这个变量你就是找遍所有C和汇编代码你也找不到它的原型。它是出现在内核编译的链接阶段,是一个编程语言中的标记,或者说是一个地址,在这里也确实表现为地址,__con_initcall_start__con_initcall_end一起前后界定了了代码段中.con_initcall.init

#define console_initcall(fn) \

static initcall_t __initcall_##fn \

__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

搞这个macro声明了很多已赋值的函数指针,如32CPU那么就占用4个字节,console_init就是循环运行这些函数指针多对应的函数。这个架构真是太好了,很多驱动程序只要用这个macro就可以声明自己,而内核初始化部分就无须直接关心有多少console驱动程序做这样的声明,靠着强大的编译器就可以帮助你了解到。还是GCCLD厉害,微软的编译器功能太少,灵活度不高(至少微软对外放出的编译器是这样,说不定他们内部有很高级的)。不过毕竟微软不希望你利用他们的操作系统再去开发别的系统。呵呵。

所以分析内核代码是一定要想象这段代码的运行环境,否则你很难理解作者到底要干什么。文境切换代码更是如此,经过schedule函数直接就到另外一个进程去了,很神奇。还有在SMP环境下,一个函数直接会分叉,编程同时在多个CPU运行。今天就看了这点东西,以后再遇到什么好玩的东西,再和大家分享。