对于linux而言,.ko文件为驱动文件,在终端可以使用lsmod列出已经安装的模块,使用insmod xxx.ko安装所需要的模块,modinfo xxx.ko打印某个模块提供的信息,rmmod xxx卸载某个不需要的模块。
insmod与module_init宏。在源代码中,使用宏声明了一个函数,使用module_init()宏对宏内的函数进行insmod的绑定,insmod中会去执行宏所绑定的函数。dmsg指令可看见函数内打印。
module_exit宏与rmmod。也是一种module_exit()宏对函数的绑定与rmmod对其内函数的执行。
insmod的version magic与zImage中的version magic因一模一样,否则会导致驱动模块安装不了,这是一种安全措施。
如何保证内核源代码树与模块内核源代码一样(version magic)保证两个用的是同一个内核源码树即可。
模块的常用宏可用modinfo查看。
MODULE_xxx宏用于添加模块描述信息,MODULE_LICENSE("GPL")为许可证信息为GPL,GPL是指内核为GPL的内核;MODULE_AUTHOR()为作者描述;MODULE_DESCRIPTION()为模块描述(作用);MODULE_ALIAS()模块别名。
函数修饰符:
static int __init chrdev_init(void);
static void __exit chrdev_exit(void);
__init是一个宏,一个内核宏,与段相关,放入.init.text段默认为.text段。__exit也是一个宏把这个函数.exit.text段默认为.text段。所有__init的函数是被统一存在特定地方的,因为安装是一次性的,所以安装完成后这个段会被释放,__exit同理。
printk()只能在内核范围使用,printf的调试信息一般为全开或全关,但在内核中的整体调试时,要善用打印级别做调试信息打印,可以靠更改打印级别控制可见信息输出。
<linux/module.h>里存放了module_init;和module_exit。 <linux/init.h>存放了__init;和__exit。驱动头文件与应用层头文件不同,应用层头文件由编译器提供,驱动由内核提供(内核源码)/include/linux/xxx/xxx.h。
MAKEFILE分析,KERN_DIR= 这是内核源码树目录,obj-m+=xxx.o编译目标xxx.c对应,-m是将其编译为一个模块。
make -c $(KERN_DIR) M='pwd' modules
本质上是make modules,进入内核源码树中用内核模块规则进行模块编译,完成后把文件移动回来。KERN_DIR=/Root/driver/kernel要精准到kernel的位置就行。
内核启动后会打印释放init段的内存大小。
嵌入式系统工作原理:应用层——>API——>设备驱动——>硬件。
API指write、read、open、close等,驱动源码中应该提供这些API实体。
flie_operations结构体:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
包装了多种函数指针,使驱动接口与API相接,挂接实体函数;设备驱动向内核注册时提供该结构体类型变量;每个驱动都有这样的一个结构体。
注册函数是由内核提供的,注册前驱动是用不了的,注册是向内核进行注册,注册后驱动就有了登记可以使用。