根据面经准备面试-第一期-2026小米嵌入式软开
8.昨天有个问题,I/O复用、select、poll、epoll的区别没解释清楚
I/O 复用就是:你可以同时盯着所有 10 个水龙头,哪个先出水就先处理哪个,其他没出水的继续盯着 —— 这样一个人就能高效处理多个 I/O 操作。
select是最老的复用工具,拿着水龙头编号进行检查,数量限制,最多1024个fd从用户到内核
poll是select升级版,解决数量限制,使用链表数组记录文件描述符fd
epoll先告诉epoll
“我要监控这 1000 个水龙头”,epoll
会把它们记在一个 “红黑树”(高效的数据结构)里,只注册一次,不用每次,内核会盯着这些水龙头,一旦某个出水(有数据),就把它放进 “就绪链表”(类似 “报警列表”)。
形象点说:
select
像 “小卖部老板盯几个货架”,poll
像 “超市店员盯一排货架”,epoll
像 “仓库管理员用监控器盯所有货架,只处理报警的”。
9.linux字符设备驱动程序的设计流程,file_operations 中read write,应用程序怎么读取到字符设备中的数据;
设计流程
1.先定义file_operations
结构体,实现open
/read
/write
/release
等操作函数
2.注册字符设备:通过cdev_init
初始化cdev
,cdev_add
注册到内核,分配设备号(alloc_chrdev_region
);
3.创建设备文件:通过class_create
和device_create
自动生成/dev/xxx
(替代手动mknod
)
read/write
read
:从设备读取数据到用户空间,用copy_to_user(dst, src, len)
(内核→用户);write
:从用户空间写入数据到设备,用copy_from_user(dst, src, len)
(用户→内核)
应用程序访问方式
int fd = open("/dev/xxx", O_RDWR)
:打开设备,获取文件描述符;read(fd, buf, sizeof(buf))
:调用驱动的read
函数,数据通过copy_to_user
传递到buf
;write(fd, buf, len)
:调用驱动的write
函数,数据通过copy_from_user
从buf
传入内核
10.系统调用过程、platform 总线驱动设计、mmap 函数
系统调用1.用户态调用库函数,触发软中断
2.陷入内核态,保存用户态上下文,寄存器,栈
3.内核通过系统调用号查sys_call_table,执行对应内核函数(如sys_read
)
执行完毕,恢复上下文,返回用户态
platform总线驱动
1.定义platform-device描述设备资源,如地址、中断,或由设备树组成
2.定义platform-driver实现probe初始化设备,注册字符设备,remove清理函数
3.通过platform-driver-register注册驱动,内核自动匹配设备
mmap函数
作用是将内核空间内存(如设备缓冲区)映射到用户空间,用户程序可直接访问,避免read
/write
的数据拷贝,提高效率。驱动中需实现file_operations
的mmap
函数,通过remap_pfn_range
建立虚拟地址映射
(11)UART串口有时钟线吗,他是怎么保证数据发完之后对方知道你发完的,UART怎么保证数据的准确性;
uart无专用时钟线,双方通过预设波特率115200同步数据
uart帧格式包含停止位,1,2个bit,高电平,表示一帧数据结束,例如一个起始位,低+8个数据位+1个停止位(高)
准确性保证
硬件上通过奇偶校验位(奇校验、偶校验)检测单bit错误
软件上可添加CRC校验或超时重传机制如modbus协议
(12)多个进程访问同一个文件时,文件描述符是一样的吗?
不同,文件描述符fd,是进程私有表的索引,每个进程打开同一文件,会分配不同pd,但这些fd最终指向内核中同一个struct file结构体,记录文件偏移量、权限
(13)uboot的启动流程了解吗;stage1 和 stage2 。uboot源码看过吗,其中的数组?(uboot代码必须理解)
uboot相当于bootloader的一种,
1.初始化硬件
2.引导内核启动把存在 Flash 里的内核文件(比如zImage
)读到内存,然后告诉 CPU“跳到内核的起始地址,开始运行内核”。
- stage1(汇编实现,如start.S):初始化 CPU(禁用中断、设置工作模式);初始化关键外设(时钟、SDRAM);将 uboot 代码从 Flash(如 NAND)搬移到 SDRAM;跳转到 stage2 的 C 入口函数(board_init_r)。
- stage2(C 语言实现,如board_init_r):初始化更多外设(网卡、SD 卡、LCD);解析环境变量(如bootcmd、bootargs);加载内核镜像到 SDRAM(从 Flash 或网络);跳转到内核入口(如kernel_entry)。
- 命令表
cmd_tbl_t
:存储 uboot 支持的命令(如bootm
、tftp
),通过U_BOOT_CMD
宏注册; - 设备驱动表:如
driver_lists
,存储各类外设驱动(如 SPI、I2C 驱动)。
源码中的数组:
补充:uboot相当于设备上电后第一个运行程序、负责唤醒硬件、如初始化内存、cpu时钟、串口、硬盘
硬件准备好后,把kernel从硬盘加载到内存,内核管理所有硬件资源(CPU、内存、摄像头、传感器等)
(14)添加过uboot中的命令吗?linux文件系统的格式.
定义命令结构体和实现函数并且加入makefile,重新编译uboot
Linux 文件系统格式:
- 磁盘类:ext2/3/4(通用)、xfs(高性能)、btrfs(支持快照);
- 嵌入式专用:jffs2(闪存,支持擦除均衡)、yaffs2(NAND 闪存);
- 虚拟文件系统:proc(内核信息)、sysfs(设备信息)、tmpfs(内存文件系统)。
(15)多线程通信
多线程共享进程地址空间,通信方式主要依赖同步机制:
1.互斥锁,保护临界资源,同一时间仅允许一个线程访问
2.条件变量,线程件同步,如生产者-消费者模型
3.信号量,控制并发线程树
4.读写锁,读多写少场景优化,允许多个读线程同时访问,写线程独占
(16)共享内存,进程间通信方式, 共享内存如果发生泄漏,怎么查看?ipcs -m?
共享内存:是最快的 IPC(也就是进程通信) 方式,通过内核创建一块物理内存,映射到多个进程的虚拟地址空间,直接访问无需拷贝。
其他 IPC 方式:管道(匿名 / 命名)、消息队列、信号量、信号、Socket
共享内存泄漏查看:
- 用
ipcs -m
查看系统中所有共享内存(含shmid
、权限、大小); - 若进程退出未删除共享内存,会残留,可通过
ipcrm -m shmid
手动删除。
(17)创建的设备文件 /dev/下的,读取时文件描述符一样吗?
不同。文件描述符是进程私有资源,每个进程打开/dev
下的同一设备文件时,内核会分配不同的 fd(如进程 A 的 fd=3,进程 B 的 fd=4),但它们指向内核中同一个struct file
结构体(关联设备驱动的file_operations
)。
(18)算法相关:反转链表,链表的头插和尾插法;
这个不太好解释,网上很多
(19)画过原理图吗,用什么软件画的,AD? 那在AD软件中在如果根据原理图上的器件快速选中PCB图上的器件
在 AD 中根据原理图器件快速选中 PCB 器件的方法:
- 打开原理图和 PCB 文件,确保两者同步(执行 “Design→Update PCB Document”);
- 在原理图中选中目标器件,按
Ctrl+F
,或通过 “Tools→Cross Select Mode” 开启交叉选择模式,PCB 中对应器件会自动高亮选中。
(20)了解Makefile吗,Makefile语法格式?
target: dependencies # 目标: 依赖文件 command # 生成目标的命令(必须以Tab开头)
关键要素:
- 变量:
CC = gcc
(编译器)、CFLAGS = -Wall -O2
(编译选项),引用时用$(CC)
; - 自动变量:
$@
(目标名)、$^
(所有依赖)、$<
(第一个依赖);
(21)struct结构体的大小,字节对齐、数组 sizeof()大小;
- 结构体成员对齐到自身大小的整数倍(如
int
占 4 字节,对齐到 4 的倍数); - 结构体总大小是最大成员大小的整数倍(添加填充字节)。这里可以看下b站陈子青
数组 sizeof:返回数组总字节数 = 元素个数 × 单个元素大小,与是否初始化无关。
char arr[10]
:sizeof(arr)=10
;
int arr[5]
:sizeof(arr)=20
(5×4)