linux面试_设备树面试问题-优快云博客
1 设备树
在内核源码中,存在大量对板级细节信息描述的代码。这些代码充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目录,对内核而言这些platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data绝大多数纯属垃圾冗余代码。为了解决这一问题,ARM内核版本3.x之后引入了原先在Power PC等其他体系架构已经使用的Flattened Device Tree。
总结:在arm3.0之后引入了设备树的概念,设备树就是板级细节信息描述的代码,,开源文档:一种描述硬件资源的数据结构。 通过bootload讲硬件资源传输给内核,让内核和硬件资源相对独立
设备树结构:
根节点、子节点和属性。根节点是整个设备树的入口点,它代表了整个硬件系统。子节点是根节点的子级节点,代表了系统中的各个设备和子系统。每个节点都可以包含多个属性,用于描述设备的配置和特性。
设备树包含DTC(device tree compiler),DTS(设备树语法)(device tree source和DTB(device tree blob)。
- DTS(device tree source)
DTS
是一种ASCII
文本格式的设备树描述,在ARM Linux
中,一个dts
文件对应一个ARM
的设备,该文件一般放在arch/arm/boot/dts/
目录中。
- DTC(device tree compiler)
DTC
是将.dts
编译为.dtb
的工具,相当于gcc。
- DTB(device tree blob)
dtb文件是.dts 被 DTC 编译后的二进制格式的设备树文件,它由Linux内核解析,也可以被bootloader进行解析。
dts 是一种ASCII文件:
在内核中编程device_node (每个节点都会变成这个)
Dtb展开成Device_node时,内核还是不能直接使用,内核要将Device_node转换成
platform_device才可以开始使用
内存映射
mmap
是一个在 Linux 系统上用于内存映射的函数,它允许程序将一个文件或其他对象映射进内存空间,就像访问内存一样直接操作该对象。mmap
主要用于文件的输入/输出操作,以及共享内存。
操作GPIO管脚的控制多种种基本方式
1 shell命令:
echo "out" > /sys/class/gpio/gpio23/direction # 设置为输出
# 或者
echo "in" > /sys/class/gpio/gpio23/direction # 设置为输入
2 devmem2
3 系统调用
4 io命令(io -r -4 -寄存器地址)
bootloader
bootloader和uboot的区别?
bootloader是启动装载。这是一段很小的程序,用于在系统上电启动初期运行,
初始化关键接口,如内存,串口,关闭中断,关闭看门狗,引导系统进入内核
的一段初始化的程序。它主要任务就是将内核映像从硬盘读到RAM中,然后跳转
到内核的入口点去运行内核,从而建立系统运行的必要环境。
uboot:是bootloader的一种
编写驱动程序的一般方法
编写驱动程序的套路:
① 确定主设备号,也可以让内核分配
② 定义自己的 file_operations 结构体
③ 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构 体
④ 把 file_operations 结构体告诉内核:register_chrdev
⑤ 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这 个入口函数
⑥ 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev
⑦ 其他完善:提供设备信息,自动创建设备节点:class_create, device_create
⚫ 驱动怎么操作硬件?
◼ 通过 ioremap 映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
⚫ 驱动怎么和 APP 传输数据?
◼ 通过 copy_to_user、copy_from_user 这 2 个函数。
IIC驱动框架
设备树中添加总线以及总线下的从设备
I2C驱动有4个重要的东西,I2C总线、I2C驱动、I2C设备、I2C设备器
I2C总线:维护驱着两个链表(I2C动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等
I2C驱动:对应的就是I2C设备的驱动程序
I2C设备:是具体硬件设备的一个抽象
I2C设配器:用于I2C驱动和I2C设备间的通用,是SOC上I2C控制器的一个抽象
- 1、注册I2C驱动
- 2、将I2C驱动添加到I2C总线的驱动链表中
- 3、遍历I2C总线上的设备链表,根据
i2c_device_match
函数进行匹配,如果匹配调用i2c_device_probe
函数 - 4、
i2c_device_probe
函数会调用I2C驱动
的probe
函数
SPI驱动框架
控制器驱动程序层叫 spi_master ,主要提供transfer函数,进行spi协议的收发。spi_master 也是基于 Platform 模型的,注册 spi_master 时也会扫描一个链表进行注册设备
另一层是设备驱动层,基于 spi_bus_type,相比 i2c 在device中需要提供的信息多一些,需要有名字、片选、最大速率、模式、中断号等等,在driver里则使用spi_read、spi_writer 等函数,最终也会调用到 master->transfer 函数进行发送接收。
相比 I2C ,SPI驱动的框架是要简单的,因为它少了两种注册device的方式,另外它不需要像I2C一样去发送Start信号和设备地址去探测设备,SPI只需要片选选中就行了。但是它的底层收发的控制,相对I2C要复杂一点,毕竟4根线。
网络编程
在linux网络编程中给TCP和udp是传输层的主要协议
TCP:
TCP是一种面向连接的协议,它通过三次握手建立连接,然后在连接上进行可靠的数据传输。TCP使用序列号和确认应答(ACK)来保证数据的可靠传输,通过滑动窗口和拥塞控制算法进行流量控制和拥塞控制。
UDP的原理
相比于TCP,UDP是一种更简单的协议。UDP是无连接的,它直接在IP协议之上发送数据报,不提供数据的可靠传输、流量控制或拥塞控制。因此,UDP的延迟和开销较小,适用于对实时性要求高的应用,如语音和视频通信。
使用的是Socket通信进行上述网络通信的
TCP通信详解
在TCP通信中,我们首先需要建立一个TCP连接,然后才能在这个连接上进行数据传输。以下是
TCP通信的详细步骤和时序图:
-
服务器执行socket()函数,创建一个新的套接字。
-
服务器执行bind()函数,将套接字绑定到一个指定的地址(包括IP地址和端口号)。
-
服务器执行listen()函数,使套接字进入监听模式,等待客户端的连接请求。
-
服务器执行accept()函数,阻塞并等待客户端的连接请求。当一个客户端连接请求到来时,accept()函数返回,并创建一个新的套接字与客户端进行通信。
-
客户端执行socket()和connect()函数,向服务器发起连接请求。connect()函数会发送一个SYN(同步)数据包到服务器。
-
服务器收到SYN数据包,在accept()函数返回后,回复一个SYN+ACK(确认应答)数据包给客户端。
-
客户端收到SYN+ACK数据包,回复一个ACK数据包给服务器,完成TCP连接的建立。
-
TCP连接建立后,客户端和服务器可以通过read()和write()函数进行数据传输。
与TCP不同,UDP是一种无连接的协议,客户端和服务器不需要建立连接就可以直接发送数据。以下是UDP通信的详细步骤:
-
服务器执行socket()函数,创建一个新的套接字。
-
服务器执行bind()函数,将套接字绑定到一个指定的地址(包括IP地址和端口号)。
-
客户端执行socket()函数,创建一个新的套接字。
-
客户端可以直接通过sendto()函数发送数据到服务器。
-
服务器通过recvfrom()函数接收客户端发送的数据。
多线程编程
多线程编程是一种利用操作系统的多任务处理机制,以实现程序并发执行的编程模型。
在Linux中,线程是通过pthread
库来实现的。线程的创建和管理都是通过pthread
库提供的函数完成的。以下是一个简单的线程创建示例:
【Linux】多线程详解,一篇文章彻底搞懂多线程中各个难点!!!_linux多线程-优快云博客
linux内核中是没有线程这个概念的,而是轻量级进程的概念:LWP。一般我们所说的线程概念是C库当中的概念。
1.1线程是怎样描述的?
线程实际上也是一个task_struct,工作线程拷贝主线程的task_struct,然后共用主线程的mm_struct。线程ID是在用task_struct中pid描述的,而task_struct中tgid是线程组ID,表示线程属于该线程组,对于主线程而言,其pid和tgid是相同的,我们一般看到的进程ID就是tgid。
1.2如何查看一个线程的ID
命令:ps -eLf
每一个线程,默认在共享区中占有的空间为8M
1.4.1线程带来的优势
线程会共享内存地址空间。
创建线程花费的时间要少于创建进程花费的时间。
终止线程花费的时间要少于终止进程花费的时间。
线程之间上下文切换的开销, 要小于进程之间的上下文切换。
线程之间数据的共享比进程之间的共享要简单。
充分利用多处理器的可并行数量。(线程会提高运行效率,但当线程多到一定程度后,可能会导致效率下降,因为会有线程调度切换。)
1.4.2线程带来的缺点
- 健壮性降低:多个线程之中, 只要有一个线程不够健壮存在bug(如访问了非法地址引发的段错误) , 就会导致进程内的所有线程一起完蛋。
- 线程模型作为一种并发的编程模型, 效率并没有想象的那么高, 会出现复杂度高、 易出错、 难以测试和定位的问题。
文件IO操作
解决进程之间并发问题
多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理;
linux内核锁机制有信号量、互斥锁、自旋锁还有原子操作。
2 原子操作:原子操作是在多线程环境中保证数据一致性和同步的关键技术
原子操作只有2种状态,一种是没做,一种是做完了,看不到正在做的状态
一、信号量(struct semaphore):信号量本质上是一个计数器
是用来解决进程/线程之间的同步和互斥问题的一种通信机制,是用来保证两个或多个关键代码不被并发调用。
也就是说信号量通过PV操作同步解决了进程/线程对临界资源利用的冲突问题;
互斥锁 :
进入临界区,想访问全局变量的时候,要先申请锁,才有资格访问。使用完后释放,如果申请不到,就会导致进程休眠