掌握Linux内核中常用的链表、队列数据结构、学会使用面向对象的思想和方法去分析Linux内核模块、子系统等复杂软件框架。
你将收获
适用人群
课程介绍
同学笔记
2020-08-18 15:18:01
导出符号可以被其他模块使用,只需使用前声明一下即可。
模块使用以下宏导出符号到内核符号表中:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);
EXPORT_SYMBOL_GPL只适合GPL许可的模块进行调用
示例:导出整数加、减运算函数符号的内核模块
calculate_lib.c
#include<linux/init.h> #include<linux/module.h> #include "calculate_lib.h" int add_int(int a,int b) { return a+b; } EXPORT_SYMBOL_GPL(add_int); int sub_int(int a,int b) { return a-b; } EXPORT_SYMBOL_GPL(sub_int); MODULE_LICENSE("GPL v2");
calculate_lib.h
#ifndef _CALCULATE_LIB_H_ #define _CALCULATE_LIB_H_ extern int add_int(int a,int b); extern int sub_int(int a,int b); #endif
Makefile
KDIR :=/lib/modules/$(shell uname -r)/build PWD=$(shell pwd) obj-m = calculate_lib.o all: $(MAKE) -C $(KDIR) M=$(PWD) rm -rf *.o *.mod.c clean: rm -rf *.ko
导出符号的引用
假设,模块B调用模块A的导出函数。
[1] 在模块A中.c源文件或者.h头文件中使用EXPORT_SYMBOL(xxxx) 导出函数.
[2] 在模块B中用 "extern" 申明函数(如, extern int xxxx)或包含模块A的头文件,申明以后就能够直接使用导出的函数了。
编译模块A,在模块A编译好后会生成符号表文件Module.symvers, 里面有函数地址和函数名对应关系,把这个文件拷贝到需要调用的模块B的源代码下,替换模块B的该文件。
然后重新编译B模块.这样就能够让模块B调用模块A的函数,以后加载模块顺序也必须先A后B,卸载相反。
加载导出函数以后,可以使用 cat proc/kallsyms来查看所有的导出符号
cat /proc/kallsyms | grep add_int
模块间函数互相调用
com_lib.c
#include<linux/init.h> #include<linux/module.h> #include "com_lib.h" void com_fun1(void) { printk(" com_fun1\n"); } EXPORT_SYMBOL_GPL(com_fun1); void com_fun2(void) { printk(" com_fun2\n"); } EXPORT_SYMBOL_GPL(com_fun2); int __init com_lib_init(void) { printk("com_lib_init\n"); return 0; } void __exit com_lib_exit(void) { printk(" com_lib_exit\n"); } module_init(com_lib_init); module_exit(com_lib_exit); MODULE_LICENSE("GPL v2");
calculate_lib.c
#include<linux/init.h> #include<linux/module.h> #include "calculate_lib.h" #include "com_lib.h" static int flag =5; int add_int(int a,int b) { printk("flag : %d \n",flag); com_fun1(); com_fun2(); printk("calculate_lib add_int\n"); return a+b; } EXPORT_SYMBOL_GPL(add_int); int sub_int(int a,int b) { com_fun2(); printk("calculate_lib add_int\n"); return a-b; } EXPORT_SYMBOL_GPL(sub_int); static int lib_init(void) { flag=1; printk("calculate_lib lib_init\n"); com_fun1(); com_fun2(); return 0; } static void lib_exit(void) { printk("calculate_lib lib_ exit \n"); } module_init(lib_init); module_exit(lib_exit); MODULE_LICENSE("GPL v2");
test_demo.c
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/device.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/pci.h> #include "calculate_lib.h" #include "com_lib.h" static int test_init(void) { int a=3,b=5,t; printk(">>test_init \n"); t = add_int(a,b); printk("\n\n add_int %d+%d=%d\r\n",a,b,t); com_fun1(); com_fun2(); return 0; } static void test_exit(void) { printk(">>test_exit\n"); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL v2");
多核模块间互相调用,可在同一个目录下编译
Makefile
obj-m +=calculate_lib.o obj-m +=test_demo.o obj-m += com_lib.o KDIR :=/lib/modules/$(shell uname -r)/build PWD=$(shell pwd) modules: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean
make后,Module.symvers文件中的内容如下:
依次加载com_lib.ko、calculate_lib.ko、test_demo.ko
insmod com_lib.ko insmod calculate_lib.ko insmod test_demo.ko
dmesg中打印如下信息
2020-08-17 17:27:57
一般应用程序直接调用系统、库、底层模块的API;如果反过来,用户写一个函数,让系统直接调用该函数,称为回调。
应用层app.c
#include<stdio.h> #include"module.h" void func1(void) { printf("func1...\n"); } void func2(void) { printf("func2...\n"); } int main(void) { runcallback(func1); runcallback(func2); return 0; }
底层模块module.c
void runcallback(void (*fp)(void)) { fp(); }
module.h
#ifndef __RUNCALLBACK__H #define __RUNCALLBACK__H void runcallback(void (*fp)(void)); #endif
底层在合适的时机就会去调用应用程序函数。
回调函数解耦
高层模块使用回调函数,底层模块在合适的时机就会去调用高层模块的函数,达到模块间双向通信的目的,但同时又引入了耦合。解耦的方法是在两个模块间定义个抽象接口。
app.c
#include <stdio.h> #include "module.h" int sd_read(void) { printf("sd read fun\n"); return 90; } struct storage_device sd={ "sdcard",sd_read }; void main() { register_device(sd); read_dev("sdcard"); }
module.c
#include <stdio.h> #include "module.h" #define MAX_DEV 100 struct storage_device dev_list[MAX_DEV]={0}; int num=0; int register_device(struct storage_device dev) { if(num<MAX_DEV) dev_list[num++]=dev; return 0; } int read_dev(char *dev_name) { int i; for(i=0;i<MAX_DEV;i++) { if(!strcmp(dev_name,dev_list[i].name)) break; } if(i==MAX_DEV) { printf("err\n"); return -1; } return dev_list[i].read(); }
module.h
#ifndef _MODULE_H_ #define _MODULE_H_ typedef int (*read_fp)(void); struct storage_device{ char name[20]; read_fp read; }; extern int read_dev(char *dev_name); extern int register_device(struct storage_device dev); #endif
2020-08-13 19:50:34
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/uaccess.h> #define GLOBALMEM_SIZE 0x100 #define MEM_CLEAR 0x01 static int global_major =0; module_param(global_major,int ,S_IRUGO); #define DEV_NUM 3 struct globalmem_dev{ struct cdev cdev; struct class *my_class; unsigned char mem[GLOBALMEM_SIZE]; }; struct globalmem_dev* globalmem_devp; static ssize_t char_dev_read(struct file *, char *, size_t,loff_t *); static ssize_t char_dev_write(struct file *, const char *, size_t,loff_t *); static int char_dev_open(struct inode *inode, struct file *filp); static int char_dev_release(struct inode *inode, struct file *filp); static int char_dev_open(struct inode *inode, struct file *filp) { printk("This chrdev is in open\n"); struct globalmem_dev *dev = container_of(inode->i_cdev,struct globalmem_dev ,cdev); filp->private_data=dev; return(0); } static int char_dev_release(struct inode *inode, struct file *filp) { printk("This chrdev is in release\n"); return(0); } static ssize_t char_dev_read(struct file *filp, char *buf, size_t len,loff_t *offet) { struct globalmem_dev *dev=filp->private_data; printk("read buffer len is %u \n",len); copy_to_user(buf,dev->mem,len); return len; } static ssize_t char_dev_write(struct file *filp, const char *buf, size_t len,loff_t *offet) { struct globalmem_dev *dev=filp->private_data; printk("the write buffer len is %u\n",len); copy_from_user(dev->mem,buf,len); return len; } struct file_operations char_dev_fops = { .owner = THIS_MODULE, .read = char_dev_read, .write = char_dev_write, .open = char_dev_open, .release = char_dev_release, }; #define DEV_NAME "globalmem" static void char_dev_setup_cdev(struct globalmem_dev *dev,int index) { printk("======char_dev_setup_cdev==============\r\n"); int err,devno=MKDEV(global_major,index); cdev_init(&dev->cdev, &char_dev_fops); err=cdev_add(&dev->cdev, devno, 1); if(err) printk("Error %d adding %s_%d",err,DEV_NAME,index); char strName[20]; sprintf(strName,"%s%d",DEV_NAME,index); printk("strName %s\r\n",strName); dev->my_class = class_create(THIS_MODULE,strName); device_create(dev->my_class, NULL, devno, NULL, strName); printk("create device %s!\r\n",strName); } static int char_dev_init(void) { int result = 0; int err = 0,i; printk("***********************\r\n"); dev_t dev = MKDEV(global_major, 0); if (global_major) { result = register_chrdev_region(dev,DEV_NUM, DEV_NAME); //静态申请设备号 } else { err = alloc_chrdev_region(&dev, 0,DEV_NUM, DEV_NAME);//动态分配设备号 global_major = MAJOR(dev); } printk("major num is %d \r\n", global_major); if (result < 0) return result; globalmem_devp=kzalloc(sizeof(struct globalmem_dev)*DEV_NUM,GFP_KERNEL); if(!globalmem_devp) { result = -ENOMEM; goto fail_malloc; } for(i=0;i<DEV_NUM;i++) { char_dev_setup_cdev(globalmem_devp+i,i); } fail_malloc: unregister_chrdev_region(MKDEV(global_major,0),DEV_NUM); return result; } static void char_dev_exit(void) { int i; for(i=0;i<DEV_NUM;i++) { device_destroy((globalmem_devp+i)->my_class, MKDEV(global_major, i)); class_destroy((globalmem_devp+i)->my_class); cdev_del(&(globalmem_devp+i)->cdev); } kfree(globalmem_devp); unregister_chrdev_region(MKDEV(global_major, 0), DEV_NUM); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Liubz");
与字符设备驱动中例子对比,字符设备驱动中的驱动仅支持单个设备,而本例可同时支持多个设备。只是简单得修改了char_dev_open()、char_dev_init()和char_dev_exit()。
在char_dev_read()和char_dev_write()函数中使用了struct globalmem_dev *dev=filp->private_data;获得globalmem_dev的实例指针。实际上,大多数Linux驱动都遵循这样的一个规则,就是将文件的私有数据private_data指向设备结构体。
支持单实例和多实例代码对比 单实例 多实例 分析 调用container_of()函数,通过结构体成员的指针找到对应结构体的指针
没有更多了
课程讨论
mystlcj_sky
weixin_38386736
张飞online
来源:队列:顺序队列
张飞online