1.kbuild编译系统

kernel源码目录第一层有Kconfig(后厨原材料)和.config(点菜的清单)文件。menuconfig(菜单),Deconfig(预制菜单 熟客使用)。
在这里插入图片描述

obj-y += foo.o bar.o  // 表示foo.c和bar.c文件被编译成foo.o和bar.o
// kernel/driver/spi/build-in.a静态库是该目录下所有目标文件(.o文件)打包而成

目标由多个源文件编译得到:追加的效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
malloc不是内核API而是libc函数,参考【notes7】。

1.1 Kconfig

参考【bmc1】内核添加驱动。
在这里插入图片描述
驱动模块使用的资源一摸一样(如gpio),只能加载一个,如下只能选一个,互斥:
在这里插入图片描述
depends on只有依赖条件成立,配置项才显示。

1.2 Makefile

在这里插入图片描述
条件编译:最常用的两种格式#ifdef和#ifndef 。#undef :取消已定义的标识符
在这里插入图片描述
如下book145.c和_public.c都有 #include"_public.h",会重复包含。
在这里插入图片描述
在_public.c中如下这样写,_public.h就不会被重复包含。
在这里插入图片描述
如下$前一个tab键不能8个空格。make默认是make all,如果将all这行book3删除,则make不会编译book3,可指定make book3,book3相当于标签。-欧2是让编译效率最高,一般正式发布用。gcc命令选项 :-c编译不链接。
在这里插入图片描述
在这里插入图片描述
如下是编译内核,第一行判断路径是否存在。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
EXRA_CFLAGS用于额外编译选项:
在这里插入图片描述

2.内核模块化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1 传参

权限位S_IRUGO是用在sysfs文件系统里,static int…只是初始化给默认值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 符号导出

驱动程序编译生成的 ko 文件是相互独立的, 即模块之间变量或者函数在正常情况下无法进行互相访问。extern:声明来自另一个模块。
在这里插入图片描述
在这里插入图片描述

2.4 错误码

在这里插入图片描述
在这里插入图片描述
如下fail里将return ret。如上地址是64位内核空间最后一页,指针落到这段地址说明是错误的无效指针。
在这里插入图片描述

2.5 uevent

在这里插入图片描述

struct kobject *mykobject01;
struct kset *mykset;
struct kobj_type mytype;
static int mykobj_init(void)
{
    int ret;
    // 创建并添加一个kset
    mykset = kset_create_and_add("mykset", NULL, NULL);
    // 分配并初始化一个kobject
    mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    mykobject01->kset = mykset;
    // 初始化并添加kobject到kset
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01"); // mykobject01
    // 触发一个uevent事件,表示kobject的属性发生了变化
    ret = kobject_uevent(mykobject01, KOBJ_CHANGE);
    return 0;
}
static void mykobj_exit(void)
{
    kobject_put(mykobject01); // 释放kobject
    kset_unregister(mykset);
}
module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");

// insmod 上面.ko
// ls /sys/mykset/mykobject01/  (里面是空的,没有任何属性文件)
// dmesg | tail -n 1   或  udevadm monitor --kernel
// kobject_uevent: KOBJ_CHANGE for mykobject01

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如下device_create里调用kobject_uevent告诉用户态的二进制udev机制,说有一个设备节点的创建,udev帮对设备文件创建。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 设备号

具有相同主设备号(哪一类)设备使用相同驱动程序,次设备号(哪一个)用来标识连接系统中相同的设备。l:符号链接,s:套接字,p:管道。
在这里插入图片描述
在这里插入图片描述
如下次设备号个数不是次设备号(hello_minor)。
在这里插入图片描述
在这里插入图片描述

块设备申请主设备号

hello_major为0是系统自动分配的主设备号即register_blkdev函数分配。
在这里插入图片描述
在这里插入图片描述

2.4 字符设备驱动

如下crw—中c就是char字符设备(以字节为单位输入输出,没有固定大小,也没有缓冲区,数据会立即传输: 串口、鼠标)。块设备以b开头(以块为单位输入输出,有缓冲区,数据下发以后会在缓冲区中缓存,可以被分区,还可以格式化它的文件系统【不能裸设备访问,坏块多,都会上文件系统】:硬盘、U盘)。网络设备ifconfig查看。
在这里插入图片描述
在这里插入图片描述
cdev_init是方法填充,cdev_add是属性(如主次设备号)的填充。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

设备读写

在这里插入图片描述
write函数中一个进程写没问题,两进程写:第一个进程运行到kzalloc时,第二个进程也执行了kzalloc,只第二个进程地址保存在c中,第一个进程分配内存空间地址丢失造成内存泄漏。第一个进程运行到kzalloc时第二个进程调用了kfree,这时第一个进程执行到copy_from_user出现问题。
在这里插入图片描述
在这里插入图片描述
如下cdev_map是整个绿色:
在这里插入图片描述

杂项设备

在这里插入图片描述

struct file_operations misc_fops = { //文件操作集
    .owner = THIS_MODULE ////将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模
    .read = 
    .write = 
};
struct miscdevice misc_dev = {       //杂项设备结构体
    .minor = MISC_DYNAMIC_MINOR,     //动态申请的次设备号
    .name = "test",                  //杂项设备名字是hello_misc
    .fops = &misc_fops,              //文件操作集

};
static int __init misc_init(void)           
{ 
    int ret;
    ret = misc_register(&misc_dev); //在初始化函数中注册杂项设备
    if (ret < 0)
    {
        printk("misc registe is error \n"); //打印注册杂项设备失败
    }
    return 0;
}
static void __exit misc_exit(void)
{
    misc_deregister(&misc_dev);     //在卸载函数中注销杂项设备
    printk(" misc goodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

2.5 platform总线

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例:
在这里插入图片描述
在这里插入图片描述

//  paltform_device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define GPIO0_BASE (0xfdd60000)
#define GPIO0_DR (GPIO0_BASE + 0x0004)
#define GPIO0_DDR (GPIO0_BASE + 0x000C)
static struct resource led_resource[] = {
    [0] = DEFINE_RES_MEM(GPIO0_DR, 4),  //数据 寄存器
    [1] = DEFINE_RES_MEM(GPIO0_DDR, 4), //方向 寄存器
};
static void led_release(struct device *dev)
{}
unsigned int led_hwinfo[1] = { 7 };  // 片选偏移 ,led - GPIIO0_C7引脚
static struct platform_device led_pdev = {   // 没有设备树之前   //////////////////////////重点
    .name = "led_pdev",   //匹配下面paltform_driver.c
    .id = 0,
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
    .dev = {
            .release = led_release,
            .platform_data = led_hwinfo,
        },
};

///////////////////////////////////////////////////////////////////////////////////////
static __init int led_pdev_init(void)
{
    printk("pdev init\n");
    platform_device_register(&led_pdev);  // platform总线添加一设备
    return 0;
}
module_init(led_pdev_init);
static __exit void led_pdev_exit(void)
{
    printk("pdev exit\n");
    platform_device_unregister(&led_pdev);
}
module_exit(led_pdev_exit);
MODULE_DESCRIPTION("Platform LED device");
MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");
// paltform_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define DEV_MAJOR 243
#define DEV_NAME  "led"
static struct class *led_test_class;
//结构体led_data来管理我们LED灯的硬件信息
struct led_data {
    unsigned int led_pin;
    unsigned int __iomem *va_DDR;
    unsigned int __iomem *va_DR;
    struct cdev led_cdev;
};
static int led_cdev_open(struct inode *inode, struct file *filp)
{
    unsigned int val = 0;
    struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
    printk("led_cdev_open() \n");
    // 设置引脚输出
    val = readl(cur_led->va_DDR);  // 方向
    val |= ((unsigned int)0X1 << (cur_led->led_pin+16));
    val |= ((unsigned int)0X1 << (cur_led->led_pin));
    writel(val,cur_led->va_DDR);
    //设置默认输出高电平
    val = readl(cur_led->va_DR); // 数据
    val |= ((unsigned int)0X1 << (cur_led->led_pin+16));
    val |= ((unsigned int)0x1 << (cur_led->led_pin));
    writel(val, cur_led->va_DR);
    filp->private_data = cur_led;
    return 0;
}
static int led_cdev_release(struct inode *inode, struct file *filp)
{
    return 0;
}
static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
                  size_t count, loff_t * ppos)
{
    unsigned long val = 0;
    unsigned long ret = 0;
    int tmp = count;
    struct led_data *cur_led = (struct led_data *)filp->private_data;
    val = kstrtoul_from_user(buf, tmp, 10, &ret);
    val = readl(cur_led->va_DR); // 方向
    if (ret == 0)
    {
        val |= ((unsigned int)0x1 << ((cur_led->led_pin)+16));
        val &= ~((unsigned int)0X1 << (cur_led->led_pin));
    }
    else
    {
        val |= ((unsigned int)0x1 << (cur_led->led_pin+16));
        val |= ((unsigned int)0X1 << (cur_led->led_pin));
    }
    writel(val, cur_led->va_DR);
    *ppos += tmp;  // 更新文件位置指针
    return tmp; // 返回写入的字节数
}
static struct file_operations led_cdev_fops = {
    .open = led_cdev_open,
    .release = led_cdev_release,
    .write = led_cdev_write,
};

/////////////////////////////////////////////////////////////////////////////////////////
static int led_pdrv_probe(struct platform_device *pdev)  // 传入device,驱动需要去提取设备的资源,完成字符设备的注册等工作
{
    struct led_data *cur_led;
    unsigned int *led_hwinfo;
    struct resource *mem_DR;
    struct resource *mem_DDR;
    dev_t cur_dev;
    int ret = 0;
    printk("led platform driver probe\n");
    //第一步:提取平台设备提供的资源
    //devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
    cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
    if(!cur_led)
        return -ENOMEM;
    led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
    if(!led_hwinfo)
        return -ENOMEM;
    led_hwinfo = dev_get_platdata(&pdev->dev); // LED灯的寄存器偏移量 即 7
    cur_led->led_pin = led_hwinfo[0];
    mem_DR = platform_get_resource(pdev, IORESOURCE_MEM, 0);  // 數據寄存器
    mem_DDR = platform_get_resource(pdev, IORESOURCE_MEM, 1); //方向寄存器
    cur_led->va_DR = devm_ioremap(&pdev->dev, mem_DR->start, resource_size(mem_DR)); // 寄存器地址转化为虚拟地址
    cur_led->va_DDR = devm_ioremap(&pdev->dev, mem_DDR->start, resource_size(mem_DDR));

    //第二步:注册字符设备(才有fops)
    cur_dev = MKDEV(DEV_MAJOR, pdev->id);
    register_chrdev_region(cur_dev, 1, "led_cdev");
    cdev_init(&cur_led->led_cdev, &led_cdev_fops);
    ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
    if(ret < 0)
    {
        printk("fail to add cdev\n");
        goto add_err;
    }
    device_create(led_test_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id); // /dev/led0
    //platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
    platform_set_drvdata(pdev, cur_led);
    return 0;
add_err:
    unregister_chrdev_region(cur_dev, 1);
    return ret;
}
static int led_pdrv_remove(struct platform_device *pdev)
{
    dev_t cur_dev; 
    //platform_get_drvdata,获取当前LED灯对应的结构体
    struct led_data *cur_data = platform_get_drvdata(pdev);
    printk("led platform driver remove\n");
    cur_dev = MKDEV(DEV_MAJOR, pdev->id);
    //cdev_del删除对应的字符设备
    cdev_del(&cur_data->led_cdev);
    //删除/dev目录下的设备
    device_destroy(led_test_class, cur_dev);
    //unregister_chrdev_region, 注销掉当前的字符设备编号
    unregister_chrdev_region(cur_dev, 1);
    return 0;
}
static struct platform_device_id led_pdev_ids[] = {
    {.name = "led_pdev"}, //匹配
    {}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);
//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。
static struct platform_driver led_pdrv = {    //////////////////////////重点
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver.name = "led_pdev",
    .id_table = led_pdev_ids,
};
static __init int led_pdrv_init(void)
{
    printk("led platform driver init\n");
    //class_create,来创建一个led类
    led_test_class = class_create(THIS_MODULE, "test_leds");
    //调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销
    platform_driver_register(&led_pdrv);
    return 0;
}
module_init(led_pdrv_init);
static __exit void led_pdrv_exit(void)
{
    printk("led platform driver exit\n");   
    platform_driver_unregister(&led_pdrv);
    class_destroy(led_test_class);
}
module_exit(led_pdrv_exit);
MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");

2.6 设备驱动模型

在这里插入图片描述

... __list_add(struct list_head *new,    //node3
                              struct list_head *prev,   //head
                              struct list_head *next)   //node2 
{
        next->prev = new;  //node2->prev 等于 node3
        new->next = next; //node3->next等于 node2
        new->prev = prev; //node3->prev 等于 head
}

如下是类似管道,先入先出,node123从左往右按顺序排列。list_add函数调用如下栈,先入后出,如上图。
在这里插入图片描述

// kobject是最基础的结构体,/sys/device目录是一个kobject,里面还有很多kobject
// drivers/base/core.c:
static struct kobject * class_kobj   = NULL; 
static struct kobject * devices_kobj = NULL; 
/* 创建/sys/class */ 
class_kobj = kobject_create_and_add("class", NULL); 
/* 创建 /sys/devices */ 
devices_kobj = kobject_create_and_add("devices", NULL); 

在这里插入图片描述
如上注册完bus后生成如下目录。
在这里插入图片描述
如下创建链接就行,没必要重复。
在这里插入图片描述
在这里插入图片描述
如下比device注册做的事情少很多。
在这里插入图片描述
创建链接指向module,表示该驱动是由哪个内核模块提供能力,同样module也反向指向驱动,表示它提供什么样的驱动能力。
在这里插入图片描述
driver去扫匹配成功后不退出,因为挂在这总线上设备不一定只有一个,比如usb上挂鼠标,挂键盘。device只扫一个driver就停止。bus_for_each_drv扫的是klist_driver。如下粉色框是driver_register封装。
在这里插入图片描述

3.设备树

dts -》 dtb - 》device_node - 》platform_device。
在这里插入图片描述
DTC(Device Tree Compiler,源代码和相关工具放在scripts/dtc目录,CONFIG_DTC,也可反编译)编译器将dts/dtsi文件编译成二进制dtb文件。
在这里插入图片描述

reg=<0x100 0x4> : 地址从0x100开始的4字节寄存器区域

spi和i2c设备属于非内存映射设备,它们的地址对cpu不可访问,而父设备的驱动(即总线控制器驱动)代表cpu访问,spi reg代表spi chip select 0 , i2c reg代表设备地址

在这里插入图片描述

3.1 时钟

时钟(类似心跳)生产者(板卡厂商,定义):1.clock-cells:=0表示一个时钟,=1表示多个时钟。
在这里插入图片描述
2.clock-frequency:频率,单位HZ
3.assigned-clocks:
在这里插入图片描述
4.assigned-clocks-rates:
在这里插入图片描述
时钟消费者(驱动,引用)
1.clocks:类似assigned-clocks
2.clock-names:
在这里插入图片描述
mac1: ftgmac@14060000 {
compatible = “aspeed,ast2700-mac”, “faraday,ftgmac100”;
reg = <0x0 0x14060000 0x0 0x200>;
interrupts-extended = <&intc1_4 1>;

		clocks = <&syscon1 SCU1_CLK_GATE_MAC1CLK>;

syscon1: syscon@14c02000 {
compatible = “aspeed,ast2700-scu1”, “syscon”, “simple-mfd”;
aspeed,ast2700-scu.h , clk-ast2700.c
在这里插入图片描述

3.2 中断属性

1.interrupts:下图有3位(父亲是gic控制器),interrupt-cells给引用的子节点用的,不是当前节点。
2.interrupt-controller:当前节点是一个中断控制器。
在这里插入图片描述

ft5x06:ft5x06@38{ //触摸屏有事件 → 产生中断信号 → 通过RK_PB5引脚 → GPIO控制器接收 →  GPIO控制器向GIC报告中断号33 → CPU处理中断
	interrupts=<RK_PB5   IRQ_TYPE_LEVEL_LOW>;
	interrupt-parent = <&gpio0>
};

RK_PB5是Rockchip(瑞芯微)平台的GPIO引脚命名:
RK:Rockchip缩写
P:Port(端口)
B5:B组的第5个引脚
RK_PB5 通常对应具体的物理引脚号,比如GPIO0_B5

// 获取中断号
int irq = gpio_to_irq(RK_PB5);  // 将GPIO引脚转换为Linux内核中断号
// 注册中断处理函数
request_irq(irq, touch_handler, IRQF_TRIGGER_LOW, "ft5x06", ...);

3.interrupt-cells:约束children的interrupt属性有几位组成,为了解析方便。
4.interrupt-parent:指向谁提供中断即中断控制器。

irq_of_parse_and_map函数 :解析设备节点的"interrupts"属性, 并将对应的中断号映射到系统的中断号,拿到irq,为了后续request_irq。

irq_get_trigger_type 函数:获取interrupts属性的最后一位即触发方式。

3.3 pinctrl & gpio

pinctrl指定pin复用功能(gpio,串口,i2c…),
在这里插入图片描述
在这里插入图片描述
/proc/device-tree/…
在这里插入图片描述
在这里插入图片描述
如下gpio0:32到64。每个gpio控制器里有4组,每组8个。
在这里插入图片描述
在这里插入图片描述

4.休眠&唤醒

休眠状态指的是一种系统低功耗运行状态。在此状态下,各种支持休眠模式的外围设备也都全部进入休眠模式,CPU 挂起,所有用户态应用程序和内核态进程全部被冻结,内存处于自刷新模式。系统处于休眠状态会屏蔽所有除唤醒之外的所有命令,直到系统被某种原因唤醒才会解除此种状态。

// cat /sys/power/state【freeze: 可快速恢复,mem: 长时间 省电】 : 支持哪些挂起模式,不是当前状态
// echo freeze/mem > /sys/power/state : 触发休眠,命令敲完后,串口卡住没反应了 (在/sys/power/autosleep【CONFIG_PM_AUTOSLEEP:自动休眠唤醒】是off状态才行)

// kernel/power/suspend.c: 
int pm_suspend(suspend_state_t state)  // autosleep也是调用这行函数
{
		error = enter_state(state);  // 进入suspend状态, 具体的状态根据state值而定, 如果执行成功的话, 一直到系统退出suspend的状态后此函数才退出
}
// 如果外设没有实现dev_pm_ops,那么外设对于休眠唤醒没有任何动作
struct dev_pm_ops {
        int (*suspend)(struct device *dev);//休眠,下电,与下面成对出现
        int (*resume)(struct device *dev);//唤醒,上电
				...
        int (*runtime_suspend)(struct device *dev); // 单设备:并不是要求设备一定要进入低功耗状态, 而是要求设备在suspend后,不再处理数据,不再和CPUs、RAM进行任何的交互,直到设备的.runtime_resume被调用
        int (*runtime_resume)(struct device *dev);
};

如整机休眠时调用下面外设的.suspend。
在这里插入图片描述
在这里插入图片描述
如下查看哪个驱动模块当前有活跃锁,导致整机休眠不下去。自动休眠唤醒suspend控制器发现如果/sys/kernel/debug/wakeup_sources中active_since一列【如下就是调用wake_lock函数,也可echo设置/sys/power/wake_lock】都为0即没有持锁【即没有唤醒】就休眠。
在这里插入图片描述
如下__pm_relax用来释放先前通过__pm_stay_awake增加的唤醒请求即active_since一列。
在这里插入图片描述
在这里插入图片描述
如按触屏和播放视频都不能休眠。
在这里插入图片描述
在这里插入图片描述

/*
有时候进程在读设备时,发现设备数据还没准备好,没办法正常读取设备。或在写设备时,发现设备缓冲区满,没办法正常写设备。进程在操作设备时,如果条件不满足,就让它进入休眠等待,直到条件满足,就可唤醒进程进行后面操作。
初始化:
	DECLARE_WAIT_QUEUE_HEAD()   //初始化等待队列头,宏的静态方式
	或
	wait_queue_head_t wq;     //动态方式
	init_waitqueue_head(&wq);
休眠:	
	wait_event()   //不可被打断,死等,直到满足条件为止
	wait_event_interruptible()  //可使用信号打断它,常用
唤醒:	
	wake_up()   //对应wait_event()
	wake_up_interruptible()	 //对应上面可中断方式休眠的进程wait_event_interruptible() 
*/
# include <linux/wait.h>
DECLARE_WAIT_QUEUE_HEAD(wq);
{ // hc_read函数中
	printk(KERN_INFO "read hc_dev %p\n",hc_dev);
 // wait_event(wq,hc_dev->c!=NULL);  //第一个参数:等待队列头。第二个参数:等待条件: 如果设备字符串区(即echo值进设备文件里)为空就进入等待(唤醒方法在write函数中)。
	wait_event_interruptible(wq,hc_dev->c!=NULL);
}                                                                                                                                                                 
{ // hc_write函数中
	printk(KERN_INFO"%s write done",current->comm);
//	wake_up(&wq);  // 当写入字符设备成功后调用wakeup函数唤醒等待队列上的进程
	wake_up_interruptible(&wq);
	return count;
}

在这里插入图片描述
如下等待条件满足,如上卡住的即休眠的进程会正常退出(两个进程cat卡住休眠,执行如下一行,两个进程都不会卡住)。
在这里插入图片描述

5.proc文件系统

在这里插入图片描述
在这里插入图片描述

/*
新调试方法:利用proc文件系统在pro文件夹下创建接口,读写这个接口就可实现对内核的调试
struct proc_ops    //pro文件夹下创建接口第一种方式
proc_create()
struct seq_operations   //第二种方式
proc_create_seq()

remove_proc_entry  //移除接口
*/
#include<linux/module.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#define PROC_DEBUG
#ifdef PROC_DEBUG
#include<linux/proc_fs.h>  //传统第一种方式
#include<linux/seq_file.h>  //seq第二种方式
#endif
char * str = "hello proc\n";
#ifdef PROC_DEBUG 
int hp_open(struct inode * inode, struct file * filp)
{
	printk(KERN_INFO"open %ld\n",strlen(str));
	return 0;
}
ssize_t hp_read(struct file * filp, char __user * buff, size_t count, loff_t * f_pos)
{
	ssize_t retval=0;
	int n = strlen(str); // 内容总长度(11字节,含\n)
	if(*f_pos >= n) // 已经读完
		goto out;
	if(*f_pos + count > n)  // 防止越界
		count = n - *f_pos; // 调整读取长度
	if(copy_to_user(buff,str,count))  //将字符串str("hello proc\n")赋值到buff用户空间
	{
		retval = -EFAULT;
		goto out;
	}
	*f_pos += count; // 更新偏移量
	return count;	
out:
	return retval;
}
struct proc_ops hp_ops = {   //下面 __init函数中proc_create调用hp_ops创建接口文件
	.proc_open = hp_open,
	.proc_read = hp_read,
};
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
void * hp_seq_start (struct seq_file *m, loff_t *pos)  //pos表示当前读到哪个位置或写到哪个位置了
{
	printk(KERN_INFO"seq start\n");
	if(*pos >= strlen(str))  //pos指当前读到或写到哪个位置,索引
		return NULL;
	return &str[*pos];  //拿出字符串中字符,将地址返回,这返回值将来作为下面其他函数的v传入
}
void hp_seq_stop(struct seq_file *m, void *v)
{
	printk(KERN_INFO"seq stop\n");  //清除start函数一些工作,start里开辟一些空间或申请一些锁,这里清除
}
void * hp_seq_next (struct seq_file *m, void *v, loff_t *pos)  //改变索引值
{
	printk(KERN_INFO"seq next\n");
	(*pos)++;
	if(*pos >= strlen(str))
		return NULL;
	return &str[*pos];  
}
int hp_seq_show (struct seq_file *m, void *v)
{
	printk(KERN_INFO"seq show\n");
	seq_putc(m,*(char*)v);  //将获得到的字符一个一个打印出
	return 0;
}
const struct seq_operations seq_ops={  //构建这结构体
	.start = hp_seq_start,
	.stop = hp_seq_stop,
	.next = hp_seq_next,
	.show = hp_seq_show,
};
#endif
//11111111111111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	
{
	printk(KERN_INFO "HELLO LINUX MODULE\n");
#ifdef PROC_DEBUG
	proc_create("hello_proc",0,NULL,&hp_ops);  //第一个参数即显示在pro目录下文件名称,
 //第二个参数默认0只读权限。第三个参数父节点,null默认pro目录。最后一个参数是操作的结构体地址
	proc_create_seq("hello_seq_proc",0,NULL,&seq_ops); //就可在pro目录下创建对应节点
#endif
	return 0;
}
static void __exit hello_exit(void)
{
#ifdef PROC_DEBUG
	remove_proc_entry("hello_proc",NULL);  //第二个参数是父节点
	remove_proc_entry("hello_seq_proc",NULL);
#endif
	printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(hello_init);
module_exit(hello_exit); ...

show next调用了11次,最后一次返回null会调用stop。cat又会再调用一次,调start后直接返回null到stop。
在这里插入图片描述

6.调用堆栈内核函数

在这里插入图片描述
WARN_ON(condition)函数实际也是调用dump_stack,如WARN_ON(1)条件判断成功,类似BUGON(1)。
在这里插入图片描述

7.printk

cat /proc/cmdline查看console=ttyS0,如下图左边分支是远程登录,缺点是log_buf是环形buff(会循环覆盖,日志不全)。左右分支都有dmesg,左分支dmesg不受下面x控制,所以dmesg是最全的。
在这里插入图片描述
在这里插入图片描述

// Linux 内核为printk定义了8个打印等级,KERN_EMERG等级最高,KERN_DEBUG等级最低。在内核配置时,有一个宏来设定系统默认的打印等级 CONFIG_MESSAGE_LOGLEVEL_DEFAULT,通常该值设置为4,那么只有打印等级高于4时才会打印到终端或者串口。
// kern_levels.h
#define KERN_EMERG      KERN_SOH "0"    /* system is unusable 紧急事件,一般是系统崩溃之前的提示消息 */
#define KERN_ALERT      KERN_SOH "1"    /* action must be taken immediately 必须立即采取行动 */
#define KERN_CRIT       KERN_SOH "2"    /* critical conditions 临界状态,通常涉及严重的硬件或者软件操作失败 */
#define KERN_ERR        KERN_SOH "3"    /* error conditions 报告错误状态,经常用来报告硬件错误 */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions 对可能出现问题的情况进行警告,通常不会对系统造成严重问题 */
#define KERN_NOTICE     KERN_SOH "5"    /* normal but significant condition 有必要的提示,通常用于安全相关的状况汇报 */
#define KERN_INFO       KERN_SOH "6"    /* informational 提示信息,驱动程序常用来打印硬件信息 */
#define KERN_DEBUG      KERN_SOH "7"    /* debug-level messages 用于调试信息 */ 

// printk("[me]%s[%d].\n",__func__,__LINE__);
// printk调用printk_safe_enter_irqsave --> local_irq_save【把当前的中断状态(开或关)保存到flags中,然后禁用当前处理器上的中断】,所以:板卡 --> 没有办法跟外界通信(I2C、UART、SPI都用到中断)

在这里插入图片描述

8.动态打印

printk是全局的且只能设打印等级,动态打印可控制选择模块的打印,在内核配置打开CONFIG_DYNAMIC_DEBUG:
printk会关中断影响性能,如果在usb的read/write里printk,那么usb就没法直接用了。所以用动态打印,调试时才打开,control节点默认不输出,如下操作才输出,+p是将动态打印语句【pr_debug/dev_dbg()】转为printk语句(相当于下面的define dev_dbg …)。
在这里插入图片描述

9.trace

ltrace,strace参考【notes7】epoll,这俩底层实现是ptrace(如下)。
在这里插入图片描述

9.1 tracefs

在这里插入图片描述

10.性能工具

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.1 磁盘io

在这里插入图片描述
在这里插入图片描述

10.2 内核热点函数

在这里插入图片描述

10.3 网络

ifconfig和ip分别属于net-tools和iproute2:
在这里插入图片描述
上面是统计信息,下面是网络吞吐:
在这里插入图片描述

10.4 找到cpu瓶颈

在这里插入图片描述
在这里插入图片描述

10.5 valgrind内存泄漏排查

在这里插入图片描述
在这里插入图片描述

10.6 主频设置

在这里插入图片描述
可echo上面支持的策略 > scaling_governor。
在这里插入图片描述

10.7 驱动稳定性

access_ok:判断arg地址的数据长度是不是和len一样大:
在这里插入图片描述
likely一般用在if判断里,cpu会把当前指令后面指令预取出来,等到执行时就去执行,效率提高,但是也要判断后面那条指令大概率执不执行,执行的话取出来,不执行则跳过。
在这里插入图片描述

10.8 平均负载

性能变慢uptime查看负载情况:平均负载=平均活跃进程数(这进程不仅仅包含正在使用cpu的进程,还包含等待cpu和等待io的进程)。

当平均负载高于cpu数量(参考【notes7】并发或top查看几个cpu核)70%时,就应该排查负载高的问题,一旦负载高,会导致进程响应慢,进而影响服务正常功能。

cpu密集型进程: # stress --cpu 2 --timeout 600  # 持续600秒后自动停止,top/mpstat -P ALL 5 【监控所有cpu,每5秒输出一组数据】查看有2个cpu核100%跑满了,共6个核占满了2个,平均负载30%,uptime中load average第一个即1分钟的平均负载慢慢=2(因为2个进程在跑)

io密集型进程: # stress --i 1 --timeout 600 # 启动1个IO压力线程,不停的刷sync,脏数据刷到磁盘中,没有指定cpu,sync即io操作(均衡负载是把一些sync操作平均分给cpu)会在每个cpu上挂一些系统调用,uptime中load average第一个即1分钟的平均负载慢慢=1

大量进程超过cpu核,等待cpu释放资源,希望cpu调度自己即等待态: # stress -c 8 --timeout 600,uptime中load average第一个即1分钟的平均负载慢慢=8,pidstat查看是不是出现了等待io情况(wait一列等待CPU的时间)

11.debugfs

procfs反应进程状态信息,sysfs用于linux设备模型,debugfs(打开CONFIG_DEBUG_FS, mount -t debugfs none /sys/kernel/debug)如上面第5章节,cat /proc/filesystems查看全部文件系统(nodev表示基于内存的文件系统,不需要物理设备,如像ext4文件系统必须基于块设备)。

cat /etc/fstab是自动挂载的配置文件:/dev/mmcblk0p2 /boot … (/boot里有dtb,image,logo.bmp等),查看flash或emmc能不能正常挂载,能正常挂载,上电Bootloader读取 /boot。

/sys/kernel/debug/gpio:有哪些gpio被注册使用。
/sys/kernel/debug/pinctrl:驱动能力、上下拉(pinconf.c / cat pinconf-pins),复用功能(pinmux.c / cat pinmux-pins)。
/sys/kernel/debug/wakeup_sources:见第一章。
/sys/kernel/debug/sleep_time:系统睡眠时间,没有休眠过就没有。

11.1 源码分析

kernel/fs/debugfs:file.c / inode.c / internal.h / Makefile

static struct  file_system_type  debug_fs_type = {
        .owner =        THIS_MODULE,
        .name =         "debugfs",
        .mount =        debug_mount,
        .kill_sb =      kill_litter_super,  // 卸载,对应上行
};
MODULE_ALIAS_FS("debugfs");
static int __init debugfs_init(void)
{
        int retval;
        //在 sysfs 中创建挂载点。
        retval = sysfs_create_mount_point(kernel_kobj, "debug"); // 这里"debug"改了,对应 /etc/fstab自动挂载也需要改
        if (retval)
                return retval;
        //注册 debugfs 文件系统类型(遍历file_system_type,添加nodev加入链表),cat /proc/filesystems。
        retval = register_filesystem(&debug_fs_type); 
        //根据初始化结果清理或标记状态。
        if (retval)
                sysfs_remove_mount_point(kernel_kobj, "debug");
        else
                debugfs_registered = true;
        return retval;
}
core_initcall(debugfs_init);

11.2 api

// 建立一个内核模块debugfs_hello,并在该内核模块中调用debugfs的API创建下面3个值: 
// /sys/kernel/debug/debugfs_hello/hello: 可读写的整数值,即用户可用cat和echo命令来查看和修改该
// /sys/kernel/debug/debugfs_hello/add: 可写的,当用户调用echo命令向其写入一个值,该值会被内核模块加到sum这个值中
// /sys/kernel/debug/debugfs_hello/sum:只读的,用于计算所有输入给add的值的总和,用户可用cat命令查看该值
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/debugfs.h>  ///////
static struct dentry *dir = NULL;
static unsigned int debugfs_hello;
static u32 sum = 0;
static int add_write(void *data, u64 value)
{
    sum += value;
    return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(add_ops, NULL, add_write, "%llu\n"); // 填充add_ops
static __init int hello_init(void)
{
    struct dentry *tmp_dir = NULL;
    /* create /sys/kernel/debug/debugfs_hello/  directory */
    dir = debugfs_create_dir("debugfs_hello", NULL);
    if (!dir) {
        printk(KERN_ALERT "debugfs_create_dir failed\n");
        return -1;
    }
    /* create /sys/kernel/debug/debugfs_hello/hello  value, mode: rw*/
    tmp_dir = debugfs_create_u32("hello", 00666, dir, &debugfs_hello);
    if (!tmp_dir) {
        printk(KERN_ALERT "debugfs_create_u32 failed\n");
        return -1;
    }
    /* create /sys/kernel/debug/debugfs_hello/add  value, mode: w*/
    tmp_dir = debugfs_create_file("add", 0222, dir, NULL, &add_ops);
    if (!tmp_dir) {
        printk(KERN_ALERT "debugfs_create_file failed\n");
        return -1;
    }
    /* create /sys/kernel/debug/debugfs_hello/sum  value, mode: r*/
    tmp_dir = debugfs_create_u32("sum", 0444, dir, &sum);
    if (!tmp_dir) {
        printk(KERN_ALERT "debugfs_create_u32 failed\n");
        return -1;
    }
    return 0;
}
static void __exit hello_exit(void)
{
    printk(KERN_INFO "Exit debugfs_hello module\n");
    debugfs_remove_recursive(dir);
    dir = NULL;
}
module_init(hello_init);
module_exit(hello_exit); ...
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐