你将收获

掌握Linux内核中常用的链表、队列数据结构、学会使用面向对象的思想和方法去分析Linux内核模块、子系统等复杂软件框架。

适用人群

嵌入式工程师、学习者

课程介绍

学习嵌入式一段时间了,开始学习Linux内核、驱动了,发现看不懂?看内核代码还是感觉很吃力,云里雾里? 本期课程主要侧重于数据结构基本功的学习和Linux内核中的面向对象思想。掌握了这两项技能,再去分析Linux内核中复杂的子系统和驱动代码,相信你会跟以前有不一样的体验和收获。
讨论留言

正在加载中...

同学笔记

  • q1w21_2 2020-08-18 15:18:01

    来源:Linux内核中的面向对象思想:封装(上) 查看详情

    导出符号可以被其他模块使用,只需使用前声明一下即可。

    模块使用以下宏导出符号到内核符号表中:

    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中打印如下信息

  • q1w21_2 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
    

     

  • q1w21_2 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()函数,通过结构体成员的指针找到对应结构体的指针

     
     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

没有更多了