分类: 嵌入式
2015-06-21 08:51:23
原文地址:linux内核,驱动面试题 作者:andyhzw
1.1 Spin Lock 相对于信号量的特点;在UP/MP/SMP这些不同环境下,Spin Lock的不同实现方法。
1.2 简要说明e2compr压缩文件系统的压缩策略;经其处理后,上层文件系统(EXT2/3等)是否会受到影响。
1.3 比较linux操作系统内核态和用户态的抢占性,及两者之间存在差异的原因。
1.4 linux2.2支持的最大进程数是多少,演进至2.4后,这一制约是如何克服的。
1.5 列举中断屏蔽的类型,并简单比较。
1.6 列举linux所要处理的定时/计数器(实际的外围硬件设备,不是指软件定时器),
及其用途。
2. 问答题。(各20分)
2.1 穷举在用户态空间,内核消息的获取方式、差异、以及差异的原因。
2.2 解释linux进程调度中的epoch概念,该机制的目的。
2.3 描述一个硬件中断信号发生,到为其注册的中断服务例程开始执行,之间所经过的
内核处理过程。并进一步分析,制约linux中断响应时间的因素存在于哪些方面。
2.4 比较底半机制(BH)和Tasklet的特点,及运行方面的差异。
2.5 说明引导过程中initrd.img和linuxrc作用,给出制作initrd.img的伪代码形式的流程。
2.6 在VFS层中如何区分设备文件和正规文件?
2.7 内核有几种方式访问硬件设备?
2.8 内核如何访问iomem?请举例说明。
3. 分析题。
3.1 系统时间设置问题。(10分)
同样采用date命令,目的在于修改系统时间,在不同的系统上却有不同的现象:
假定初始时间(T1)为03月01日16:00 2003年,期望的修改后时间(T2)为12月31日00:00 2002年,
<1> 在EDK系统中,执行命令 #date 123100002002,系统时间立即会变为T2;但系统重新启动之后,系统时间仍恢复为T1的时间区间:03月01日16:02 2003年,修改并未保留下来。
<2> 在RedHat7.2/8.0中,重复上述设置,系统重新启动之后,时间设置得到了保留,系统运行于T2的时间区间:12月31日00:02 2002年。请分析<1> 中所表现出的现象是由于什么原因造成的;
3.2 在大家相关于串口的工作中,可能会遇到这样的问题:(20分)
串口在发送数据时,不是连续且迅速地发送,而是以一个固定的节拍(10秒),且每次只发送等长度的一段数据,请列举可能造成这一问题的所有原因;
3.3 Oops分析。(50分)
以下是一段经过符号解析过的Oops信息,我们知道造成这个Oops的直接原因在于对于内核函数__wake_up的调用过程之中出现了问题;基于上述判断,请基于已经给出的__wake_up相关源码和反汇编码,大致分析问题出现在哪个源码行,并给出分析结论;
———————————————————–
ksymoops信息:
ksymoops2.4.4on i686 2.2.19-rthal3. Options used
-v /usr/src/linux-2.2.19/vmlinux (specified)
-k /proc/ksyms (specified)
-l /proc/modules (specified)
-o /lib/modules/2.2.19-rthal3/ (default)
-m /usr/src/linux-2.2.19/System.map (specified)
current->tss.cr3 = 07d29000, %cr3 = 07d29000
*pde = 00000000
Oops: 0000
CPU: 0
EIP: 0010:[
Using defaults from ksymoops -t elf32-i386 -a i386
eax: 00000014 ebx: c0eabf74 ecx: 00000013 edx: 00000021
esi: 00000000 edi: 00000020 ebp: c0eabf6cesp: c0eabf60
ds: 0018 es: 0018 ss: 0018
Process in.identd (pid: 828, process nr: 6, stackpage=c0eab000)
Stack:00000000 c807ca0400000021 c0eabf74 c807be21 c0eabfb0 c807bf74
c807c940
00000000 0000000000000000 c803fb60 c807e000 00000e20 2b124c28
0000027c
0010a000 c807c900 000000000000f944 bffff944 c803d2ec 00000000
00000000
Call Trace: [
[
[
Code: 8b 02 85 45 fc 74 1b 85 ff 74 10 837a44 00 740a85 f6 75
>>EIP; c01110c1 <__wake_up+2d/6c> <=====
Trace; c807ca04 <[rt_das]timeout+c4/c8>
Trace; c807be21 <[rt_das]read_timeout+25/28>
Trace; c807bf74 <[rt_das]pulse_isr+150/19c>
Trace; c807c940 <[rt_das]timeout+0/c8>
Trace; c803fb60 <[rtai]global_irq_handler+0/80>
Trace; c807e000 <.bss.end+14a1/????>
Trace; c807c900 <[rt_das]board+0/28>
Trace; c803d2ec <[rtai]dispatch_global_irq+28/90>
Trace; c803c0a0 <[rtai]GLOBAL0_interrupt+18/34>
Code; c01110c1 <__wake_up+2d/6c>
00000000 <_EIP>:
Code; c01110c1 <__wake_up+2d/6c> <=====
0: 8b 02 movl (%edx),%eax <=====
Code; c01110c3 <__wake_up+2f/6c>
2: 85 45 fc testl %eax,0xfffffffc(%ebp)
Code; c01110c6 <__wake_up+32/6c>
5: 74 1b je 22 <_EIP+0×22> c01110e3
<__wake_up+4f/6c>
Code; c01110c8 <__wake_up+34/6c>
7: 85 ff testl %edi,%edi
Code; c01110ca <__wake_up+36/6c>
9: 74 10 je 1b <_EIP+0×1b> c01110dc
<__wake_up+48/6c>
Code; c01110cc <__wake_up+38/6c>
b: 837a44 00 cmpl $0×0,0×44(%edx)
Code; c01110d0 <__wake_up+3c/6c>
f: 740aje 1b <_EIP+0×1b> c01110dc
<__wake_up+48/6c>
Code; c01110d2 <__wake_up+3e/6c>
11:85 f6 testl %esi,%esi
Code; c01110d4 <__wake_up+40/6c>
13: 75 00 jne 15 <_EIP+0×15> c01110d6
<__wake_up+42/6c>
Unable to handle kernel paging request at virtual address 66fe4603
current->tss.cr3 = 00e94000, %cr3 = 00e94000
*pde = 00000000
Oops: 0000
CPU: 0
EIP: 0010:[
EFLAGS:00010a83
Warning (Oops_read): Code line not seen, dumping what data is available
>>EIP; c01113e6
1 warning issued. Results may not be reliable.
<附录>
1. __wake_up的源码:
void __wake_up(struct wait_queue **q, unsigned int mode)
{
struct task_struct *p, *best_exclusive;
struct wait_queue *head, *next;
unsigned int do_exclusive;
if (!q)
goto out;
/*
* this is safe to be done before the check because it
* means no deference, just pointer operations.
*/
head = WAIT_QUEUE_HEAD(q);
read_lock(&waitqueue_lock);
next = *q;
if (!next)
goto out_unlock;
best_exclusive = 0;
do_exclusive = mode & TASK_EXCLUSIVE;
while (next != head) {
p = next->task;
next = next->next;
if (p->state & mode) {
if (do_exclusive && p->task_exclusive) {
if (best_exclusive == NULL)
best_exclusive = p;
}
else {
wake_up_process(p);
}
}
}
if (best_exclusive)
wake_up_process(best_exclusive);
out_unlock:
read_unlock(&waitqueue_lock);
out:
return;
}
2. __wake_up的反汇编码:
c0111094 <__wake_up>:
c0111094: 55 pushl %ebp
c0111095: 89 e5 movl %esp,%ebp
c0111097: 83 ec 08 subl $0×8,%esp
c011109a: 57 pushl %edi
c011109b: 56 pushl %esi
c011109c: 53 pushl %ebx
c011109d: 89 55 fc movl %edx,0xfffffffc(%ebp)
c01110a0:85 c0 testl %eax,%eax
c01110a2: 74 50 je c01110f4 <__wake_up+0×60>
c01110a4: 8d 48 fc leal 0xfffffffc(%eax),%ecx
c01110a7: 89 4d f8 movl %ecx,0xfffffff8(%ebp)
c01110aa: 8b 18 movl (%eax),%ebx
c01110ac: 85 db testl %ebx,%ebx
c01110ae: 74 44 je c01110f4 <__wake_up+0×60>
c01110b0:31 f6 xorl %esi,%esi
c01110b2: 89 d7 movl %edx,%edi
c01110b4: 83 e7 20 andl $0×20,%edi
c01110b7: 39 cb cmpl %ecx,%ebx
c01110b9: 74 2d je c01110e8 <__wake_up+0×54>
c01110bb: 90 nop
c01110bc: 8b 13 movl (%ebx),%edx
c01110be: 8b 5b 04 movl 0×4(%ebx),%ebx
c01110c1: 8b 02 movl (%edx),%eax
c01110c3: 85 45 fc testl %eax,0xfffffffc(%ebp)
c01110c6: 74 1b je c01110e3 <__wake_up+0×4f>
c01110c8: 85 ff testl %edi,%edi
c01110ca: 74 10 je c01110dc <__wake_up+0×48>
c01110cc: 837a44 00 cmpl $0×0,0×44(%edx)
c01110d0: 740aje c01110dc <__wake_up+0×48>
c01110d2:85 f6 testl %esi,%esi
c01110d4: 75 0d jne c01110e3 <__wake_up+0×4f>
c01110d6: 89 d6 movl %edx,%esi
c01110d8: eb 09 jmp c01110e3 <__wake_up+0×4f>
c01110da:89 f6 movl %esi,%esi
c01110dc: 89 d0 movl %edx,%eax
c01110de: e8 2d f9 ff ff call c0110a10
c01110e3: 3b 5d f8 cmpl 0xfffffff8(%ebp),%ebx
c01110e6: 75 d4 jne c01110bc <__wake_up+0×28>
c01110e8:85 f6 testl %esi,%esi
c01110ea: 74 08 je c01110f4 <__wake_up+0×60>
c01110ec:89 f0 movl %esi,%eax
c01110ee: e8 1d f9 ff ff call c0110a10
c01110f3: 90 nop
c01110f4: 8d 65 ec leal 0xffffffec(%ebp),%esp
c01110f7: 5b popl %ebx
c01110f8: 5e popl %esi
c01110f9:5fpopl %edi
c01110fa: 89 ec movl %ebp,%esp
c01110fc: 5d popl %ebp
c01110fd: c3 ret
c01110fe:89 f6 movl %esi,%esi
———————————————————–
内核驱动题
以下设计应该包括设计文档,实现策略说明,代码包,测试用例,使用说明.要求:按照综合编程题目的要求编写代码和文档。
参考资料:
1.设计并实现一个软件watchdog设备,以监视系统运行情况.(50分)
说明:watchdog设备用于监测系统运行状态,正常运行的系统定期写watchdog以使其不会超时,一旦超时,意味系统已挂起;watchdog应该重启系统. 现在的软件watchdog不重启系统,只用于监视应用程序的运行.
2.设计并实现一个简化的、容量可以变化的内存FIFO设备.(50分)
1。 I/O Port。
和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:io端口的基地址,
参数2:io端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当于访问一段内存。经常性的,我们要获得一块内存的物理地址。
2。内存操作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages。 请注意,kmalloc等函数返回的是物理地址!
注意,kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块程序需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
3。中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq ,void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags, const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的中断。
4。一些常见的问题。
对硬件操作,有时时序很重要。但是如果用C语言写一些低级的硬件操作的话,gcc往往会对你的程序进行优化,这样时序会发生错误。如果用汇编写呢,gcc同样会对汇编代码进行优化,除非用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现出来。
由于设备种类繁多,相应的设备驱动程序也非常之多。尽管设备驱动程序是内核的一部分,但设备驱动程序的开发往往由很多人来完成,如业余编程高手、设备厂商等。为了让设备驱动程序的开发建立在规范的基础上,就必须在驱动程序和内核之间有一个严格定义和管理的接口,例如SVR4提出了DDI/DDK规范,其含义就是设备与驱动程序接口/设备驱动程序与内核接口(Device-Driver Interface/Driver-Kernel Interface)。通过这个规范,可以规范设备驱动程序与内核之间的接口。
Linux的设备驱动程序与外接的接口与DDI/DKI规范相似,可以分为三部分:
(1) 驱动程序与内核的接口,这是通过数据结构file_operations来完成的。
(2) 驱动程序与系统引导的接口,这部分利用驱动程序对设备进行初始化。
(3) 驱动程序与设备的接口,这部分描述了驱动程序如何与设备进行交互,这与具体设备密切相关。
根据功能,驱动程序的代码可以分为如下几个部分:
(1)驱动程序的注册和注销
(2)设备的打开与释放
(3)设备的读和写操作
(4)设备的控制操作
(5)设备的中断和查询处理
前三点我们已经给予简单说明,后面我们还会结合具体程序给出进一步的说明。关于设备的控制操作可以通过驱动程序中的ioctl()来完成,例如,对光驱的控制可以使用cdrom_ ioctl()。
与读写操作不同,ioctl()的用法与具体设备密切相关,例如,对于软驱的控制可以使用floppy_ioctl(),其调用形式为:
static int floppy_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long param)
其中cmd的取值及含义与软驱有关,例如,FDEJECT表示弹出软盘。
除了ioctl(),设备驱动程序还可能有其他控制函数,如lseek()等。
对于不支持中断的设备,读写时需要轮流查询设备的状态,以便决定是否继续进行数据传输,例如,打印机驱动程序在缺省时轮流查询打印机的状态。如果设备支持中断,则可按中断方式进行
1、检测某个端口所占用的进程
2、对于linux主机的cpu负载使用,什么情况下user的比例升高,什么情况下system的比例升高,请联系实际举例。
3、在不umount的情况下,如何重新设置mount的参数。
4、不小心在系统下执行了chmod -x/sbin/chmod 怎么办?
5、linux文件的权限位x对目录和文件有何不同?
6、找出/taomee目录下的所有常规文件并设置权限644
7、如何查找某一文件被哪个进程打开?
8、新增一块存储设备,lvm操作的命令如何写
9、给主机host:172.16.0.2 增加gateway10.0.0.1
10、socket和tcp访问mysql的区别?
二、shell
1、使用awk打印出 welcome to taomee
2、如何将一个文件中的taomee、******、peoplenet中的*内容进行替换成network(*的内容不同)
3、找出access.log中访问top 10的ip地址
4、打印1-100奇数
5、删除一个文件中行号为奇数的行
6、替换某一个文件的字符串
7、exec和souce区别
8、显示24小时前的内容,
9、linux的优化
10、iptables表和链
Linux内核产生并发的原因主要是中断和睡眠再调度。
一 :中断,由于中断执行是异步的,而且是在非抢占式内核中打断当前运行内核代码的唯一方法,所以中断是可以和其它内核代码并发执行的。因此如果中断操作和被中断的内核代码访问同样的内核数据,就会发生竞争。
二 :睡眠和再调度, 处于进程上下文(下面会进行讲述)的内核任务可以睡眠(睡眠意味放弃处理器),这时调度程序会调度其它程序去执行(首先执行调度任务队列中的内核任务,然后执行软中断等,最后从运行队列中选择一个高优先级的用户进程运行)。显然这里也会造成内核并发访问,当睡眠的内核任务和新投入运行的内核任务访问同一共享数据时,就发生了竞争
Linux的同步机制从2.0到2.6以来不断发展完善。从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁。这些同步机制的发展伴随Linux从单处理器到对称多处理器的过渡;
伴随着从非抢占内核到抢占内核的过度。Linux的锁机制越来越有效,也越来越复杂。
Linux的内核锁主要是自旋锁和信号量。
自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图请求一个已被争用(已经被持有)的自旋锁,那么这个线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的执行线程便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的执行线程同时进入临界区。
Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
排队自旋锁(FIFO Ticket Spinlock)是 Linux 内核 2.6.25 版本引入的一种新型自旋锁,它通过保存执行线程申请锁的顺序信息解决了传统自旋锁的“不公平”问题。排队自旋锁的代码由 Linux 内核开发者 Nick Piggin 实现,目前只针对 x86 体系结构(包括 IA32 和 x86_64),相信很快就会被移植到其它平台。
自旋锁(Spinlock)是在Linux 内核中广泛运用的底层同步机制。它是一种工作于多处理器环境的特殊的锁,在单处理环境中自旋锁的操作被替换为空操作。当某个处理器上的内核执行线程申请自旋锁时,如果锁可用,则获得锁,然后执行临界区操作,最后释放锁;如果锁已被占用,线程并不会转入睡眠状态,而是忙等待该锁,一旦锁被释放,则第一个感知此信息的线程将获得锁。
传统的自旋锁本质上用一个整数来表示,值为1代表锁未被占用。这种无序竞争导致执行线程无法保证何时能取到锁,某些线程可能需要等待很长时间。随着计算机处理器个数的不断增长,这种“不公平”问题将会日益严重。