linux驱动基础面试题(二)

linux系统中多线程同步的方法有?

互斥锁、条件变量、信号量、读写锁

进程间通信的方式及优缺点

a、无名管道:无名管道是一种半双工的通信方式,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是指父子进程关系。

b、有名管道:有名管道也是一种半双工的方式,但是它允许无亲缘关系进程间的通信 。

c、信号量:信号量是一个计时器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步。

d、消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识,消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

e、信号:信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生f

f、共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但是多个进程都可以访问。共享内存是最快的IPC方式,他是针对其他进程间通信方式运行效率低而专门设计的。他往往与其他通信机制,如信号量,配合使用,来实现进程间的通信与同步。

请问 int *p 和char *p分别占几个字节

都占四个字节,因为两者都是指针变量,指针变量里面保存的是地址,地址在操作系统中是固定长度的,长度是有系统的位数

决定的,操作系统是32位,长度则为4个字节,操作系统为64位,长度则为8个字节。

请简述sizeof(),和strlen()的区别

sizeof,测量一个变量或数据类型所占的字节长度,统计字符串长度的时候加上’\0’,在编译阶段的时候就可以计算出长度

strlen,计算字符串长度,在统计字符串长度的时候不加上’\0’,在函数运行的时候才计算出长度。

顺便一提:scanf("%s",str);遇到空格会停止。可以选择用gets(str);

请简述用户空间的内存分配及各空间保存的数据类型

用户空间供为3G,分为:栈空间,堆空间,数据区,代码段

栈空间保存:局部变量,函数形参,自动变量。栈空间特点,先进后出,空间由系统管理;栈空间生命周期所在函数执行结束

后释放;栈空间保存的局部变量未初始化时,默认初始化为随机值。

堆空间:由malloc , calloc ,ralloc,这些好函数分配的控件位堆空间,堆空间特点:先进先出,由用户管理

数据区:又分为.bss段、.data段、常量区。其中.bss段保存的是未初始化的全局变量,当全局变量未初始化时,系统默认初始化为0,常量区保存的是常量,里面保存的值不能被修改,只能做读操作。.data段是保存已经初始化的全局变量以及被static修饰的变量(静态变量)。数据区的声明周期是整个程序执行完之后再释放。

代码段保存的是代码。

文件系统的主要功能

文件系统的主要功能是实现对文件的按名存储

存储结构分配顺序

如果BootLoader、内核、启动参数以及其他的系统映像四部分在固态存储设备上分别独立存放,则其存储结构的分配顺序应当是:BootLoader、启动参数、内核、文件系统。

下列字符串定义的区别

char *p ="hello world";
char p[] = "hello world";

char *p 代表指针p是一个指向字符常量的指针,指向一个常量区域,如果采用p[0] = 'H’等语句,就会报错。 而 char p[],中的p是一个被分配在一个可读可写内存中的字符数组的首地址,可以用p[0] = 'H’赋。

c和c++中的struct有什么不同?

c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的struct中可以。c++中struct和class的主要区别是在于默认的存取权限不同,struct默认为Public,而class默认为private。

realloc函数的缺陷是:

a)没有对入口参数的非法性进行检查
b)在重新分配一个内存空间的时候,没有对这个空间进行合法性判断就释放掉原来的空间,因此有一定的概率会把原来内存空间的数据释放掉,造成数据会无缘无故丢失。

分析下列关于内存申请的程序

    void GetMemory1(char *p)    
    {    
        p = (char *)malloc(100);    
    }    

    void Test1(void)    
    {    
        char *str = NULL;    
        GetMemory1(str);      
        strcpy(str, "hello world");    
        printf(str);  //str一直是空,程序崩溃     
    }   

程序会崩溃,因为str没有申请到内存空间。编译器总是要为函数的每个参数制作临时副本,把str当做参数传入到
GetMemory中时,在GetMemory中为参数p申请到了一块堆内存了,然后函数执行完毕,这时候参数p就会退栈,而p所指向的那一块堆内存并没有释放掉,而且也没有相应的指针指向这块内存的首地址,我们申请出来的堆内存就永远无法使用了,这就造成了内存泄漏。每调用一次GetMemory函数,就泄漏一块内存。
就象: int a = 100;
int b = a; // 现在b等于a
b = 500; // 现在能认为a = 500 ?
显然不能认为a = 500,因为b只是等于a,但不是a! 当b改变的时候,a并不会改变,b就不等于a了。
因此,虽然p已经有new的内存,但str仍然是null

volatile、static、const

volatile的作用是告知编译器,它修饰的变量随时都可能被改变,因此,编译后的程序每次在使用该变量的值时,都会从变量的地址中读取数据,而不是从寄存器中获取。用在以下情况:

  • 并行设备的硬件寄存器(如:状态寄存器)
  • 一个中断服务子程序中会访问到的非自动变量(也就是全局变量)
  • 多线程应用中被几个任务共享的变量。

static:

  • static修饰全局变量:被修饰的全局变量的作用域范围限定为本源文件,另外的源文件不能通过extern关键词来使用这个全局变量。
  • static修饰局部变量:a)被修饰的局部变量只能被初始化一次。b)被修饰的局部变量的存储空间发生了改变,不再存储在栈中,而是存储在数据段中。
  • static修饰函数:被修饰的函数作用域范围限定为本源文件,另外的源文件不能使用被修饰的函数

const:被const修饰的变量为只读的变量。例如:

a) const int a;// a是只读的,a不能重新被赋值
b) int const a;// 同(b)
c) int const *a //指针a指向的内容是只读的,不能通过*a = 10来赋值,但是指针a本身可以重新被赋值,例如 a = &b
d) const int *a //同(c)
e) int *const a //指针a本身是只读的,不能令a重新指向,例如 a = &b,但是指针a指向的内容可以通过*a = 10来赋值
f) const int *const a //指针a和a指向的内容都是只读的,不能执行a=&b,*a=10操作

在32位系统中,有如下结构体,那么sizeof(fun)的数值是()

#pragma pack(1)
 
struct fun{
	int i;
	double d;
	char c;
};

可能是一般的内存对齐做习惯了,如果本题采用内存对齐的话,结果就是24(int 4 double char 7)。但是#pragma pack(1)让编译器将结构体数据强制按1来对齐。

每个特定平台上的编译器都有自己的默认“对齐系数”(32位机一般为4,64位机一般为8)。我们可以通过预编译命令#pragma pack(k),k=1,2,4,8,16来改变这个系数,其中k就是需要指定的“对齐系数”。

只需牢记:

  • 第一个数据成员放在offset为0的地方,对齐按照对齐系数和自身占用字节数中,二者比较小的那个进行对齐。
  • 在数据成员完成各自对齐以后,struct或者union本身也要进行对齐,对齐将按照对齐系数和struct或者union中最大数据成员长度中比较小的那个进行;

linux目录结构,选项是/usr、/tmp、/etc目录的作用。(选择题)

解答:linux目录图:
在这里插入图片描述

  • /usr:不是user的缩写,其实usr是Unix Software Resource的缩写,也就是Unix操作系统软件资源所放置的目录,而不是用户的数据啦。
  • /tmp:这是让一般使用者或者是正在执行的程序暂时放置档案的地方。这个目录是任何人都能够存取的,所以你需要定期的清理一下。当然,重要资料不可放置在此目录啊。
  • /etc:系统主要的设定档几乎都放置在这个目录内,例如人员的帐号密码档、各种服务的启始档等等。
    一般来说,这个目录下的各档案属性是可以让一般使用者查阅的,但是只有root有权力修改。
    FHS建议不要放置可执行档(binary)在这个目录中。 比较重要的档案有:/etc/inittab, /etc/init.d/,
    /etc/modprobe.conf, /etc/X11/, /etc/fstab, /etc/sysconfig/等等。

Linux中的文件/目录权限设置命令是什么?

解答:chmod 777 -R

下面四个选项是四个整数在内存中的存储情况,请选择其中最大的一个。(大小端题)

  • Big-endian 低地址 高地址 12 34 56 78
  • Big-endian 低地址 高地址 56 78 12 34
  • Little-endian 低地址 高地址 34 56 78 12
  • Little-endian 低地址 高地址 78 12 34 56
    解答:大端小端问题:

所谓的大端模式(BE big-endian),是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中(低对高,高对高);
所谓的小端模式(LE little-endian),是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中(低对低,高对高)。
那么A:12345678、B:56781234、C:12785634、D:56341278。

下面这段程序的运行结果?(填空题)

int main() {
	int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
	memcpy(a + 3, a, 5);
	for (int i = 0; i<10; i++){
		printf("%d ", a[i]);
	}
	return 0;
}

解答:0 1 2 0 1 5 6 7 8 9

首先看一下内存复制函数memcpy()函数的定义:
void * memcpy ( void * destination, const void * source, size_t num );

将source指向的地址处的 num 个字节 拷贝到 destination 指向的地址处。注意,是字节

因为memcpy的最后一个参数是需要拷贝的字节的数目!一个int类型占据4个字节!这样的话,本题5字节,实际上只能移动2个数字(往大的去)。如果要想达到将a地址开始的5个元素拷贝到a+3地址处,需要这么写:
memcpy(a + 3, a, 5*sizeof(int));

C语言编译过程中,volatile关键字和extern关键字分别在哪个阶段起作用?(填空题)

解答:volatile应该是在编译阶段,extern在链接阶段。

volatile关键字的作用是防止变量被编译器优化,而优化是处于编译阶段,所以volatile关键字是在编译阶段起作用。

linux系统打开设备文件,进程可能处于三种基本状态,如果多次打开设备文件,驱动程序应该实现什么?(填空题)

互斥。

copy_to_user()和copy_from_user()主要用于实现什么功能?一般用于file_operations结构的哪些函数里面?

由于内核空间和用户空间是不能互相访问的,如果需要访问就必须借助内核函数进行数据读写。copy_to_user():完成内核空间到用户空间的复制,copy_from_user():是完成用户空间到内核空间的复制。一般用于file_operations结构里的read,write,ioctl等内存数据交换作用的函数。当然,如果ioctl没有用到内存数据复制,那么就不会用到这两个函数。

请简述主设备号和次设备号的用途。如果执行mknod chartest c 4 64,创建chartest设备。请分析chartest使用的是那一类设备驱动程序。

1)主设备号:主设备号标识设备对应的驱动程序。虽然现代的linux内核允许多个驱动程序共享主设备号,但我们看待的大多数设备仍然按照“一个主设备对应一个驱动程序”的原则组织。

 次设备号:次设备号由内核使用,用于正确确定设备文件所指的设备。依赖于驱动程序的编写方式,我们可以通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索引。

2)chartest 表示设备节点,4表示主设备号,64表示次设备号。(感觉类似于串口终端或者字符设备终端)。

设备驱动程序中如何注册一个字符设备?分别解释一下它的几个参数的含义。

注册一个字符设备驱动有两种方法:
1) void cdev_init(struct cdev *cdev, struct file_operations *fops)
该注册函数可以将cdev结构嵌入到自己的设备特定的结构中。cdev是一个指向结构体cdev的指针,而fops是指向一个类似于f file_operations结构(可以是file_operations结构,但不限于该结构)的指针.

2) int register_chrdev(unsigned int major, const char *namem , struct file)operations *fopen);

该注册函数是早期的注册函数,major是设备的主设备号,name是驱动程序的名称,而fops是默认的file_operations结构(这是只限于file_operations结构)。对于register_chrdev的调用将为给定的主设备号注册0-255作为次设备号,并为每个设备建立一个对应的默认cdev结构。

insmod 一个驱动模块,会执行模块中的哪个函数?rmmod呢?这两个函数在设计上要注意哪些?遇到过卸载驱动出现异常没?是什么问题引起的?

答: insmod调用init函数,rmmod调用exit函数。这两个函数在设计时要注意什么?卸载模块时曾出现卸载失败的情形,原因是存在进程正在使用模块,检查代码后发现产生了死锁的问题。

要注意在init函数中申请的资源在exit函数中要释放,包括存储,ioremap,定时器,工作队列等等。也就是一个模块注册进内核,退出内核时要清理所带来的影响,带走一切不留下一点痕迹。

驱动中操作物理绝对地址为什么要先ioremap?

因为内核没有办法直接访问物理内存地址,必须先通过ioremap获得对应的虚拟地址

内核配置编译及Makefile?

https://www.cnblogs.com/CrazyCatJack/p/6121231.html

linux中断实现机制、tasklet和workqueue的区别和底层实现的区别,为什么要区分中断上半部和中断下半部。

(中断上半部及下半部详细文档:https://download.csdn.net/download/kai_zone/10631972)

tasklet和workqueue区别?
tasklet运行于中断上下文,不允许阻塞 、休眠,而workqueue运行与进程上下文,可以休眠和阻塞。

中断和中断的上半部分和下半部分的问题

中断会打断内核中进程的正常调度和运行,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。所以中断处理程序中所有不要求立即完成的,在开中断的环境下,由中断后半段完成.

中断前半段主要完成尽可能少的比较紧急的功能,例如简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。

复杂的内容则交由中断下半部来执行,而且中断下半部可以被新的中断打断,这也是底半部和顶半部的最大不同。

当然, 如果中断比较简单,就不用区分上下半部了。

Linux 系统实现底半部的机制主要有:

三种:tasklet,工作队列和软中断。

中断和轮询哪个效率高?怎样决定是采用中断方式还是采用轮询方式去实现驱动?

中断是CPU处于被动状态下来接受设备的信号,而轮询是CPU主动去查询该设备是否有请求。凡事都是两面性,所以,看效率不能简单的说那个效率高。如果是请求设备是一个频繁请求cpu的设备,或者有大量数据请求的网络设备,那么轮询的效率是比中断高。如果是一般设备,并且该设备请求cpu的频率比较底,则用中断效率要高一些。主要是看请求频率。

中断注册函数和中断注销函数

request_irq free_irq

设备驱动模型三个重要成员是?platfoem总线的匹配规则是?

总线,设备,驱动。匹配规则就是当有一个新的设备挂起时,总线被唤醒,match函数被调用,用device名字去跟本总线下的所有驱动名字去比较。相反就是用驱动的名字去device链表中和所有device的名字比较。如果匹配上,才会调用驱动中的probe函数,否则不调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值