linux驱动程序入门3
linux驱动程序入门
1 seq_file机制
1.1 seq_file原理
普通的文件没有一定的组织结构,文件可以从任意位置开始读写。有一种文件与普通文件不同,它包含一系列的记录,而这些记录按照相同的格式来组织,这种文件称为顺序文件(sequential file)。
struct seq_file {
char *buf; //缓冲
size_t size; //大小
size_t from;
size_t count;
size_t pad_until;
loff_t index;
loff_t read_pos;
u64 version;
struct mutex lock; // 锁
const struct seq_operations *op; //seq 操作
int poll_event;
void *private;
};
seq_file常用操作接口如下:
int seq_open(struct file *, const struct seq_operations *); //打开
int seq_read(struct file *, char __user*, size_t, loff_t *); //读
loff_t seq_lseek(struct file *, loff_t, int); // 定位
int seq_release(struct inode, struct file *); // 释放
int seq_escape(struct seq_file *, const char *, const char *); // 写缓冲
int seq_putc(struct seq_file *m, char c); // 把一个字符输出到seq_file文件
int seq_puts(struct seq_file *m, const char s); // 把一个字符串输出到seq_file文件
seq_operation结构是seq_file机制中所需要实现的操作接口。
struct seq_operation {
void *(*start)(struct seq_file *m, loff_t *pos); // 开始操作,返回pos指向记录
void *(*stop)(struct seq_file *m, void *v); // 关闭操作
void *(*next)(struct seq_file *m, void *v, loff_t *pos); // 寻找seq_file文件中的下一个记录
int (*show)(struct seq_file *m, void*v); //格式化输出记录信息
}
1.2 /proc文件系统
Linux内核中的/proc 文件系统是一种特殊的文件系统,通过它可以在运行时访问内核的内部数据结构、改变内核设置,内核可以通过它向进程发送信息。应用程序可以通过/proc 文件系统获取有关进程的有用信息,Linux中的ps、top命令就是通过读取/proc下的文件来获取它们需要的信息。
,/proc 主要存放由内核控制的状态信息,它存储于内存中而不是硬盘或其他存储设备上。/proc 文件系统的根目录就是/proc。对于系统中的任何一个进程来说,在proc的子目录里都有一个同名的进程ID。
用/proc 文件系统可以获取进程信息、电源管理(APM)信息、CPU信息(cpuinfo)、负载信息(loadavg)、系统内存信息(meminfo)等等。
fz@fz-VMware-Virtual-Platform:/proc$ ls
1 1690 2014 29 322 34 3614 371 384 4016 4279 48 57 79 consoles misc
10 1691 2017 3 3220 340 3615 372 385 4024 4280 4804 58 8 cpuinfo modules
100 17 2020 30 3221 341 362 3720 3852 4026 4281 4805 59 80 crypto mounts
101 1714 2055 301 3222 342 3621 3727 3857 4040 43 4806 6 81 devices mpt
102 1715 21 302 3223 345 3628 373 386 4049 4316 4838 60 82 diskstats mtrr
103 1718 2127 303 3224 346 363 3732 387 41 4324 4839 61 83 dma net
104 1721 2164 304 323 347 3634 3733 388 4109 4348 4840 62 84 driver pagetypeinfo
105 1723 2176 306 324 348 364 3735 389 4111 4358 49 622 85 dynamic_debug partitions
107 1724 22 307 325 349 365 3736 39 4113 437 493 623 86 execdomains pressure
108 1736 2218 308 326 35 3653 3737 390 4114 438 4982 625 87 fb schedstat
109 1738 23 309 327 350 366 3739 391 4132 4393 4989 63 88 filesystems scsi
11 174 2367 31 328 352 3665 374 3915 4160 44 4995 630 89 fs self
111 1748 2368 3102 329 353 3667 3740 392 4196 4403 5 64 9 interrupts slabinfo
12 1757 2371 311 3291 3538 367 375 393 42 4449 50 65 90 iomem softirqs
121 1767 2372 312 33 3539 3675 3755 3930 421 4450 5000 66 91 ioports stat
1258 177 2379 313 330 354 368 3758 394 422 4451 5005 67 92 irq swaps
1259 1797 24 314 3301 3540 3681 376 395 4266 45 5011 68 93 kallsyms sys
1265 18 2446 315 3308 3548 3682 3760 396 4267 4527 51 69 95 kcore sysrq-trigger
1266 181 2447 316 331 355 3683 3762 397 4268 4531 52 7 96 key-users sysvipc
1274 1866 2448 317 332 356 3684 377 3973 4269 4538 525 70 97 keys thread-self
1281 1879 2461 3176 333 3562 3686 378 398 4270 4594 529 71 98 kmsg timer_list
1283 1882 25 318 3335 3564 369 379 399 4271 46 53 72 99 kpagecgroup tty
13 19 2590 3187 334 357 3690 3792 3991 4272 4660 530 73 acpi kpagecount uptime
14 1926 2591 319 335 358 3695 38 4 4273 47 532 74 asound kpageflags version
15 196 26 32 336 359 37 380 40 4274 4722 54 749 bootconfig latency_stats version_signature
16 199 2603 320 337 3590 370 381 400 4275 4728 545 75 buddyinfo loadavg vmallocinfo
168 2 2654 321 3375 36 3703 382 4000 4276 4735 55 76 bus locks vmstat
1687 20 27 3215 338 360 3705 3825 401 4277 476 551 77 cgroups mdstat zoneinfo
1689 2012 28 3216 339 361 3707 383 4011 4278 4763 56 78 cmdline meminfo
fz@fz-VMware-Virtual-Platform:/proc$
1.3 /proc文件系统接口
/proc文件系统的条目用proc_dir_entry结构描述
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid; //用户
kgid_t gid; //用户组
loff_t size;
const struct inode_operations *proc_iops; // 节点操作
const struct file_operations *proc_fops; // 文件操作
struct proc_dir_entry *parent; // 父路径
struct rb_root subdir;
struct rb_node subdir_node;
void *data;
atomic_t count; // 使用计数
atomic_t in_use; //当前调用者数量
void *data;
atomic_t count;
atomic_t in_use;
struct completion *pde_unload_completion;
struct list_head pde_openers; /* who did ->open, but not ->release*/
spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
u8 namelen;
char name[];
}
下面是几个重要的/proc文件系统接口函数。
// 该函数用于创建一个 proc 目录
// name 指定要创建的 proc 目录的名称
// parent为该 proc 目录所在的目录
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
// proc_create 函数用来创建 proc 条目
// name 为文件名称。mode 为文件权限。
// parent为文件的父目录的指针,它为 null 时表示父目录为/proc
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);
// proc_create_data 函数只比 proc_create 函数多了一个 data 参数,用于填充 proc_dir_entry结构的 data 成员
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data);
// proc_remove 函数用于删除 de 指向的 proc 条目
void proc_remove(struct proc_dir_entry *de);
// remove_proc_entry 函数用于删除 parent 目录下名为 name 的 proc 条目。parent 为 NULL表示父目录为/proc
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
下面的例子演示了如何创建/proc 文件系统的节点,并对其进行读写操作。
static const struct file_operations proc_simple_fops = {
.owner = THIS_MODULE,
.write = simple_write,
.read = seq_read,
.open = simple_proc_open,
.release = single_release,
};
int init_simple_module(void)
{
int ret = 0;
simple_buffer = (char *)vmalloc(MAX_simple_LENGTH);
if (!simple_buffer)
{
ret = -ENOMEM;
}
else
{
memset(simple_buffer, 0, MAX_LENGTH);
proc_entry = proc_mkdir("demo", NULL);
proc_status = proc_create("status", 0, proc_entry, &proc_simple_fops);
if (!proc_status)
{
ret = -ENOMEM;
vfree(simple_buffer);
printk(KERN_INFO "demo: Couldn't create proc entry\n");
}
else
{
printk(KERN_INFO "demo: Module loaded.\n");
}
}
return ret;
}
void cleanup_simple_module(void)
{
proc_remove(proc_entry);
vfree(simple_buffer);
printk(KERN_INFO "demo: Module unloaded.\n");
}
以下是打开操作的实现
static int simple_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, simple_show, inode->i_private);
}
以下是读操作的实现,它实现了打印系统当前进程信息的功能
static int simple_show(struct seq_file *file, void *iter)
{
struct task_struct *p;
char state;
seq_printf(file, "%5s%7s%7s%7s%7s%7s%7s %s\n\n",
"PID", "UID", "PRIO", "POLICY", "STATE", "UTIME", "STIME", "COMMAND");
for_each_process(p)
{ // 遍历进程
int pid = p->pid;
if (unlikely(!pid))
continue;
switch ((int)p->state)
{
case -1:
state = 'Z';
break;
case 0:
state = 'R';
break;
default:
state = 'S';
break;
}
seq_printf(file, "%5d%7d%7d%7d%7c%7d%7d %s\n",
(int)p->pid, (int)p->tgid, (int)p->rt_priority,
(int)p->policy, state, (int)p->utime, (int)p->stime, p->comm);
}
return 0;
}
以下是写操作的实现代码:
ssize_t simple_write(struct file *flip, const char __user *buff, size_t len, loff_t *offset)
{
if
(len > MAX_LENGTH)len = MAX_LENGTH; if (copy_from_user(simple_buffer, buff, len )) {
return -EFAULT; } simple_buffer[len] = 0; printk(KERN_INFO "simple_write: %s\n",simple_buffer);
return len;
}
本例运行如下:
insmod demo.ko
cat /proc/demo/status
2. Linux 内核导读
2.1 Linux 内核组成
Linux 内核主要由五个部分组成:进程调度、内存管理、文件系统、网络子系统、设备管理。
(1)进程调度(Process Schedule):Linux 支持多任务与多进程,Linux 内核调度器基于优先级可动态调整的进程调度器。Linux 进程的通信支持系统 V 的各种通信机制。Linux 内核从 2.6.23 版本开始采用 CFS(Completely Fair Scheduler)调度器。CFS 的主要理念是为任务提供处理器时间方面的公平性,确保每个任务能够有时间执行。CFS 采用红黑树思想来管
理进程调度。
(2)内存管理(Memory Management):Linux 的内存管理支持虚拟内存,它采取的是分页机制。内存管理子系统允许多个进程安全地共享主内存区域。通过内存管理,Linux 可支持超过实际内存大小的内存地址,磁盘可以被用作内存,磁盘与内存之间可以相互交换。嵌入式处理器中的 MMU 单元就是用来实现内存管理的,虚拟地址到物理地址的映射就是由MMU 单元完成的。
(3)文件系统(Filesystem):Linux 内核支持多种文件系统,在 Linux 操作系统中,用户甚至可以访问 Windows 系统的文件,这得益于 Linux 内核中的虚拟文件系统(简称VFS)。VFS 使用一个通用的文件模型来管理不同的文件系统,为所有的存储设备提供了统一的接口。VFS 支持的文件系统包括 ext2、ext3、ext4、fat、jffs2、ubifs 等,多达数十种。
(4)设备管理(Device Management):Linux 支持字符设备、块设备及网络设备三类硬件设备,并提供了平台设备的概念与 sys 文件系统来管理各种设备。Linux 的设备驱动可以编译进内核,在系统启动时加载,也可以作为模块形式动态地加载。从 2.6 版内核开始,Linux 提供了统一的内核设备模型,这个模型的最高层抽象为 Kobject,这个数据结构使所有设备在底层都具有统一的接口。
(5)网络子系统(Network Subsystem):对网络协议的支持从一开始就是 Linux 的重要特性。Linux 网络子系统包括网络协议部分与网络驱动程序。网络协议部分负责实现各种网络协议,而网络设备驱动程序负责与物理网卡通信。在应用层,Linux 支持网络套接字接口。
正因为 Linux 具有强大的网络子系统,目前 Linux 在网络服务器、交换与路由设备中的应用非常广阔。
2.2 Linux 的代码结构
Linux 的代码结构
arch 硬件平台相关代码
block 块设备核心代码
crypto 加密函数库
documentation 有关内核各个部分的通用解释和注释的文本文件
drivers 设备驱动相关代码
fs 文件系统相关代码
include 内核头文件
init 内核初始化代码
ipc System V 的进程间通信
kernel 内核核心部分:进程调度、中断处理、信号处理、模块
lib 通用内核函数
mm 内存管理
net 网络通信协议代码
samples 内核例子
security 系统安全相关代码
sound 音频体系代码
编译内核的几个命令如下所示
make menuconfig //配置内核命令
make //编译生成目标文件,包括可加载模块
make zImage //编译生成内核
make modules_install //安装模块
当需要将模块安装到非默认位置的时候,可以使用 INSTALL_MOD_PATH 指定一个前缀
make INSTALL_MOD_PATH=/foo modules_install
运行这个命令后模块将被安装到 /foo/lib/modules 目录下
2.3 内核 Makefile
Linux 内核的 Makefile 分为 4 个组成部分:
(1)顶层 Makefile:在内核代码最顶层。顶层的 Makefile 文件读取 .config 文件的内容,并总体上负责 build 内核和模块。arch 目录的 Makefile 提供了硬件体系结构相关的编译信息。Scripts 目录下的 Makefile 文件包含了所有用来根据 kbuild Makefile 构建内核所需的定义和规则。
(2)config 配置文件:内核的配置文件,一般在/arch/*/configs 下面。
(3)Makefile 的通用规则:在 scripts/目录下的 Makefile.*文件中。
(4)kbuild Makefile 文件:在各级目录下面。
kbuild Makefile 的语法结构非常简单,主要包括以下几点核心内容:
1)目标定义
目标定义就是用来定义哪些内容要作为模块编译,哪些内容要编译链接进内核。例如:
obj-y += foo.o
它表示要由 foo.c 或者 foo.s 文件编译得到 foo.o 并链接进内核。如果使用$(obj-m)则表示对象文件编译成可加载的内核模块。kbuild Makefile 文件中的目标通常是下面的形式:
obj-$(CONFIG_I2C_BOARDINFO)+= i2c-boardinfo.o
obj-$(CONFIG_I2C)+= i2c-core.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
上面的语句告诉编译器编译选项对应的目标文件。
2)多文件模块的定义
如果一个模块由多个文件组成,则采用模块名加 -objs 后缀或者 -y 后缀的形式来定义模块的组成文件。例如:
obj-$(CONFIG_FB) += fb.o
fb-y:= fbmem.o fbmon.o fbcmap.o fbsysfs.o \
modedb.o fbcvt.o
fb-objs:= $(fb-y)
上例中目标模块名为 fb,它依赖于 fbmem.o、fbmon.o、fbcmap.o、fbsysfs.o、modedb.o、
fbcvt.o 等文件,这些文件最终链接生成 fb.ko 文件。上面的斜杠\表示换行。
3)目录迭代
目录迭代是将目标依赖的文件指向另一个目录。
obj-$(CONFIG_FB_OMAP) += omap/
如果 CONFIG_FB_OMAP 的值为 y 或 m,kbuild 会将 omap 目录列入向下迭代的目标
中,但是其作用也仅限于此,至于 omap 目录下的文件是要作为模块编译还是链接入内核,
还要由 omap 目录下的 Makefile 文件的内容来决定。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)