怎么在linux 内核模块块中调用apic send ipi函数

博客访问: 131163
博文数量: 32
博客积分: 557
博客等级: 中士
技术积分: 421
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
原文地址: 作者:
最近看Linux kernel中的perf event的实现部分,看到下面的代码,觉得很有意思,记录整理一下(内核版本3.7.2):
kernel/events/core.c:
L2674-- &&static u64 perf_event_read(struct perf_event *event)
* If event is enabled and currently active on a CPU, update the
* value in the event structure:
if (event-&state == PERF_EVENT_STATE_ACTIVE) {
smp_call_function_single(event-&oncpu,
__perf_event_read, event, 1);
perf_event_read用于读取一个event的pmu hw counter的值,event subsystem在Linux kernel中的实现还比较复杂,并不单纯如pmu的读写那样简单。函数中的第一个if语句是说如果该event处于active状态,那么读取其在pmu中对应hw counter 寄存器的值。因为event的实现分为task context和cpu context两大类,简言之是在一个task运行的life cycle还是一个cpu lifecycle中对相关的pmu hw counter计数。回到perf_event_read函数,如果我们发现该event目前在某一cpu上属于active状态,那么就进入if语句块中。有意思的是,当我们正在看的这段代码正在运行时,要读的该event并不一定是在当前的cpu上,那怎么办呢?所以要实现一个所谓的cross cpu function call,也即上述代码片段中smp_call_function_single函数的实现,在UP系统中,可以肯定当前代码运行的CPU就是event所在的cpu,所以可以直接调用__perf_event_read函数,这也是UP系统下smp_call_function_single函数的实现原理。
有意思的是SMP系统下smp_call_function_single函数的实现:首先我们要知道当前代码正在上面运行的该CPU的ID(由smp_processor_id()获得),假设4-cpu系统,当前代码正在cpu1上运行,那么调用smp_processor_id返回1,这背后的原理是APIC和per-cpu实现机制的合成。要读取的event有一个成员变量event-&oncpu用来指明该event是绑定在哪个cpu上,如果event-&oncpu=1,表明执行当前代码的cpu就是event所在的cpu,那么很简单了,直接调用__perf_event_read就可以了。如果event-&oncpu!=1,那么要读取的event不在当前的cpu上,如何操控别的cpu来读取pmu中寄存器的值(每个cpu都有自己对应的pmu),原理其实很简单:IPI (Inter Processor Interrupt),就是通过local APIC给别的处理器发一个中断消息。但是具体的实现要考虑的东西可能比较多并且全面一点,表现在实际的代码上就不是太直白(直白的实现就是send_ipi这样的函数了)。在Linux内核中,每个cpu都有一个对应的csd_data的变量(很明显是一个per-cpu类型的):
kernel/smp.c:
static DEFINE_PER_CPU_SHARED_ALIGNED(struct call_single_data, csd_data); // csd means call single data
同时,系统中每个cpu都还拥有各自的一个类型为struct&call_single_queue的队列dst(list),smp_call_function_single()会根据目标cpu来获得该队列,把前述的csd作为跨cpu参数传递的方法(我怎么觉得获得csd指针最好是用类似__get_cpu_var(csd_data, cpu)这样的方式呢,但是代码中在!wait处使用__get_cpu_var(csd_data)。。。)。不管怎么说吧,跨cpu调用的参数传递方法是用了,然后如果队列dst为空,就调用arch_send_call_function_single_ipi(cpu)给参数所指定的cpu发ipi消息,目标cpu收到该消息进入中断处理函数,那么就调用csd_data-&func函数了(其实应该是ipi的中断处理函数处理dst队列中的每个结点,调用每个结点上的func函数指针,所以队列不为空时就没必要再发ipi消息了。
阅读(656) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。编辑:www.fx114.net
本篇文章主要介绍了"ipi机制",主要涉及到ipi机制方面的内容,对于ipi机制感兴趣的同学可以参考一下。
大家知道,在做内核调试器的时候,为了不影响当前环境,当中断产生的时候必须将非当前cpu外的其他cpu
的运行中断下来。那么内核调试器是怎么做到的呢?实际上这是APIC的ipi(处理器间中断)。下面我根据Linux和Windows的相应源码进行讲解。
当进入内核调试模式时,内核由KdEnterDebugger进入KeFreezeExcution。在KeFreezeExcution中首先检测是否KiFreezeExecutionLock
这个锁是busy状态,如果是则表示其他的处理器试图投递IPI给当前处理器,于是调用KiIpiServiceRoutine,这便是处理器间中断的
服务例程。那么什么是处理器间中断(IPI)呢?这要从8259A讲起。
传统的i386采用的8259A中断控制器有个缺点,如果在smp中采用这样的设计,那就只能静态的把所有外部中断源划分成若干组,
分别把每组都链接到8259A,使其与CPU有一对一的连接,然而这样就达不到动态分配中断请求的目的,也使得硬件设计变的很不简洁。
因此intel设计了更为通用的中断控制器--APIC,高级可编程中断控制器。另一方面,koala到处理器间中断请求的需要,每个cpu都
需要有个本地APIC,因为一个cpu常常要有目标的向系统中的其他CPU发出中断请求,所以从pentium开始intel就在cpu芯片
内部集成了一个本地APIC。但是SMP结构中还需要一个外部的、全局的APIC。
不管是外部还是内部,APIC都支持从0x20-0xff共240个不同的中断向量,其中0-0x1f用于cpu本身的异常。这些中断分为15个
优先级,可以按中断向量号处于16算得。除了外部APIC可以把来自外部设备的中断请求提交给系统的各个cpu外,每个cpu也可以通过其内部的apic
向其他cpu发出中断请求,这便是处理器间中断。当一个cpu向其他cpu发送中断信号时,就在自己的本地ICR寄存器(
interrupt command ragister)中存放其中断向量,和目标cpu拥有的本地apic标识出发中断。IPI信号由apic总线传递再到目标APIC。
例如,下面是我的某个cpu的idt表:
Dumping IDT:
37: 806e4864 hal!PicSpuriousService37
3d: 806e5e2c hal!HalpApcInterrupt
41: 806e5c88 hal!HalpDispatchInterrupt
50: 806e493c hal!HalpApicRebootService
63: 89b3948c serial!SerialCIsrSw (KINTERRUPT 89b39450)
&&&&&&&&& USBPORT!USBPORT_InterruptService (KINTERRUPT 898d3308)
&&&&&&&&& USBPORT!USBPORT_InterruptService (KINTERRUPT 89c50360)
73: 89d6f7e4 atapi!IdePortInterrupt (KINTERRUPT 89d6f7a8)
&&&&&&&&& atapi!IdePortInterrupt (KINTERRUPT 89daf008)
&&&&&&&&& atapi!IdePortInterrupt (KINTERRUPT 89e20bb0)
&&&&&&&&& atapi!IdePortInterrupt (KINTERRUPT 89e01bb0)
&&&&&&&&& atapi!IdePortInterrupt (KINTERRUPT 89da0bb0)
&&&&&&&&& USBPORT!USBPORT_InterruptService (KINTERRUPT 89c4f208)
83: 89c03bec VIDEOPRT!pVideoPortInterrupt (KINTERRUPT 89c03bb0)
&&&&&&&&& *** ERROR: Module load completed but symbols could not be loaded for \SystemRoot\system32\DRIVERS\HECI.sys
HECI+0x486 (KINTERRUPT 89b39bb0)
&&&&&&&&& USBPORT!USBPORT_InterruptService (KINTERRUPT 898cebb0)
&&&&&&&&& *** ERROR: Module load completed but symbols could not be loaded for \SystemRoot\system32\DRIVERS\HDAudBus.sys
HDAudBus+0x2C12 (KINTERRUPT 898c1bb0)
92: 89c445b4 serial!SerialCIsrSw (KINTERRUPT 89c44578)
94: 89c57044 USBPORT!USBPORT_InterruptService (KINTERRUPT 89c57008)
&&&&&&&&& USBPORT!USBPORT_InterruptService (KINTERRUPT )
a4: 89bedbec USBPORT!USBPORT_InterruptService (KINTERRUPT 89bedbb0)
b1: 89e2867c ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 89e28640)
NDIS!ndisMIsr (KINTERRUPT )
c1: 806e4ac0 hal!HalpBroadcastCallService
d1: 806e3e54 hal!HalpClockInterrupt
e1: 806e5048 hal!HalpIpiHandler
e3: 806e4dac hal!HalpLocalApicErrorService
fd: 806e55a8 hal!HalpProfileInterrupt
fe: 806e5748 hal!HalpPerfInterrupt
可以看到0xe1号中断便是ipi的处理例程HalpIpiHandler,在HalpIpiHandler里又会调用KiIpiServiceRoutine。
那么这些中断又是在哪注册的呢?实际上当系统初始化时,Phase1Initialization -& HalInitSystem 时,会
&&&&&&& HalpSetInternalVector(APIC_PROFILE_VECTOR, HalpProfileInterrupt);
&&&&&&& HalpSetInternalVector(APIC_PERF_VECTOR, HalpPerfInterrupt);
&&&&&&& HalpSetInternalVector(DPC_VECTOR, HalpDispatchInterrupt);
&&&&&&& HalpSetInternalVector(APC_VECTOR, HalpApcInterrupt);
&&&&&&& HalpSetInternalVector(APIC_IPI_VECTOR, HalpIpiHandler);
APIC_IPI_VECTOR便是0xE1(Windows下)。
下面看发送ipi的过程,Windows下通过KiIpiSend -& HalRequestIpi。
下面是HalRequestIpi的代码:
;++
; HalRequestIpi(
;&&&&&& IN ULONG Mask
;&&&&&& );
;Routine Description:
;&&& Requests an interprocessor interrupt
;Arguments:
;&&& Mask - Supplies a mask of the processors to be interrupted
;Return Value:
;&&& None.
APIC_IPI equ (DELIVER_FIXED OR LOGICAL_DESTINATION OR ICR_USE_DEST_FIELD OR APIC_IPI_VECTOR)
cPublicProc _HalRequestIpi ,1
cPublicFpo 1, 0
&&&&&&& mov&&&& eax, [esp+4]&&&&&&&&&&& ; (eax) = Processor bitmask
&&&&&&& shl&&&& eax, DESTINATION_SHIFT
; With an APIC we'll IPI everyone needed at the same time.
; This assumes that:
;&& (mask passed in) == (APIC logical destination mask) Since we've programmed
; the APIC Local Units to use the Processor ID as the Logical ID's this IS true
&&&&&&& pushfd&&&&&&&&&&&&&&&&&&&&&&&&& ; save interrupt mode
&&&&&&& cli&&&&&&&&&&&&&&&&&&&&&&&&&&&& ; disable interrupt
&&&&&&& STALL_WHILE_APIC_BUSY
; Set the destination address, (eax) = Processor bitmask
&&&&&&& mov&&&& dword ptr APIC[LU_INT_CMD_HIGH], eax
&&&&&&& APICFIX edx
; Now issue the command by writing to the Memory Mapped Register
&&&&&&& mov&&&& dword ptr APIC[LU_INT_CMD_LOW], APIC_IPI
;&& Wait for the delivery to happen
;&& KenR why is there a wait here????
&&&&&&& STALL_WHILE_APIC_BUSY
&&&&&&& popfd
&&&&&&& stdRET&&& _HalRequestIpi
stdENDP _HalRequestIpi
&&&&&&& page ,132
&&&&&&& subttl &PC+MP IPI Interrupt Handler&
Linux下则通过send_IPI_mask发送。
下面不妨再看看send_IPI_mask的流程:
static inline void send_IPI_mask(int mask, int vector)
__save_flags(flags);
* Wait for idle.
apic_wait_icr_idle();
* prepare target chip field
cfg = __prepare_ICR2(mask);
apic_write_around(APIC_ICR2, cfg);
* program the ICR
cfg = __prepare_ICR(0, vector);
* Send the IPI. The write to APIC_ICR fires this off.
apic_write_around(APIC_ICR, cfg);
__restore_flags(flags);
static inline int __prepare_ICR (unsigned int shortcut, int vector)
return APIC_DM_FIXED | shortcut | vector | INT_DEST_ADDR_MODE;
static inline int __prepare_ICR2 (unsigned int mask)
return SET_APIC_DEST_FIELD(mask);
讲解下上面代码,CPU内部还有一些中断控制器,APIC_ICR和APIC_ICR2是其中的两个。APIC_ICR2主要用来说明要发送的中断请求的目标。这种目标可以是具体的cpu,也可以是除发送自身外的所有cpu,还可以是包括自身在内、甚至自身。在Linux下的IPI服务例程是在init_IRQ()中注册的。比如CALL_FUNCTION_VECTOR就可以请求目标cpu执行一个指定的函数。要想系统某个CPU发出中断需要通过apic_wait_icr_idle确认或等待APIC_ICR处于空闲状态,然后通过__prepare_ICR和__prepare_ICR2准备要写入的数值。
其实质便是朝APIC_BASE寄存器中写入中断号、cpu标识等。和windows下的流程也差不多。需要说明的是IOAPIC通过读写相应寄存器来进行编程,但这些寄存器又不是通
用寄存器,而是通过映射成内存的物理地址进行访问。
下面是SoBeIt的话:
和PIC一样,控制本地APIC和IO APIC的方法是通过读写该单元中
的相关寄存器。不过和PIC不一样的是,Intel把本地APIC和IO APIC的寄存器都映射到了物
理地址空间,本地APIC默认映射到物理地址0xffe00000,IO APIC默认映射到物理地址
0xfec00000。windows HAL再进一步把本地APIC映射到虚拟地址0xfffe0000,把IO APIC映射
到虚拟地址0xffd06000,也就是说对该地址的读写实际就是对寄存器的读写,本地APIC里几
个重要的寄存有EOI寄存器,任务优先级寄存器(TPR),处理器优先级寄存器(PPR),中断命
令寄存器(ICR,64位),中断请求寄存器(IRR,256位,对应每个向量一位),中断在服务寄
存器(ISR,256位)等。IO APIC里几个重要的寄存器有版本寄存器,I/O寄存器选择寄存器、
I/O窗口寄存器(用要访问的I/O APIC寄存器的索引设置地址I/O寄存器选择寄存器,此时访
问I/O窗口寄存器就是访问被选定的寄存器)还有很重要的是一个IO重定向表,每一个表项是
一个64位寄存器,包括向量和目标模式、传输模式等相关位,每一个表项连接一条IRQ线,
表项的数目随处理器的版本而不一样,在Pentium 4上为24个表项。表项的数目保存在IO
APIC版本寄存器的[16:23]位。APIC系统支持255个中断向量,但Intel保留了0-15向量,可
用的向量是16-255。并引进一个概念叫做任务优先级=中断向量/16,因为保留了16个向量,
所以可用的优先级是2-15。当用一个指定的优先级设置本地APIC中的任务优先级寄存器TPR
后,所有优先级低于TPR中优先级的中断都被屏蔽,是不是很象IRQL的机制?事实上,APIC
HAL里的IRQL机制也就是靠着这个任务优先级寄存器得以实现。同一个任务优先级包括了16
个中断向量,可以进一步细粒度地区分中断的优先级。
有时候cpu还需要发送给除本身之外的cpu中断请求。在Linux下这是send_IPI_allbutself做的,下面看代码:
send_IPI_allbutself-&__send_IPI_shortcut
static inline void __send_IPI_shortcut(unsigned int shortcut, int vector)
* Subtle. In the case of the 'never do double writes' workaround
* we have to lock out interrupts to be safe. As we don't care
* of the value read we use an atomic rmw access to avoid costly
* cli/sti. Otherwise we use an even cheaper single atomic write
* to the APIC.
* Wait for idle.
apic_wait_icr_idle();
* No need to touch the target chip field
cfg = __prepare_ICR(shortcut, vector);
* Send the IPI. The write to APIC_ICR fires this off.
apic_write_around(APIC_ICR, cfg);
参考文献:
http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2534 从IRQ到IRQL(APIC版)
http://blog.chinaunix.net/u2/87729/showart_2251707.html
http://blog.chinaunix.net/u2/76349/showart_2194431.html Kernel硬件中断的初始化流程
/view/e0acc58bd6bedc.html A32上Linux内核中断机制分析
/sysnap/blog/item/e58ab887d73ffc22c65cc396.html IRQ和中断号的关系
/developerworks/cn/linux/kernel/l-ia64irq/index.html IA64
Linux 外部中断处理机制
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=linuxK&Number=707247&o=all&fpart=
kgdb的源代码分析
http://www.unixresources.net/linux/clf/linuxK/archive/00/00/59/03/590356.html
中断向量,IRQ,外部设备的关系分析
/Program/Clanguage/CJQ/Program_53626_5.html Linux 进程调度机制
nt4\ReactOS\Linux源码等也是必不可少的。
以上是我的一些粗略理解,有错误之处大家可以指出来。
一、不得利用本站危害国家安全、泄露国家秘密,不得侵犯国家社会集体的和公民的合法权益,不得利用本站制作、复制和传播不法有害信息!
二、互相尊重,对自己的言论和行为负责。
本文标题:
本页链接:3350人阅读
linux内核(18)
&&&&最近几天学习Linux-2.6平台上的设备驱动,所以要建立内核及内核模块的调试平台.虽然网上有很多相关教程,但多是基于2.6.26以前的通过补丁安装的,过程非常复杂,而且问题比较多.linux从 2.6.26开始已经集成了kgdb,只需要重新编译2.6.26(或更高)内核即可.kgdb安装及模块调试过程也遇到不少问题,网上网下不断的搜索与探索,才算调通.现在记录下来,供朋友们参考.
&&&&首先说一下,开始本打算安装kdb进行内核调试,后来听说kdb只能进行汇编级别的调试,所以放弃,改用kgdb.
2: 系统环境:
虚拟环境:&&&VMWare Workstation 5.5(英文版)
操作系统:&&&CentOS-4.6-i386(原内核2.6.9,将会把内核升级至2.6.26)
注:CentOS 是RedHat的一个社区版本.
&&&& (由于我们采用的linux kernel 2.6.26已经集成kgdb,kgdb再不需要单独下载)
3:系统的安装:
在VMWare中新建一台计算机:
点击 Next选中Custom 点击 Next选中 New-Workstation 5,点击Next选中Linux ,Version选中Other Linux 2.6.x kernel 点击NextVirtual machine name 输入Client(Development) 点击NextProcessors 选中 One, 点击NextMemory 输入256,点击NextNetwork connection 选中Use network address translation(NAT) (选第一个貌似也可以) 点击NextI/O adapter typesSCSI Adapters 选中默认的LSI Logic(这里如果你后面使用了SCSI格式的Disk,编译内核时需要添加相应的驱动,我选择的是IDE的硬盘,kernel默认就支持了)Disk 选中Create a new virtual disk 点击NextVirtual Disk Type 选中IDE,点击NextDisk capacity Disk size 输入80G (下面的Allocate all disk space now不要选中,表示在真正使用才分配磁盘空间, Split disk into 2 GB files项,可不选,如果你的系统分区为fat32格式,最好选中) 点击Next.Disk file ,输入Disk的名称,如:disk1.vmdk ,点击Finish.完成
安装CentOS 4.6(过程略过)
安装完成后,关闭计算机,然后Clone一台同样的计算机.步骤如下:
点击VM-&Clone选中默认的From current state,点击Next选中Create a full clone, 点击NextVirtual Machine name 输入Server(Targe),将克隆的机器命令为目标机.
说明一下,kgdb 需要两台计算机通过串口进行远程调试,两台计算机分别为:
Client(Development):开发机,也称客户机,将在该计算机上进行程序的开发,GDB将在本计算机上运行.用于输入命令控制Server(target)的运行.Server(Target): 目标机,也称服务器,就是被调试的计算机,在Development机上开发好的内核模块程序将拷贝到Target上运行,其运行受到Development命令的控制.
分别为两个系统增加一个串口,以&Output to named pipe&方式,其中:
Client端选择&this end is the client&, &the other end is a virtual machine&Server端选择&this end is the server&, &the other end is a virtual machine&备注: 两个pipe的名称要相同,并且选中下面的Connect at power on,及Advanced里面的Yield CPU on poll
以后的部分,Server上的操作与Client上的操作将以不同的背景色显示,输入的命令将以不同的字体颜色并带下划线显示.请注意:
Server(Target)&输入:&cat /dev/ttyS0
系统输出的信息: hello
Client(Development)&输入:&echo &hello& &/dev/ttuS0
串口添加完成后,使用如果命令测试:
在Server上cat /dev/ttyS0然后到Client上&echo &hello& & /dev/ttyS0这时回到Server上,如果能看到输入的hello,说明串口通讯正常.
4:升级内核2.6.26(添加关于KGDB的选项)
&&&&&&说明一下,这里是要升级Server的内核,因为kgdb是要Server上运行的,但是编译需要在Client完成(或者你也可以在Server上编译,之后再拷贝到Client上),因为调试时Client上的gdb会用到编译的内核及源代码.Client也需要升级,保证Client同Server上的内核一致,这样Client上编译的模块拿到Server上加载就不会有什么问题.
&&&&&&我习惯在windows上下载,然后共享,再到linux使用smbclient连接,拷贝到Linux上.你也可以直接在linux上下载.&
&&&&&&smbclient用法 : smbclient //192.168.0.100/share -Uadministrator 回车后,会提示输入admin的密码.之后就可以通过get获取了 192.168.0.100是我在windows主机的IP,share为共享名,-U后面是用户名
编译Kernel2.6.26
进入Client(Development)系统,将linux-2.6.26.tar.bz2拷贝到/usr/src目录下:
cd /usr/srctar jxvf linux-2.6.26.tar.bz2ln -s linux-2.6.26 linuxcd linuxmake menuconfig
File System --& 下面把ext3,ext2都编译进内核(就是把前面的M变成*)Kernel Hacking --&&
&&&&&&选中Compile the kernel with frame pointers
&&&&&&选中KGDB:kernel debugging with remote gdb&
&&&&&&并确认以下两项也是选中的(他们应该默认是选中的)&
&&&&&&& kernel debugging&
&&&&&&& Compile the kernel with debug info对于其它选项,请按实际情况,或你的要求定制.在其它网友的说明里面,会有Serial port number for KGDB等选项,但是我使用的版本未找到这些选项,所以忽略过.保存退出
make -j10 bzImage
-j10表示使用10个线程进行编译.make modules
编译内核模块make modules_install
安装内核模块make install&
将Client系统中的linux-2.6.26整个目录同步到Server上.
在Client系统上运行下列命令:
cd /usr/src/linux&&scp -r linux-2.6.26 root@Server(Target)IP:/usr/src/
&&&&&系统会提示输入root的密码,输入完了就会开始复制文件了,(这里要注意,如果原系统已经是2.6.26的内核,可以只拷贝arch/i386/boot/bzImage,及System.map文件到Server上,然后修改/boot/grub/grub.conf,但由于我是从2.6.9升级上来,所以需要将整个linux原代码目录拷贝到Server上进行升级)
升级Srever系统的内核
进入Server(Target)系统,usr/src目录:
ln -s linux-2.6.26 linux&&cd linux&&make modules_install
安装内核模块make install
安装完成后,在/boot/目录下会有即个新添加的文件./boot/grub/grub.conf文件也会添加一个新的启动项,我的如下(行号不是文件的一部分):
1 # grub.conf generated by anaconda
3 # Note that you do not have to rerun grub after making changes to this file
4 # NOTICE:
You have a /boot partition.
This means that
all kernel and initrd paths are relative to /boot/, eg.
root (hd0,0)
kernel /vmlinuz-version ro root=/dev/VolGroup00/LogVol00
initrd /initrd-version.img
9 #boot=/dev/hda
10 default=0
11 timeout=5
12 splashimage=(hd0,0)/grub/splash.xpm.gz
13 hiddenmenu
14 title CentOS (2.6.26)
root (hd0,0)
kernel /vmlinuz-2.6.26 ro root=/dev/VolGroup00/LogVol00
initrd /initrd-2.6.26.img
18 title CentOS-4 i386 (2.6.9-67.ELsmp)
root (hd0,0)
kernel /vmlinuz-2.6.9-67.ELsmp ro root=/dev/VolGroup00/LogVol00
initrd /initrd-2.6.9-67.ELsmp.img
&&&&&&&注意里面的NOTICE说明,我的系统/boot是一个独立的分区,所以下面配置的文件路径都是相对于/boot/目录的,像
/vmlinuz-2.6.26,实际到它的绝对位置应该是/boot/vmlinuz-2.6.26. 在你的系统上请根据实际情况处理.&
修改一下grub.conf,修改成下面这样:
1 # grub.conf generated by anaconda
3 # Note that you do not have to rerun grub after making changes to this file
4 # NOTICE:
You have a /boot partition.
This means that
all kernel and initrd paths are relative to /boot/, eg.
root (hd0,0)
kernel /vmlinuz-version ro root=/dev/VolGroup00/LogVol00
initrd /initrd-version.img
9 #boot=/dev/hda
10 default=0
11 timeout=5
12 splashimage=(hd0,0)/grub/splash.xpm.gz
13 hiddenmenu
14 title CentOS (2.6.26)
root (hd0,0)
kernel /vmlinuz-2.6.26 ro root=/dev/VolGroup00/LogVol00 kgdboc=ttyS0,115200
initrd /initrd-2.6.26.img
18 title CentOS (2.6.26) Wait...(kernel debug)
root (hd0,0)
kernel /vmlinuz-2.6.26 ro root=/dev/VolGroup00/LogVol00 kgdboc=ttyS0,115200 kgdbwait
initrd /initrd-2.6.26.img
22 title CentOS-4 i386 (2.6.9-67.ELsmp)
root (hd0,0)
kernel /vmlinuz-2.6.9-67.ELsmp ro root=/dev/VolGroup00/LogVol00
initrd /initrd-2.6.9-67.ELsmp.img
第一个启动项在原来的基础上添加了kgdb的参数kgdboc=ttyS0,115200
kgdboc 的意思是 kgdb over console,这里将kgdb连接的console设置为ttyS0,波特率为115200,如果不在内核启动项中配置该参数,可以在进入系统后执行命令:&
echo ttyS0 & /sys/module/kgdboc/parameters/kgdboc&&&第二个启动项,同第一个使用同一个内核,只是添加了kgdbwait参数
kgdbwait 使 kernel 在启动过程中等待 gdb 的连接。
我的启动菜单如下:
CentOS(2.6.26)CentOS(2.6.26)Wait...(kernel debug)CentOS-4 i386(2.6.9-67.ELsmp)
调用内核模块,就选择第一个,如果要调试内核启动过程,选择第二个.
5.内核调试
重启Server,通过启动菜单第二项CentOS(2.6.26)Wait...(kernel debug)进入系统. 只到系统出现:
&&&&kgdb: Registered I/O driver kgdboc
&&&&kgdb: Waiting for connection from remote gdb
进入Client系统.
cd /usr/src/linux&&gdb vmlinux&&
启动gdb开始准备调试:输出大致如下:
GNU gdb Red Hat Linux (6.3.0.0-1.153.el4rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type &show copying& to see the conditions.
There is absolutely no warranty for GDB.
Type &show warranty& for details.
This GDB was configured as &i386-redhat-linux-gnu&...Using host libthread_db
library &/lib/tls/libthread_db.so.1&
(gdb)&set remotebaud 115200(gdb)&target remote /dev/ttyS0
注意:有的文章讲:因为vmware的named piped不能被gdb直接使用,需要使用 socat -d -d /tmp/com_1 /dev/ttyS0转换,然后使用转换后的设备. socat需要自己下载安装. 但在我的环境中,直接使用并没有出错.
执行该命令后输出如下:
Remote debugging using /dev/ttyS0
kgdb_breakpoint () at kernel/kgdb.c:1674
wmb(); /*Sync point after breakpoint */
warning: shared library handler failed to enable breakpoint
看到上面的内容说明已经连接成功,但Server上依然是假死状态,这时你可以像使用本地gdb一样设置断点(break),单步执行(step),或其它命令.(gdb) cont
继续执行,Server就继续下面的系统初始化了.
系统启动完成后的内核调试:
&&&&进入Server后,执行命令
echo g & /proc/sysrq-trigger&
系统同样会中断,进入假死状态,等待远程gdb的连接.KGDB可能会输出如下信息:
SysRq: GDB
上面的命令(echo g & /proc/sysrq-trigger)可以有一个快捷键(ALT-SysRq-G)代替,当然前提是你编译内核时需要选中相关选项,并且需要修改配置文件:/etc/sysctl.conf , 我用了一下,不太好用.因为有的桌面系统中PrintScreen/SysRq键是用于截屏的.所以还是直接用命令来的好!&
我在~/.bashrc中添加了一句(添加完保存后,要执行source ~/.bashrc应用该配置):
alias debug='echo g & /proc/sysrq-trigger'
之后就可以直接输入debug来使内核进入调试状态.
Server进入调试状态后,转换到Client系统,重复上面的步骤.
6. Linux内核模块(设备驱动)的调试
编写内核模块,及Makefile
&&&&&&我使用的例子是Linux Device Driver 3中的例子scull. 你可以从.
进入Client系统,解压example.tar.gz,将其中的example/scull目录拷贝到/root/scull&
cd /root/scull&&make&&
编译应该会出错,因为Linux Device Driver 3的例子程序是基于2.6.10内核的,请:
编译scull驱动,完成后,scull目录应该多出了scull.ko及其它中间文件.scp scull.ko root@targetIp:/root/
将scull.ko模块文件拷贝到Server上.系统应该会提示输入密码:
root@targetIp's password:
输入正确密码后,应该能看到如下信息,表示复制成功了:
258k 258.0kb/s 00:00
进入Server系统输入:
cd /root/&&insmod scull.ko&&
加载scull模块.cat /sys/module/globalmem/sections/.text&&
显示scull模块的.text段地址.运行该命令后,会返回一个16进制的地址,如:
0xd099a000
echo g & /proc/sysrq-trigger&
现在Server系统变成等待状态.
再次进入Client系统:
cd /usr/src/linux&&gdb vmlinux&&(gdb)&set remotebaud 115200 &(gdb)&target remote /dev/ttyS0
Remote debugging using /dev/ttyS0
kgdb_breakpoint () at kernel/kgdb.c:1674
wmb(); /*Sync point after breakpoint */
warning: shared library handler failed to enable breakpoint
出现上面的信息表示连接成功,但此时还不可以设置断点.我试了N次,现在设置端点后,无论Server上对scull做什么操作,Client上的gdb都不会停止.也有人说这是gdb的BUG,gdb无法正常解析内核模块的符号信息. 说是下载kgdb网站上的gdbmod可以解决该问题,但我试了之后发现根本没有用. 所以只能通过命令手动加载相关符号信息:(gdb)&add-symbol-file /root/scull/scull.ko 0xd099a000&
注意: 0xd099a000地址是在上面获取的,命令输入完了,系统会有如下提示信息(第二行后面的y是自己输入的:
add symbol table from file &/root/scull/scull.ko& at .text_addr = 0xd099a000
Reading symbols from /root/scull/scull.ko...done.
(gdb)break scull_write
Breakpoint 1 at 0xd099a2d9: file /root/scull/main.c,line 338.
(gdb)break scull_read
Breakpoint 2 at 0xd099a1a2: file /root/scull/main.c,line 294
(gdb)cont&&&
Continuing
Client上的工作暂停,回到Server系统上:
Server现在处于运行状态,在Server上运行下面的命令:
cat /proc/devices | grep &scull&
查看scull模块分配的major.我的系统中返回如下:
253 scullp
253 sculla
mknod /dev/scull c 253 0
253是刚才查询到的版本号.echo &this is a test & & /dev/scull
测试输入函数:scull_write. 该命令输入完成后,进程应该会停下来, 请切换到Client系统.cat /dev/scull
测试输出函数:scull_read. 该命令输入完成后,进程应该会停下来, 请切换到Client系统.
回到Client系统,应该能看到gdb的输出信息:
Breakpoint 1, scull_write (filp=0xce5870c0,buf=0xb7f44000
&this is a test/nias | /usr/bin/which --tty-only --read-alias
--show-dot --show-tilde'/n&,count=15,f_pos=0xce5c5f9c)
at /root/scull/main.c:338
现在就可以像调试本地程序一样调试scull.ko模块了.
以同样的方法也可以调试其它函数.(初始化函数暂时没想到办法调试,因为使用这种方法需要先加载后,才可以进行调试)
你可以自由使用和转载本文档,转载时请注明出处. ()
如果可以,我想写一个脚本自动完成这个添加符号文件的过程,简化调试过程.
1 开启虚拟机,虚拟机运行到 kgdb: Waiting for connection from remote gdb
2. 在Host机上运行: socat tcp-listen:8888 /tmp/vbox2, 其中/tmp/vbox2为管道文件,它是目标机串口的重定向目的文件,socat将这个管道文件又重定向到tcp socket的8888端口。
3. 开启一个新的虚拟终端,cd path/to/kernel/source/tree, 然后执行gdb ./vmlinux
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &&
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.& Type &show copying&
and &show warranty& for details.
This GDB was configured as &i486-linux-gnu&...
(gdb) set-remote
set remote baud rate to 115200c/s
set remote target to local tcp socket
kgdb_breakpoint () at kernel/kgdb.c:1721
1721&&&&&&& wmb(); /* Sync point after breakpoint */
Continuing.
目标机会一直启动,直到提示输入用户名密码。
4. 进入目标机,输入用户名密码(推荐使用字符界面下的root用户),输入g命令,目标机被断下,控制移交到Host机中的gdb中。(目标机root的用户目录中的.bashrc中添加一行alias g='echo g&/proc/sysrq-trigger')
5. 在Host机中的gdb中
(gdb) set-mod-break
set breakpoint in system module init function
Breakpoint 1 at 0xc014bac5: file kernel/module.c, line 2288.
Continuing.
6. 在目标机中
insmod klogger2.ko
目标机再次断下,控制权移交Host机中的gdb
7. 在Host机中的gdb中
[New Thread 4693]
[Switching to Thread 4693]
Breakpoint 1, sys_init_module (umod=0x0, len=0, uargs=0x0)
at kernel/module.c:2288
2288&&&&&&& if (mod-&init != NULL)
(gdb) print-mod-segment
Name:.note.gnu.build-id Address:0xdf977058
Name:.text Address:0xdf975000
Name:.rodata Address:0xdf977080
Name:.rodata.str1.4 Address:0xdf9774b4
Name:.rodata.str1.1 Address:0xdf977522
Name:.parainstructions Address:0xdf977a00
Name:.data Address:0xdf978440
Name:.gnu.linkonce.this_module Address:0xdf978480
Name:.bss Address:0xdf978a00
Name:.symtab Address:0xdf977a08
Name:.strtab Address:0xdf978078
(gdb) add-symbol-file /home/done/programs/linux-kernel/vlogger/klogger2.ko 0xdf975000 -s .data 0xdf978440 -s .bss 0xdf978a00
add symbol table from file &/home/done/programs/linux-kernel/vlogger/klogger2.ko& at
.text_addr = 0xdf975000
.data_addr = 0xdf978440
.bss_addr = 0xdf978a00
(y or n) y
Reading symbols from /home/done/programs/linux-kernel/vlogger/klogger2.ko...done.
(gdb) b hook_init
Breakpoint 2 at 0xdf976d19: file /home/done/programs/linux-kernel/vlogger/hook.c, line 255.
你可以调试自己些的LKM模块了
附gdb的初始化配置文件~/.gdbinit
define set-remote
echo set remote baud rate to 115200c/s\n
set remotebaud 115200
echo set remote target to local tcp socket\n
target remote tcp:localhost:8888
define set-mod-break
echo set breakpoint in system module init function\n
break kernel/module.c:2288
define print-mod-segment
set $sect_num=mod-&sect_attrs-&nsections
set $cur=0
while $cur & $sect_num
printf &Name:%-s Address:0x%x\n&,mod-&sect_attrs-&attrs[$cur]-&name,mod-&sect_attrs-&attrs[$cur]-&address
set $cur=$cur+1
后记:gdb的调试脚本真难写,简单的字符串变量连接和等价判断都显得十分困难,不知道是我水平太差还是gdb的脚本功能太弱,总之比起Windbg来说,内核调试困难程度上了个等级。
使用kgdb调试linux内核及内核模块
创建时间:
文章属性:原创
文章提交:&(_)
作者:xcspy成员 ladybug
1. 几种内核调试工具比较
kdb:只能在汇编代码级进行调试;
&&&& 优点是不需要两台机器进行调试。
gdb:在调试模块时缺少一些至关重要的功能,它可用来查看内核的运行情况,包括反汇编内核函数。
kgdb:能很方便的在源码级对内核进行调试,缺点是kgdb只能进行远程调试,它需要一根串口线及两台机器来调试内核(也可以是在同一台主机上用vmware软件运行两个操作系统来调试)
使用kdb和gdb调试内核的方法相对比较简单,这里只描述如何使用kgdb来调试内核。
2.软硬件准备
一台开发机developer(192.168.16.5 com1),一台测试机target(192.168.16.30 com2),都预装redhat 9;一根串口线
下载以下软件包:
linux内核2.4.23&&&&&&&& linux-2.4.23.tar.bz2
kgdb内核补丁1.9版&&&&&& linux-2.4.23-kgdb-1.9.patch
可调试内核模块的gdb&&&& gdbmod-1.9.bz2
3.1 测试串口线
物理连接好串口线后,使用一下命令进行测试,stty可以对串口参数进行设置
在developer上执行:
stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
echo hello & /dev/ttyS0
在target上执行:
stty ispeed 115200 ospeed 115200 -F /dev/ttyS1
cat /dev/ttyS1
串口线没问题的话在target的屏幕上显示hello
3.2 安装与配置
3.2.1 安装
下载linux-2.4.23.tar.bz2,linux-2.4.23-kgdb-1.9.patch,gdbmod-1.9.bz2到developer的/home/liangjian目录
*在developer机器上
#cd /home/liangjian
#bunzip2 linux-2.4.23.tar.bz2
#tar -xvf linux-2.4.23.tar
#bunzip2 gdbmod-1.9.bz2
#cp gdbmod-1.9 /usr/local/bin
#cd linux-2.4.23
#patch -p1 & ../linux-2.4.23-kgdb-1.9.patch
#make menuconfig
在Kernel hacking配置项中将以下三项编译进内核
KGDB: Remote (serial) kernel debugging with gdb
KGDB: Thread analysis
KGDB: Console messages through gdb
注意在编译内核的时候需要加上-g选项
#make bzImage
使用scp进行将相关文件拷贝到target上(当然也可以使用其它的网络工具)
#scp arch/i386/boot/bzImage root@192.168.16.30:/boot/vmlinuz-2.4.23-kgdb
#scp System.map root@192.168.16.30:/boot/System.map-2.4.23-kgdb
#scp arch/i386/kernel/gdbstart&&root@192.168.16.30:/sbin
gdbstart为kgdb提供的一个工具,用于激活内核钩子,使内核处于调试状态
3.2.2 配置
*在developer机器上
在内核源码目录下编辑一文件.gdbinit(该文件用以对gdb进行初始化),内容如下:
#vi .gdbinit
define rmt
set remotebaud 115200
target remote /dev/ttyS0
以上在.gdbinit中定义了一个宏rmt,该宏主要是设置使用的串口号和速率
*在target机器上
编辑/etc/grub.conf文件,加入以下行:
#vi /etc/grub.conf
title Red Hat Linux (2.4.23-kgdb)
&&&&root (hd0,0)
&&&&kernel /boot/vmlinuz-2.4.23-kgdb ro root=/dev/hda1
在root目录下建立一个脚本文件debugkernel,内容如下:
#!/bin/bash
gdbstart -s 115200 -t /dev/ttyS1 &&EOF
#chmod +x debugkernel
这个脚本主要是调用gdbstart程序设置target机上使用的串口及其速率,并使内核处于调试状态
3.3 开始调试
target上的内核或内核模块处于调试状态时,可以查看其变量、设置断点、查看堆栈等,并且是源码级的调试,和用gdb调试用户程序一样
3.3.1 内核启动后调试
*在target机器上
重启系统,选择以 2.4.23-kgdb内核启动,启动完成后运行debugkenel,
这时内核将停止运行,在控制台屏幕上显示信息,并等待来自developer的
About to activate GDB stub in the kernel on /dev/ttyS1
Waiting for connection from remote gdb...
*在developer机器上
#cd /home/liangjian/linux-2.4.23
# gdb vmlinux
GNU gdb Red Hat Linux (5.3post-0.rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type &show copying& to see the conditions.
There is absolutely no warranty for GDB.&&Type &show warranty& for details.
This GDB was configured as &i386-redhat-linux-gnu&...
breakpoint () at kgdbstub.c:1005
1005&&&&&&&&&&&&&&&&&&&&atomic_set(&kgdb_setting_breakpoint, 0);
这时target上的内核处于调试状态,可以查看其变量、设置断点、查看堆栈等,和用gdb调试用户程序一样
#0&&breakpoint () at kgdbstub.c:1005
#1&&0xc0387f48 in init_task_union ()
#2&&0xc01bc867 in gdb_interrupt (irq=3, dev_id=0x0, regs=0xc0387f98) at
gdbserial.c:158
#3&&0xc010937b in handle_IRQ_event (irq=3, regs=0xc0387f98, action=0xce5a9860)
at irq.c:452
#4&&0xc0109597 in do_IRQ (regs=
&&&&&&{ebx = -, ecx = -1, edx = -, esi = -, edi
= -, ebp = -, eax = 0, xds
= -, xes = -, orig_eax = -253, eip = -, xcs =
16, eflags = 582, esp = -, xss = -}) at irq.c:639
#5&&0xc010c0e8 in call_do_IRQ ()
查看jiffies变量的值
(gdb) p jiffies
$1 = 76153
如果想让target上的内核继续运行,执行continue命令
(gdb) continue
Continuing.
3.3.2 内核在引导时调试
kgdb可以在内核引导时就对其进行调试,但并不是所有引导过程都是可调试的,如在kgdb 1.9版中,它在init/main.c的start_kernel()函数中插入以下代码:
start_kernel()
&&&&......
&&&&&&&&smp_init();
#ifdef CONFIG_KGDB
&&&&&&&&if (gdb_enter) {
&&&&&&&&&&&&&&&&gdb_hook();&&&&&&&&&&&& /* right at boot time */
&&&&......
所以在smp_init()之前的初始化引导过程是不能调试的。
另外要想让target的内核在引导时就处于调试状态,需要修改其/etc/grub.conf文件为如下形式:
title Red Hat Linux (2.4.23-kgdb)
&&&&root (hd0,0)
&&&&kernel /boot/vmlinuz-2.4.23-kgdb ro root=/dev/hda1 gdb gdbttyS=1 gdbbaud=115200
*在target机器上
引导2.4.23-kgdb内核,内核将在短暂的运行后暂停并进入调试状态,打印如下信息:
Waiting for connection from remote gdb...
*在developer机器上
#cd /home/liangjian/linux-2.4.23
# gdb vmlinux
GNU gdb Red Hat Linux (5.3post-0.rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type &show copying& to see the conditions.
There is absolutely no warranty for GDB.&&Type &show warranty& for details.
This GDB was configured as &i386-redhat-linux-gnu&...
breakpoint () at kgdbstub.c:1005
1005&&&&&&&&&&&&&&&&&&&&atomic_set(&kgdb_setting_breakpoint, 0);
查看当前堆栈
#0&&breakpoint () at kgdbstub.c:1005
#1&&0xc0387fe0 in init_task_union ()
#2&&0xc01bc984 in gdb_hook () at gdbserial.c:250
#3&&0xc0388898 in start_kernel () at init/main.c:443
在do_basic_setup函数处设置断点,并让内核恢复运行
(gdb) b do_basic_setup
Breakpoint 1 at 0xc0388913: file current.h, line 9.
(gdb) continue
Continuing.
[New Thread 1]
[Switching to Thread 1]
Breakpoint 1, do_basic_setup () at current.h:9
9&&&&&&&&&&&&&& __asm__(&andl %%esp,%0; &:&=r& (current) : &0& (~8191UL));
内核在do_basic_setup断点处停止运行后查看当前堆栈
#0&&do_basic_setup () at current.h:9
3.3.3 内核模块调试调试
要想调试内核模块,需要相应的gdb支持,kgdb的主页上提供了一个工具gdbmod,它修正了gdb 6.0在解析模块地址时的错误,可以用来正确的调试内核模块
*在developer机器上
写了个测试用的内核模块orig,如下:
void xcspy_func()
&&&&printk(&&1&xcspy_func\n&);
&&&&printk(&&1&aaaaaaaaaaa\n&);
int xcspy_init()
&&&&printk(&&1&xcspy_init_module\n&);
&&&&return 0;
void xcspy_exit()
&&&&printk(&&1&xcspy_cleanup_module\n&);
module_init(xcspy_init);
module_exit(xcspy_exit);
编译该模块:
#cd /home/liangjian/lkm
#gcc -D__KERNEL__ -DMODULE -I/home/liangjian/linux-2.4.23/include -O -Wall -g -c -o orig.o orig.c
#scp orig.o root@192.168.16.30:/root
开始调试:
# gdbmod vmlinux
GNU gdb 6.0
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type &show copying& to see the conditions.
There is absolutely no warranty for GDB.&&Type &show warranty& for details.
This GDB was configured as &i686-pc-linux-gnu&...
设置符号文件的搜索路径
(gdb) set solib-search-path /home/liangjian/lkm
breakpoint () at kgdbstub.c:1005
1005&&&&&&&&&&&&&&&&&&&&atomic_set(&kgdb_setting_breakpoint, 0);
设置断点使得可以调试内核模块的init函数,查内核源码可知,内核是通过module.c文件的第566行(sys_init_module函数中)mod-&init来调用模块的init函数的
(gdb) b module.c:566
Breakpoint 1 at 0xc011cd83: file module.c, line 566.
Continuing.
[New Thread 1352]
[Switching to Thread 1352]
这时在target机器上执行insmod orig.o,developer则相应的在断点处被暂停,如下
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
Breakpoint 1, sys_init_module (name_user=0xc03401bc &\001&,
mod_user=0x80904d8) at module.c:566
566&&&&&&&&&&&& if (mod-&init && (error = mod-&init()) != 0) {
使用step命令进入模块的init函数
(gdb) step
xcspy_init () at orig.c:12
12&&&&&&&&&&&&&&printk(&&1&xcspy_init_module\n&);
调试内核模块的非init函数相对比较简单,只要先在target上执行insmod orig.o,这时由于模块的符号被加载,可以直接在developer的gdb中对想调试的模块函数设置断点,如bt xcspy_func,后面当xcspy_func被调用时就进入了调试状态。
如果想调试内核模块的init函数,由于在执行insmod之前模块的符号还没有被加载,不能直接对模块的init函数设置断点,所以相对来说要困难一些。可以采用两种变通的方法:1,采用上面介绍的在内核调用模块的init函数被调用之前的某处插入断点,如bt sys_init_module()或bt module.c:566;2,在developer上让内核处于运行状态,在target上先执行一遍insmod orig.o,这时orig.o的符号已经被加载到内存中,可以直接在developer的gdb中对模块的init函数设置断点,如bt
xcspy_init,然后在target上rmmod orig.o,当下次在target上重新加载orig.o时就进入了调试状态,developer在xcspy_init处被暂停。
重启服务器
#&echo&1&&&/proc/sys/kernel/sysrq&&
#&echo&b&&&/proc/sysrq-trigger&&
1. /proc/sys/kernel/sysrq
&&&&&& 向sysrq文件中写入1是为了开启SysRq功能。根据linux/Documentations/sysrq.txt中所说:SysRq代表的是Magic System Request Key。开启了这个功能以后,只要内核没有挂掉,它就会响应你要求的任何操作。但是这需要内核支持(CONFIG_MAGIC_SYSRQ选项)。向/proc/sys/kernel/sysrq中写入0是关闭sysrq功能,写入1是开启,其他选项请参考sysrq.txt。需要注意的是,/proc/sys/kernel/sysrq中的值只影响键盘的操作。
&&&&&& 那么怎么使用SysRq键呢?
&&&&&& 在x86平台上,组合键”&ALT& + SysRq + &command key&“组成SysRq键以完成各种功能。但是,在一些键盘上可能没有SysRq键。SysRq键实际上就是”Print Screen“键。并且可能有些键盘不支持同时按三个按键,所以你可以”按住ALT键“,”按一下SysRq键“,再”按一下&command key&键“,如果你运气好的话,这个会有效果的。不过放心,现在的键盘一般都支持同时按3个或3个以上的键。
&&&&&& &command key&有很多,这里只挑几个来说,其他的可以参考sysrq.txt文件。
'b' —— 将会立即重启系统,并且不会管你有没有数据没有写回磁盘,也不卸载磁盘,而是完完全全的立即关机
'o' —— 将会关机
's' —— 将会同步所有以挂在的文件系统
'u' —— 将会重新将所有的文件系统挂在为只读属性
2. /proc/sysrq-trigger
&&&&&& 从文件名字就可以看出来这两个是有关系的。写入/proc/sysrq-trigger中的字符其实就是sysrq.txt中说的&command key&键所对应的字符,其功能也和上述一样。
所以,这两行命令先开启SysRq功能,然后用'b'命令让计算机立刻重启。
/proc/sysrq-trigger该文件能做些什么事情呢?&
# 立即重新启动计算机 ()
echo &b& & /proc/sysrq-trigger
# 立即关闭计算机(shuts off the system)
echo &o& & /proc/sysrq-trigger
# 导出内存分配的信息 (可以用/var/log/message 查看)()&
echo &m& & /proc/sysrq-trigger
# 导出当前CPU寄存器信息和标志位的信息()
echo &p& & /proc/sysrq-trigger
# 导出线程状态信息 (
echo &t& & /proc/sysrq-trigger
# 故意让系统崩溃 ()
echo &c& & /proc/sysrq-trigger
# 立即重新挂载所有的文件系统 (
echo &s& & /proc/sysrq-trigger
# 立即重新挂载所有的文件系统为只读 (
echo &u& & /proc/sysrq-trigger
呵呵,此外还有两个,类似于强制注销的功能
由于很多原因,没有将调试器放在内核中,这也使得内核调试变得比较困难,不过还好有类似等辅助工具,通过它们,我们也可以完成内核的调试。
目标机系统:,目标机使用的是虚拟机系统
开发机系统:开发机系统用什么不是很重要,不一定必须相同。
&&目标机环境准备
在目标机上需要做的主要是编译并使用调试版本的内核。
&&&&&&&编译内核,以上的内核内置,推荐使用,并保证下的,,选中;
&&&&&&&使用调试版本的内核,并设置为启动时等待连接
以上两点可以参考文章:《VirtualBox下Ubuntu8.10的KGDB内核调试》
可以将目标机的启动方式改为字符界面启动,用如下的方法:在目录下建立文件,并在其中加入下面一行:
然后将下的改成:
/etc/rc3.d/K30gdm
由于使用了字符界面启动,因此将只能以命令的形式使用,组合键不再有效,而只能通过命令。可以在中设置一个,以免每次都要输入一长串命令。由于上述命令需要权限,因此推荐修改用户的配置文件,在最后加入一行:
&&开发机环境准备
由于目标机使用的是虚拟机,因此开发机需要做一些虚拟机相关的配置尤其是串口设置(可以参考文章:《Ubuntu下VirtualBox虚拟机串口设置》)。其次是调试器的一些设置。
将目标机中编译好的内核树到开发机下面(注意符号连接的指向改变),切换到内核树目录下,建立一下三个脚本文件:
文件(用于连接到目标机的脚本):
文件(用于设置模块加载时的断点):
文件(用于显示所加载模块在内存中的各个段的地址):
&&启动虚拟机中的目标机系统,系统在运行到等待连接时停下;
&&在开发机中使用将主机中的管道(与目标机的串口相连)与主机中端口为的相连;
&&进入到开发机中的内核树目录下,使用进入调试模式;
&&在中执行连接到目标机,并输入()命令继续启动目标机;
&&目标机起动后,在目标机中输入命令的别名命令,断下目标机,并将控制权限转交给开发机中的;
&&进入开发机的,执行命令,在模块加载处设置断点,并输入命令继续执行目标机;
&&在目标机中使用命令加载模块,会自动产生断点,执行再次返回到开发机中的;
&&在开发机的中执行,显示刚加载的模块的各个段在内存中的地址,然后继续在中执行命令,是段的地址,并确定加载符号;
&&这样就可以在开发中的中设置断点了,命令将在模块初始化函数入口点设置断点,设置断点后用命令执行目标机,接着会在处触发断点,然后可以用,,等命令进行单步调试和变量查看了。
创建xen虚拟机:
virt-install&&-n&bootmgr&-r200&-f&/root/xen/bootmgr.img&-s&1&-l&
virt-install&&-n&centos&-r512&-f&/root/xen/centos.img&-s&6&-l&(注意:安装的时候ip要输入和200同个网段的)
3.5. 常见命令
xm create /path/to/config_file
xm shutdown DomainName
xm reboot DomainName
xm pause DomainName
xm resume DomainName
xm console DomainName
更多命令请使用 xm –help查看
mkdir&-p&/data0/software/
cd&/data0/software/
#&32位系统&centos&5.x&
wget&http://pkgs.repoforge.org/qemu/qemu-img-0.14.1-2.el5.rfx.i386.rpm
wget&http://pkgs.repoforge.org/qemu/qemu-0.14.1-2.el5.rfx.i386.rpm
rpm&-ivh&qemu-img-0.14.1-2.el5.rfx.i386.rpm
rpm&-ivh&qemu-0.14.1-2.el5.rfx.i386.rpm
#&64位系统&centos&5.x
wget&http://pkgs.repoforge.org/qemu/qemu-img-0.14.1-2.el5.rfx.x86_64.rpm
wget&http://pkgs.repoforge.org/qemu/qemu-0.14.1-2.el5.rfx.x86_64.rpm
rpm&-ivh&qemu-img-0.14.1-2.el5.rfx.x86_64.rpm
rpm&-ivh&qemu-0.14.1-2.el5.rfx.x86_64.rpm
#&32位系统&&centos&6.x
wget&http://pkgs.repoforge.org/qemu/qemu-0.15.0-1.el6.rfx.i686.rpm
wget&http://pkgs.repoforge.org/qemu/qemu-img-0.15.0-1.el6.rfx.i686.rpm
rpm&-ivh&qemu-0.15.0-1.el6.rfx.i686.rpm
rpm&-ivh&qemu-img-0.15.0-1.el6.rfx.i686.rpm
#&64位系统&&centos&6.x
wget&http://pkgs.repoforge.org/qemu/qemu-0.15.0-1.el6.rfx.x86_64.rpm
wget&http://pkgs.repoforge.org/qemu/qemu-img-0.15.0-1.el6.rfx.x86_64.rpm
rpm&-ivh&qemu-0.15.0-1.el6.rfx.x86_64.rpm
rpm&-ivh&qemu-img-0.15.0-1.el6.rfx.x86_64.rpm
#安装完毕,qemu的bios的启动信息在&/usr/share/qemu下
#创建个虚拟机目录
mkdir&/data0/software/win2003
cd&/data0/software/win2003
#创建个10G硬盘镜像
qemu-img&create&disk.10G&10G
#假设win2003镜像位置在当前目录
#启动虚拟机的命令
#boot&d&是从光驱启动&boot&c&是硬盘
qemu&-L&/usr/share/qemu&-m&512&-hda&disk.10G&-localtime&-boot&d&-cdrom&./win2003.iso&&-localtime&-net&nic,vlan=0&-net&tap,vlan=0,ifname=tap0,script=no&-net&user
-boot d :从光驱引导 a(软盘引导) c(硬盘引导) d(光驱引导)
-cdrom : ISO文件,也可以直接使用光驱设备(/dev/cdrom)...别忘了插入光盘
-hda : 就是虚拟机里的硬盘啦,也就是刚才qemu-img创建出的东东。
-enable-audio : 声卡支持
其它参数:
-full-screen :Start in full screen.
-usb: Enable the USB driver (will be the default soon)
-kernel bzImage:Use bzImage as kernel image.
-append cmdline:Use cmdline as kernel command line
-initrd file:Use file as initial ram disk.
QEMU&提供两种模拟模式。第一种,系统模拟,安装完全的虚拟机。运行在该系统的软件看到的计算机与主机系统完全不同 — 例如,可以在实际的 x86-64 openSUSE 计算机上运行 PowerPC Debian 系统。用户模式模拟没有这么完整。这种方式下,QEMU&模拟库会用于每个二进制文件,他们将主计算机看成自己的,因此
PowerPC 二进制文件能看到主机 x86-64 openSUSE 系统的 /etc 目录和其他配置文件。用户模式模拟能简化对本地资源、网络等的访问。
每个模拟方式都有其安装要求。对于系统模拟,安装客户操作系统就像安装一台单独的计算机(下载并使用预先配置的磁盘镜像是一个替代方法)。对于用户空间模拟,不需要安装整个系统,但要考虑安装使用软件所需的支持库。也许还需要配置跨编译器以生成想要测试的二进制文件。
#&按&ctrl&+&alt&释放虚拟机中的鼠标
#网络桥虚拟网卡:
yum&install&tunctl
yum&install&bridge-utils
#rc.local启动脚本
iptables&-t&nat&-A&POSTROUTING&-o&eth0&-s&10.0.67.0/24&-j&MASQUERADE
tunctl&-t&tap0
ifconfig&tap0&10.0.67.1&netmask&255.255.255.0
#虚拟机里网关设置成&10.0.67.1&即可联网
___________________________________________________________
#以下为有问题后检查用
echo&1&/proc/sys/net/ipv4/ip_forward
查看&tun模块
lsmod&|&grep&tun
modprobe&tun
本地虚拟机启动方法:
.\qemu-system-i386.exe&-L&.\Bios&-m&512&-hda&.\bootmgr-s001.vmdk&-localtime&-boot&d
17.3. 使用 qemu-img
qemu-img 命令行工具是 Xen 和 KVM 用来格式化各种文件系统的。可使用 qemu-img 格式化虚拟客户端映像、附加存储设备以及网络存储。qemu-img 选项及用法如下。
格式化并创建新映像或者设备
创建新磁盘映像文件名为 size,格式为 format。
# qemu-img create [-6] [-e] [-b base_image] [-f format] filename [size]
If base_image is specified, then the image will record only the differences from base_image. No size needs to be specified in this case. base_image will never be modified unless you use the &commit& monitor command.
将现有映像转换成另一种格式
转换选项是将可识别格式转换为另一个映像格式。
命令格式:
# qemu-img convert [-c] [-e] [-f format] filename [-O output_format] output_filename
convert the disk image filename to disk image output_filename using format output_format. it can be optionally encrypted (&-e& option) or compressed (&-c& option).
only the format &qcow& supports encryption or compression. the compression is read-only. it means that if a compressed sector is rewritten, then it is rewritten as uncompressed data.
加密法是使用非常安全的 128 位密钥的 AES 格式。使用长密码(16 个字符以上)获得最大程度的保护。
当使用可增大的映像格式,比如 qcow 或 cow 时,映像转换可帮助您获得较小的映像。在目的映像中可检测并压缩空白字段。
获得映像信息
info 参数显示磁盘映像信息。info 选项的格式如下:
# qemu-img info [-f format] filename
给出磁盘映像文件名信息。使用它可获得在磁盘中保留空间大小,它可能与显示的大小有所不同。如果在磁盘映像中保存有 vm 快照,则此时也会显示。
支持格式
映像格式通常是自动获取的。支持以下格式:
& & Raw 磁盘映像格式(默认)。这个格式的优点是可以简单、容易地导出到其它模拟器中。如果您的文件系统支持中断(例如在 Linux 中的 ext2 或者 ext3 以及 Windows 中的 NTFS),那么只有写入的字段会占用空间。使用 qemu-img info 了解 Unix/Linux 中映像或者 ls -ls 使用的实际大小。&
& & QEMU 映像格式,最万能的格式。使用它可获得较小映像(如果您的系统不支持中断,例如在 Windows 中,它会很有用)、额外的 AES 加密法、zlib 压缩以及对多 VM 快照的支持。&
& & 旧的 QEMU 映像格式。只用于与旧版本兼容。&
& & 写入映像格式的用户模式 Linux 副本。包含 cow 格式的目的只是为了与前面的版本兼容。它无法在 Windows 中使用。&
& & VMware 3 和 4 兼容映像格式。&
& & Linux 压缩回送映像,只有在重复使用直接压缩的 CD-ROM 映像时有用,比如在 Knoppix CD-ROM 中。&
将img文件转化为qcow2_cow文件形式:
qemu-img convert root.img -O qcow2 kvm-centos-5.4-64-weibo_duilie_php5.2.14.qcow2_cow
反之一样。
&&& 按需支付是云计算服务众所周知的优势之一。
&&& 以EC2为例,它所提供的三种服务方式中,On—Demond&instance提供以Gb/小时为颗粒度的计费单位,无须预付费,也无需承诺试用时长,并可以通过Auto
Scaling功能自动增删所租用的虚拟资源,做到了按需支付,我们目前所宣传的也基本上指的是这种模式。
&&& 另外一种Reserved&Instance收费方式与On-demond&instance本质一样,只不过ReservedInstance需要事先承诺合同时长,如一年或三年,并需要交纳一定的一次性费用,但是此后在实际使用中仍然按照小时计费,而且单价要比On-demond&instance平均降低50%。在服务等级上,Reserved&instance也要高于On-demond&instance,亚马逊保证Reserved&instance用户随时可以获得其所需要的服务资源,而On-demond&instance则没有这方面的保证,当然,这种情况也很少出现。
&&& 最后,说说其第三种Spot&Instance,也是最有意思的一种。在这种服务中,用户可以自己定价,定下用户愿意接受的最高价格,来租用EC2服务的闲散资源。亚马逊根据供需情况会周期性的发布即时价格,当用户最高限价高于其即时价格时,服务进行,且实际支付价格为系统即时价格。当用户最高限价低于即时价格时,系统自动终止服务,待即时价格低于用户最高限价时服务再次启动。这对于用户的预算是一个更灵活的保证方式。这种模式更适合于需要大量计算能力但对计算响应要求不高的用户,如科学计算等。当然,用户需要自行保证使用Spot&instance的应用对于随时宕机具有调整能力
使用QEMU+GDB能够实现源代码级的内核调试,但是,存在一个问题——当内核允许中断时,单步命令(n与s)会进入时钟中断。通过浏览QEMU的源代码,大体把原因找了出来。
单步命令(n与s)在gdb远程调试通讯协议中是s(参看info gdb),qemu的gdb stub在受到s命令后将虚拟CPU进入单步状态,但是在接收到s命令前,qemu的虚拟CPU是停止的(在等待gdb的命令),注意,这个时
候,虚拟时钟计时并没有停止,所以,很可能在qemu的虚拟CPU还没开始的时候就需要触发时钟中断了,但是虚拟CPU还在停止状态,中断无法触发。接收到s命令后,虚拟CPU开始执行指令。这时,如
果内核允许中断,虚拟时钟就将触发中断,所以s命令执行一条指令后停止在时钟中断处理程序的开始处,而不是希望的函数中下一条指令处。
  现在看一下问题的解决方法。在我看来,需要修改gdb远程调试内核时单步命令的语义。有两个方向。
  1.在gdb上修改。在处理用户的n与s命令时不是发送协议中的s命令,而是分两步。首先确定下一条指令的开始位置(或者下一行源程序对应的指令的开始位置)。对于有些RISC机器机器指令固定为某个长度,那么确定这个位置比较简单,但是对于像x86这样的变长指令的体系结构就需要稍微麻烦一点(需要确定当前指令的长度等)。然后假如第一步确定的地址是naddr。现在像处理用户的tbreak *naddr一样处理就可以了,接着发送继续运行命令c就可以了。
  2.在qemu的gdb stub上修改协议命令s的处理方法。接收到s命令后不是让虚拟CPU进入单步执行状态,而是确定在没有中断的情况下,下一条指令的位置(注意对于当前是跳转指令的情况处理比较复杂),然后在这个位置设置临时断点,在虚拟CPU到达这个断点进入gdb
stub后立即将其取消。
  这两种处理方法中,我认为1比较好,实现起来清晰明了,但是需要对gdb的代码比较熟悉。2方法比较复杂,尤其是在当前指令是跳转指令时,不太容易确定临时断点的位置。
  另外作为暂时的权宜之计,我们可以只使用tbreak +offset来代替n与s命令。
&&&&最近几天学习Linux-2.6平台上的设备驱动,所以要建立内核及内核模块的调试平台.虽然网上有很多相关教程,但多是基于2.6.26以前的通过补丁&安装的,过程非常复杂,而且问题比较多.linux从&2.6.26开始已经集成了kgdb,只需要重新编译2.6.26(或更高)内核即可.kgdb安装及模块调试过程也遇到不少问题,网上网下不断的搜索与&探索,才算调通.现在记录下来,供朋友们参考.
&&&&首先说一下,开始本打算安装kdb进行内核调试,后来听说kdb只能进行汇编级别的调试,所以放弃,改用kgdb.
2:&系统环境:
虚拟环境:&&&VMWare&Workstation&5.5(英文版)
操作系统:&&&CentOS-4.6-i386(原内核2.6.9,将会把内核升级至2.6.26)
注:CentOS&是RedHat的一个社区版本.
&&&&&(由于我们采用的linux&kernel&2.6.26已经集成kgdb,kgdb再不需要单独下载)
3:系统的安装:
在VMWare中新建一台计算机:
点击&Next&
选中Custom&点击&Next&
选中&New-Workstation&5,点击Next&
选中&Linux&,Version选中Other&Linux&2.6.x&kernel&点击Next&
Virtual&machine&name&输入Client(Development)&点击Next&
Processors&选中&One,&点击Next&
Memory&输入256,点击Next&
Network&connection&选中Use&network&address&translation(NAT)&(选第一个貌似也可以)&点击Next&
I/O&adapter&types&
SCSI&Adapters&选中默认的LSI&Logic(这里如果你后面使用了SCSI格式的Disk,编译内核时需要添加相应的驱动,我选择的是IDE的硬盘,kernel默认就支持了)&
Disk&选中Create&a&new&virtual&disk&点击Next&
Virtual&Disk&Type&选中IDE,点击Next&
Disk&capacity&Disk&size&输入80G&(下面的Allocate&all&disk&space&now不要选中,表示在真正使用才分配磁盘空间,&Split&disk&into&2&GB&files项,可不选,如果你的系统分区为fat32格式,最好选中)&点击Next.&
Disk&file&,输入Disk的名称,如:disk1.vmdk&,点击Finish.完成&
安装CentOS&4.6(过程略过)
安装完成后,关闭计&算机,然后Clone一台同样的计算机.步骤如下:
点击VM-&Clone&
选中默认的From&current&state,点击Next&
选中Create&a&full&clone,&点击Next&
Virtual&Machine&name&输入Server(Targe),将克隆的机器命令为目标机.&
说&明一下,kgdb&需要两台计算机通过串口进行远程调试,两台计算机分别为:&
Client(Development):开发机,也称客户机,将&在该计算机上进行程序的开发,GDB将在本计算机上运行.用于输入命令控制Server(target)的运行.&
Server(Target):&目标机,也称服务器,就是被调试的计算机,在Development机上开发好的内核模块程序将拷贝到Target上运行,其运行受到&Development命令的控制.&
分别为两个系统增加一个串口,以&Output&to&named&pipe&方式,其中:
Client端选择&this&end&is&the&client&,&&the&other&end&is&a&virtual&machine&&
Server端选择&this&end&is&the&server&,&&the&other&end&is&a&virtual&machine&&
备注:&两个pipe的名称要相同,并且选中下面的Connect&at&power&on,及Advanced里面的Yield&CPU&on&poll&
以后的部分,Server上的操作与Client上的操作将以不同的背景色&显示,输入的命令将以不同的字体颜色并带下划线显示.请注意:&
Server(Target)&输入:&cat&/dev/ttyS0
系统输出的信息:&hello&Client(Development)&输入:&echo&&hello&&&/dev/ttuS0&
串&口添加完成后,使用如果命令测试:&
在Server上cat&/dev/ttyS0&
然后到Client上&echo&&hello&&&&/dev/ttyS0&
这时回到Server上,如果能看到输入的hello,说明串口通讯正常.&
4:升级内核&2.6.26(添加关于KGDB的选项)
&&&&&&说明一下,这里是要升级Server的内核,因为kgdb是要Server上运行的,但是编译需要在Client完成(或者你也可以在Server上编&译,之后再拷贝到Client上),因为调试时Client上的gdb会用到编译的内核及源代码.Client也需要升级,保证Client同&Server上的内核一致,这样Client上编译的模块拿到Server上加载就不会有什么问题.&
首先下载kernel&2.6.26
&&&&&&我习惯在windows上下载,然后共享,再到linux使用smbclient连接,拷贝到Linux上.你也可以直接在linux上下载.&
&&&&&&smbclient用法&:&smbclient&//192.168.0.100/share&-Uadministrator&回车后,会提示输入admin的密码.之后就可以通过get获取了&192.168.0.100是我在windows主机的IP,share为共享名,-U后面是用户名
编译Kernel2.6.26&
进入Client(Development)系统,将linux-2.6.26.tar.bz2拷贝到/usr/src目录下:&
cd&/usr/src&
tar&jxvf&linux-2.6.26.tar.bz2&
ln&-s&linux-2.6.26&linux&
make&menuconfig&
File&System&--&&下面把ext3,ext2都编译进内核(就是把前面的M变成*)&
Kernel&Hacking&--&&
&&&&&&选中Compile&the&kernel&with&frame&pointers
&&&&&&选中KGDB:kernel&debugging&with&remote&gdb&
&&&&&&并确认以下两项也是选中的(他们应该默认是选中的)&
&&&&&&&&kernel&debugging&
&&&&&&&&Compile&the&kernel&with&debug&info&
对&于其它选项,请按实际情况,或你的要求定制.&
在其它网友的说明里面,会有Serial&port&number&for&KGDB等选项,但是我使用的版本未找到这些选项,所以忽略过.&
make&-j10&bzImage
-j10表示使&用10个线程进行编译.&
make&modules
编译内核模块&
make&modules_install
安装内核模&块&
make&install&
将Client系统中的linux-2.6.26整个目录同步到Server上.&
在Client系统上运行下列命令:&
cd&/usr/src/linux&&&
scp&-r&linux-2.6.26&root@Server(Target)IP:/usr/src/
&&&&&系统会提示输入root的密码,输入完了就会开始复制文件了,(这里要注意,如果原系统已经是2.6.26的内核,可以只拷贝arch/i386&/boot/bzImage,及System.map文件到Server上,然后修改/boot/grub/grub.conf,但由于我是从2.6.9&升级上来,所以需要将整个linux原代码目录拷贝到Server上进行升级)&
升级Srever系统的内核&
进入&Server(Target)系统,usr/src目录:&
ln&-s&linux-2.6.26&linux&&&
cd&linux&&&
make&modules_install
安装内核模块&
make&install
安&装完成后,在/boot/目录下会有即个新添加的文件./boot/grub/grub.conf文件也会添加一个新的启动项,我的如下(行号不是文件的&一部分):
&1&#&grub.conf&generated&by&anaconda
&3&#&Note&that&you&do&not&have&to&rerun&grub&after&making&changes&to&this&file
&4&#&NOTICE:&&You&have&a&/boot&partition.&&This&means&that
&5&#&&&&&&&&&&all&kernel&and&initrd&paths&are&relative&to&/boot/,&eg.
&6&#&&&&&&&&&&root&(hd0,0)
&7&#&&&&&&&&&&kernel&/vmlinuz-version&ro&root=/dev/VolGroup00/LogVol00
&8&#&&&&&&&&&&initrd&/initrd-version.img
&9&#boot=/dev/hda
10&default=0
11&timeout=5
12&splashimage=(hd0,0)/grub/splash.xpm.gz
13&hiddenmenu
14&title&CentOS&(2.6.26)
15&&&&&root&(hd0,0)
16&&&&&kernel&/vmlinuz-2.6.26&ro&root=/dev/VolGroup00/LogVol00
17&&&&&initrd&/initrd-2.6.26.img
18&title&CentOS-4&i386&(2.6.9-67.ELsmp)
19&&&&&root&(hd0,0)
20&&&&&kernel&/vmlinuz-2.6.9-67.ELsmp&ro&root=/dev/VolGroup00/LogVol00
21&&&&&initrd&/initrd-2.6.9-67.ELsmp.img
&&&&&&&注意里面的NOTICE说明,我的系统/boot是一个独立的分区,所以下面配置的文件路径都是相对于/boot/目录的,像&/vmlinuz-2.6.26,实际到它的绝对位置应该是/boot/vmlinuz-2.6.26.&在你的系统上请根据实际情况处理.&
修改一下grub.conf,修改成下面这样:
&1&#&grub.conf&generated&by&anaconda
&3&#&Note&that&you&do&not&have&to&rerun&grub&after&making&changes&to&this&file
&4&#&NOTICE:&&You&have&a&/boot&partition.&&This&means&that
&5&#&&&&&&&&&&all&kernel&and&initrd&paths&are&relative&to&/boot/,&eg.
&6&#&&&&&&&&&&root&(hd0,0)
&7&#&&&&&&&&&&kernel&/vmlinuz-version&ro&root=/dev/VolGroup00/LogVol00
&8&#&&&&&&&&&&initrd&/initrd-version.img
&9&#boot=/dev/hda
10&default=0
11&timeout=5
12&splashimage=(hd0,0)/grub/splash.xpm.gz
13&hiddenmenu
14&title&CentOS&(2.6.26)
15&&&&&root&(hd0,0)
16&&&&&kernel&/vmlinuz-2.6.26&ro&root=/dev/VolGroup00/LogVol00&kgdboc=ttyS0,115200
17&&&&&initrd&/initrd-2.6.26.img
18&title&CentOS&(2.6.26)&Wait...(kernel&debug)
19&&&&&root&(hd0,0)
20&&&&&kernel&/vmlinuz-2.6.26&ro&root=/dev/VolGroup00/LogVol00&kgdboc=ttyS0,115200&kgdbwait
21&&&&&initrd&/initrd-2.6.26.img
22&title&CentOS-4&i386&(2.6.9-67.ELsmp)
23&&&&&root&(hd0,0)
24&&&&&kernel&/vmlinuz-2.6.9-67.ELsmp&ro&root=/dev/VolGroup00/LogVol00
25&&&&&initrd&/initrd-2.6.9-67.ELsmp.img
第一个启动项在原来的基础上添加了kgdb的参数kgdboc=ttyS0,115200
kgdboc&的意思是&kgdb&over&console,这里将kgdb连接的console设置为ttyS0,波特率为115200,如果不在内核启动项中配置该参数,可以在进入系统后执行命&令:&
echo&ttyS0&&&/sys/module/kgdboc/parameters/kgdboc&&&&
第二个启动&项,同第一个使用同一个内核,只是添加了kgdbwait参数
kgdbwait&使&kernel&在启动过程中等待&gdb&的连接。&
我的启动菜单如下:&
CentOS(2.6.26)&
CentOS(2.6.26)Wait...(kernel&debug)&
CentOS-4&i386(2.6.9-67.ELsmp)&
调用内核模块,就选择第一个,如果要调试内核启动过程,选择第二个.&
5.内核调试
重&启Server,通过启动菜单第二项CentOS(2.6.26)Wait...(kernel&debug)进入系统.&只到系统出现:
&&&&&kgdb:&Registered&I/O&driver&kgdboc
&&&&&kgdb:&Waiting&for&connection&from&remote&gdb&&&
进入Client系统.
cd&/usr/src/linux&&&
gdb&vmlinux&&
启动gdb开始准备调试:输出大致如下:
GNU&gdb&Red&Hat&Linux&(6.3.0.0-1.153.el4rh)
Copyright&2004&Free&Software&Foundation,&Inc.
GDB&is&free&software,&covered&by&the&GNU&General&Public&License,&and&you&are
welcome&to&change&it&and/or&distribute&copies&of&it&under&certain&conditions.
Type&&show&copying&&to&see&the&conditions.
There&is&absolutely&no&warranty&for&GDB.&&Type&&show&warranty&&for&details.
This&GDB&was&configured&as&&i386-redhat-linux-gnu&...Using&host&libthread_db&
library&&/lib/tls/libthread_db.so.1&&(gdb)&set&remotebaud&115200&
(gdb)&target&remote&/dev/ttyS0
注意:有的文章讲:因为vmware的named&piped不能被gdb直接使用,需要使用&socat&-d&-d&/tmp/com_1&/dev/ttyS0转换,然后使用转换后的设备.&socat需要自己下载安装.&但在我的环境中,直接使用并没有出错.
执行该命令后输出如下:&
Remote&debugging&using&/dev/ttyS0
kgdb_breakpoint&()&at&kernel/kgdb.c:1674
1674&&wmb();&/*Sync&point&after&breakpoint&*/
warning:&shared&library&handler&failed&to&enable&breakpoint看到上面的内容说明已经连接成功,但Server上依然是假死状态,这时你可以像使用本地gdb一样设置断点(break),单步执&行(step),或其它命令.&
(gdb)&cont
继续执行,Server就继续下面的系统初始化了.&
系统启动完成后的内核&调试:
&&&&进入Server后,执行命令&
echo&g&&&/proc/sysrq-trigger&
系统同样会中断,进入假死状态,等待远程gdb的连接.KGDB可能会&输出如下信息:&
SysRq:&GDB&&&&上面的命令(echo&g&&&/proc/sysrq-trigger)可以有一个快捷键(ALT-SysRq-G)代替,当然前提是你编译内核时需要选中相关选项,并且需要修改配置&文件:/etc/sysctl.conf&,&我用了一下,不太好用.因为有的桌面系统中PrintScreen/SysRq键是用于截屏的.所以还是直接用命令来的好!&
我&在~/.bashrc中添加了一句(添加完保存后,要执行source&~/.bashrc应用该配置):
alias&debug='echo&g&&&/proc/sysrq-trigger'
之后就可以直接输入debug来使内核进入调试状态.&
Server进入调试状态&后,转换到Client系统,重复上面的步骤.&
6.&Linux内核模块(设备驱动)的调试
编写内核模块,及Makefile
&&&&&&我使用的例子是Linux&Device&Driver&3中的例子scull.&你可以从这里下载LDD3的所有例子程序.
进入Client系统,解压example.tar.gz,将其中的example/scull目录拷贝到/root/scull&
然后执&行:&
cd&/root/scull&&&
编译应该会出错,因为Linux&Device&Driver&3的例子程序是基于2.6.10内核的,请参靠下面的说明修改这些错误:
编译scull驱动,完成后,scull目录应该多出了scull.ko&及其它中间文件.&
scp&scull.ko&root@targetIp:/root/
将&scull.ko模块文件拷贝到Server上.系统应该会提示输入密码:
root@targetIp's&password:输入正确密码后,应该能看到如下信息,表示复制成功了:&
scull.ko&&&&100%&&258k&258.0kb/s&00:00进入Server系统输入:&
cd&/root/&&&
insmod&scull.ko&&
加载scull模块.&
cat&/sys/module/globalmem/sections/.text&&
显示scull模块的.text段地址.运行该命令后,会返回&一个16进制的地址,如:&
0xd099a000echo&g&&&/proc/sysrq-trigger&&
现在Server&系统变成等待状态.&
再次进入Client系统:&
cd&/usr/src/linux&&&
gdb&vmlinux&&&
(gdb)&set&remotebaud&115200&&&
(gdb)&target&remote&/dev/ttyS0&
Remote&debugging&using&/dev/ttyS0
kgdb_breakpoint&()&at&kernel/kgdb.c:1674
1674&&wmb();&/*Sync&point&after&breakpoint&*/
warning:&shared&library&handler&failed&to&enable&breakpoint出现上面的信息表示连接成功,但此时还不可以设置断点.我试了N次,现在设置端点后,无论Server上对scull做什么操&作,Client上的gdb都不会停止.也有人说这是gdb的BUG,gdb无法正常解析内核模块的符号信息.&说是下载kgdb网站上的gdbmod可以解决该问题,但我试了之后发现根本没有用.&所以只能通过命令手动加载相关符号信息:&
(gdb)&add-symbol-file&/root/scull/scull.ko&0xd099a000&
注意:&0xd099a000地址是在上面获取的,命令输入完了,系统会有如下提示信息(第二行后面的y是自己输入的:&
add&symbol&table&from&file&&/root/scull/scull.ko&&at&.text_addr&=&0xd099a000
Reading&symbols&from&/root/scull/scull.ko...done.&&&&&&&&(gdb)break&scull_write
Breakpoint&1&at&0xd099a2d9:&file&/root/scull/main.c,line&338.(gdb)break&scull_read
Breakpoint&2&at&0xd099a1a2:&file&/root/scull/main.c,line&294(gdb)cont&&&
ContinuingClient上的工作暂停,回到Server系统上:&
Server现在处于运行状态,在Server上运行下面的命&令:&
cat&/proc/devices&|&grep&&scull&
查看scull模块分配的major.我的系统中返回如下:&
253&scullp
253&scullamknod&/dev/scull&c&253&0
253是刚才查询到的&版本号.&
echo&&this&is&a&test&&&&&/dev/scull
测试输入函数:scull_write.&该命令输入完成后,进程应该会停下来,&请切换到Client系统.&
cat&/dev/scull
测试输出函&数:scull_read.&该命令输入完成后,进程应该会停下来,&请切换到Client系统.&
回到Client系统,应该能看到gdb的输&出信息:
Breakpoint&1,&scull_write&(filp=0xce5870c0,buf=0xb7f44000&
&&&&&&&this&is&a&test\nias&|&/usr/bin/which&--tty-only&--read-alias&
&&&&&&--show-dot&--show-tilde'\n&,count=15,f_pos=0xce5c5f9c)
&&at&/root/scull/main.c:338
338&&&{&&&&
(gdb)_&&现在就可以像调试本地程序一样调试scull.ko模块了.&
以同样的方法也可以调试其它函数.(初始化函数暂时没想到办法调试,因为使用这种方&法需要先加载后,才可以进行调试)&
你可以自由使用和转载本文档,转载时请注明出处.&()
如果可以,我想写一个脚本自动完成这个添加符号文件的过程,简化调试过程.
Linux&Device&Driver&3rd中的scull&例程在2.6.26上编译出错的问题
1。scripts/Makefile.build:46:&***&CFLAGS&was&changed&in&&examples/scull/Makefile&.&Fix&it&to&use&EXTRA_CFLAGS。&停止。
&&&&解决方法:将&Makefile&中的&CFLAGS&改为&EXTRA_CFLAGS&
2.&examples/scull/main.c:17:26:&error:&linux/config.h:&没有该文件或目录
&&&&解决方法:&将&main.c&中的这条&include&语句注释掉。&
3.&examples/scull/access.c:&在函数‘scull_u_open’中:&&&&examples/scull/access.c:107:&错误:&提领指向不完全类型的指针
&&&&解决方法:access.c&中添加:#include&&linux/sched.h&&
4.&examples/scull/access.c:&在函数‘scull_access_setup’中:
&&&&examples/scull/access.c:355:&警告:&格式字符串不是一个字面字符串而且没有待格式化的实参
&&&&解决方法:将&&kobject_set_name(&dev-&cdev.kobj,&&devinfo-&name);&改为:
&&&&&&&&&&&&&&&&&&&kobject_set_name(&dev-&cdev.kobj,&&%s&,&devinfo-&name);
因为&kobject_set_name&有一个像&printf&一样的参数表。&
补充&:&老外作的改动http://www.cs.fsu.edu/~baker/devices/lxr/source/2.6.25/ldd-examples/基&本上已经可以编译了&
摘录自:&&&Linux&Device&Driver&3&中的代码在&2.6.27&中编译不能通过的问题&&&
&&&&&&&&&&Using&2.6.26&Linux&Kernel&Debugger&(KGDB)&with&VM&&&
&&&&&&&&&&VMware环境下用kgdb调试内核&&&
&&&&&&&&&&Using&2.6.26&Linux&Kernel&Debugger&(KGDB)&with&VM&&
这是我写的一些关于kgdb在2.6.27里面的源代码分析,欢迎指教。&
前段时间用kgdb调了一下内核,感觉这个东西还不错,不过更令人感兴趣的是它的工作原理.&内核运行得好好的,那么多线程在好几个cpu上面跑,就是因为踩到一个断点,它就把所有东西都放下来让gdb来调,kgdb是怎样办到的?&
本文是本人看kgdb的代码的一些记录,如有错误的地方敬请指出.&
一般软件调试的原理是,调试器根据目标文件的调试信息找到源码和机器码之间的映射关系,并把它关心的指令替换成一条断点指令,x86对应的就是asm(&int&$3&),它的二进制代码是0xcc.当程序执行到这个地方时断点指令被执行,程序被中断,调试器接管它的控制权,这时可以查看内存信息甚至修改内存.当调试器完成任务后把断点指令替换回原来的指令,并把pc寄存器减一,让cpu从被中断的那条指令开始执行.这样程序可以在没有影响执行的情况下被调试.&
调试内核也离不开一般原理,只不过内核不同一般程序,不能单靠同一台机的gdb来完成.要依靠kgdb,它是内核里面的一小段程序,它可以和另外一台机器上的gdb进行通信,可以执行gdb过来的一些命令并返回信息,完成调试过程.&
关于kgdb的配置,网上有很多,而且非常详细,这里就不重复了.&
本文的内容:&
1.从内核是怎样把控制权交给kgdb的开始,到&
2.kgdb的入口函数,看kgdb怎样处理竞态,再了解&
3.kgdb和gdb之间的通信协议,接着是&
4.kgdb对具体命令的执行,最后再看看&
5.kgdb是在什么时候被启动的.&
内核版本是2.6.27.&
&&&&&&&&&1.&异步通知&&&&&&&&&&
当内核踩到一个断点时,当前进程是用什么方式通知kgdb,控制权又是怎样到kgdb手上的呢?&
先回顾一下kgdb的patch在2.4上是怎样让内核通知kgdb的处理代码的,下面是2.4.23的内核打上kgdb补丁的部分代码:&
(arch/i386/kernel/traps.c)&
#ifdef&CONFIG_KGDB&
#define&CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,after)&\&
if&(linux_debug_hook&!=&(gdb_debug_hook&*)&NULL&&&&!user_mode(regs))&\&
(*linux_debug_hook)(trapnr,&signr,&error_code,&regs)&;&\&
#define&CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,after)&
#define&DO_VM86_ERROR(trapnr,&signr,&str,&name)&\&
asmlinkage&void&do_##name(struct&pt_regs&*&regs,&long&error_code)&\&
CHK_REMOTE_DEBUG(trapnr,signr,error_code,regs,goto&skip_trap)\&
do_trap(trapnr,&signr,&str,&1,&regs,&error_code,&NULL);&\&
skip_trap:&\&
DO_VM86_ERROR这个宏是用来生产异常处理函数的。比如你的name参数是i

我要回帖

更多关于 linux内核模块编译 的文章

 

随机推荐