Chinaunix首页 | 论坛 | 博客
  • 博客访问: 168581
  • 博文数量: 67
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 22
  • 用 户 组: 普通用户
  • 注册时间: 2015-06-17 17:44
个人简介

一入程序深似海,从此妹子成路人

文章分类

全部博文(67)

文章存档

2016年(13)

2015年(54)

我的朋友

分类: 嵌入式

2015-07-11 23:46:18

原文地址: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,对内存管理(MMU)有什么看法

2,对usb驱动有什么认识

3,内核的5大模块

4,开发板的内存起始地址
《》


5,
    1、Disabling daemons (关闭 daemons)
    2、Shutting down the GUI (关闭GUI)
    3、Changing kernel parameters (改变内核参数)
    4、Kernel parameters (内核参数)
    5、Tuning the processor subsystem(处理器子系统调优)
    6、Tuning the memory subsystem (内存子系统调优)
    7、Tuning the file system(文件系统子系统调优)
    8、Tuning the network subsystem(网络子系统调优)


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代表锁未被占用。这种无序竞争导致执行线程无法保证何时能取到锁,某些线程可能需要等待很长时间。随着计算机处理器个数的不断增长,这种“不公平”问题将会日益严重。



阅读(1618) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~