当前位置: 首页 > news >正文

Linux——平台设备及其驱动

目录

前言

一、平台设备

二、平台驱动

三、平台驱动简单实例

四、 电源管理

五、udev 和驱动的自动加载

六、使用平台设备的LED 驱动

七、自动创建设备节点


前言


        要满足 Linux 设备模型,就必须有总线、设备和驱动。但是有的设备并没有对应的物理总线,比如 LED、RTC 和蜂鸣器等。为此,内核专门开发了一种虚拟总线一-platfomm总线,用来连接这些没有物理总线的设备或者一些不支持热插拔的设备,DM9000 网卡
设备就是挂接在这条总线上的。

一、平台设备

平台设备是用structplatform device 结构来表示的,它的定义如下

struct platform_device {const char *name;int id;bool id auto;struct device  dev;u32 num resources;struct resource *resource;const struct platform_device_id *id_entry;/* MFD cell pointer */struct mfd_cell *mfd_cell;/*arch specific additions */struct pdev_archdata  archdata;
};


        驱动开发者关心的主要成员如下。

        name: 设备的名字,在平台总线的 match 函数中可用于同平台驱动的匹配

        id:设备的ID 号,用于区别同类型的不同平台设备。
        dev:内的 struct device。
        num_resources:平台设备使用的资源个数。
        resource: 平台设备的资源列表 (数组),指向资源数组中的首元素.

        id_entry:用于同平台驱动匹配的 ID,在平台总线的 match 函数中首先尝试匹配该 ID,如果不成功再尝试用 name 成员来匹配。

        在平台设备中,最关键的就是设备使用的资源信息的描述,这是实现设备和驱动分离的关键。struct resource 的定义如下

struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;struct resource *parent,*sibling,*child;
};


驱动开发者关心的主要成员如下。
        start: 资源的开始,对于 IO 内存来说就是起始的内存地址,对于中断资源来说就是起始的中断号,对于 DMA 资源来说就是起始的 DMA 通道号。
        end:资源的结束。
        flags:资源的标志,定义在“include/linux/ioport.h”文件中,最常见的有如下几种.

        IORESOURCE_MEM:资源的类型是内存资源,也包括I/O内存。
        IORESOURCE_IRO:资源的类型是中断资源。
        IORESOURCE_DMA:资源的类型是 DMA 通道资源。
        资源可以组成一个树形结构,由成员parent、sibling和child来完成.

        平台设备及其资源通常存在于BSP(Board Support Package,板级支持包)文件中,该文件通常包含和目标板相关的一些代码。例如对于 QT2410 目标板,其对应的 BSP文件为arch/arm/mach-s3c24xx/mach-gt2410.c,现将其描述CS8900网卡的平台设备摘录如下。

183 static struct resource qt2410_cs89x0_resources[]={
184     [0] = DEFINE_RES_MEM(0X19000000,17),
185     [1] = DEFINE_RES_IRQ(IRQ_EINT9),
186 };
187
188 static struct platform_device qt2410_cs89x0 = {
189     .name            = "cirrus-cs89x0",
190     .num_resources   = ARRAY_SIZE(qt2410_cs89x0_resources),
191     .resource        = qt2410_cs89x0_resources,
192 };


        CS8900 平台设备有两个资源,分别是 IORESOURCE_MEM 和 IORESOURCE_IRQ两种类型的,并用宏 DEFINE_RES_MEM 和 DEFINE_RES_IRQ 来定义。对于 DEFINERES_MEM 宏,里面的两个参数分别是内存的起始地址和大小:对于 DEFINE_RES_IRQ宏,里面的参数则是中断号。读者可以自行查看这两个宏的定义,最终是对 start、end和flags成员进行了赋值。最终定义的平台设备是 qt2410_cs89x0,ARRAY_SIZE 是用于获取数组元素个数的宏。

向平台总线注册和注销的平台设备的主要函数如下。

int platform_add_devices(struct platform_device **devs, int num);
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);


        platform_add_devices用于一次注册多个平台设备,platform_device_register 一次只注册一个平台设备。其实,platform_add_devices 是通过多次调用 platform_device_register来实现的。platform_device_unregister 用于注销平台设备。
        当平台总线发现有和平台设备匹配的驱动时,就会调用平台驱动内的一个函数,并传递匹配的平台设备结构地址,平台驱动就可以从中获取设备的资源信息。关于资源操作的主要函数如下。
 

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
resource_size_t resource_size(const struct resource *res);


        platform_get_resource:从平台设备 dev 中获取类型为type、序号为num的资源.

        resource_size:返回资源的大小,其值为 end-start +1。

        例如,在 CS8900 网卡驱动中就有如下的代码来获取资源及其大小。

1857     mem_res  = platform_get_resource(pdev,IORESOURCE_MEM,0);
1858     dev->irq = platform_get_irq(pdev,0);
......
1865     lp->size = resource_size(mem_res);
......
1872     virt_addr = ioremap(mem_res->start,lp->size);


        代码第1857行获取了IORESOURCE_MEM资源,序号为0。代码第1858 行获取了IORESOURCE_IRQ 资源,序号也为0。所以,当资源类型不同后,序号重新开始编号.代码第1865行获取了内存资源的大小。代码第1872行使用ioremap将内存资源进行映射得到映射后的虚拟地址。

二、平台驱动


平台驱动是用struct platform_driver 结构来表示的,它的定义如下

struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;bool prevent_deferred_probe;
};


驱动开发者关心的主要成员如下。
        probe: 总线发现有匹配的平台设备时调用。

        remove:所驱动的平台设备被移除时或平台驱动注销时调用。

        shutdown、suspend 和 resume: 电源管理函数,在要求设备电、挂起和恢复时被调用。内嵌的 struct device_driver 的 pm 成员也有对应的电源管理函数。

        id_table: 平台驱动可以驱动的平台设备ID 列表,可用于和平台设备匹配。

        向平台总线注册和注销的平台驱动的主要函数如下。
 

platform_driver_register(drv)
void platform_driver_unregister(struct platform_driver *);

        因为在驱动中,经常在模块初始化函数中注册一个平台驱动,在清除函数中注销一个平台驱动,所以内核定义了一个宏来简化这些代码,宏的定义如下。

#define module_platform_driver( __platform_driver) \module_driver(__platform_driver, platform_driver_register,\platform_driver_unregister)#define module_driver(__driver, __register, __unregister, ...) \
static int __init_driver##_init(void) \
{ \return __register(&(__driver),##__VA_ARGS__); \
} \
module_init(__driver##_init);
static void  __exit __driver##_exit(void) \
{ \__unregister(&(_driver),##_VA_ARGS__); \
} \
module_exit(__driver##_exit);


三、平台驱动简单实例


        在前面的基础之上,我们可以先来编写一个简单的平台驱动,再编写一个模块来注册两个设备,代码如下
 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/platform_device.h>static void pdev_release(struct device *dev)
{
}struct platform_device pdev0 = {.name = "pdev",.id = 0,.num_resources = 0,.resource = NULL,.dev = {.release = pdev_release,},
};struct platform_device pdev1 = {.name = "pdev",.id = 1,.num_resources = 0,.resource = NULL,.dev = {.release = pdev_release,},
};static int __init pltdev_init(void)
{platform_device_register(&pdev0);platform_device_register(&pdev1);return 0;
}static void __exit pltdev_exit(void)
{platform_device_unregister(&pdev1);platform_device_unregister(&pdev0);
}module_init(pltdev_init);
module_exit(pltdev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("register a platfom device");
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/platform_device.h>static int pdrv_suspend(struct device *dev)
{printk("pdev: suspend\n");return 0;
}static int pdrv_resume(struct device *dev)
{printk("pdev: resume\n");return 0;
}static const struct dev_pm_ops pdrv_pm_ops = {.suspend = pdrv_suspend,.resume  = pdrv_resume,
};static int pdrv_probe(struct platform_device *pdev)
{return 0;
}static int pdrv_remove(struct platform_device *pdev)
{return 0;
}struct platform_driver pdrv = {.driver = {.name    = "pdev",.owner   = THIS_MODULE,.pm      = &pdrv_pm_ops,},.probe   = pdrv_probe,.remove  = pdrv_remove,
};module_platform_driver(pdrv);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple platform driver");
MODULE_ALIAS("platform:pdev");

        在 pltdev.c 文件中,代码第 7 行至第 29 行分别定义了两个平台设备,id为0和1以示区别,名字都为 pdev,没有使用任何资源。在模块的初始化函数和清除函数中分别注册和注销了这两个平台设备。
        在 pltdrv.c 文件中,代码第 34 行至第42 行定义了一个平台驱动,名字也为pdev,这样才能和平台设备匹配。pm 是电源管理函数的集合,实现了挂起和恢复两个电源管理操作。因为是虚拟设备,所以并没有做任何电源管理相关的操作。为了简单,probe 和remove函数也只是返回成功而已。代码第44行使用module_platform_driver这个宏来简化模块初始化函数和卸载函数的编写。
        编译和测试的命令如下

 


从上面的测试结果可以看到,平台驱动驱动了两个设备 pdev.0 和 pdev.1,这是设备名字加过构成的名字。


四、 电源管理


        在平台驱动里面实现了挂起和恢复两个电源管理函数,从而可以管理设备的电源状态。 /sys/devices/platform/pdev.0/power/control 和 /sys/devices/platform/pdev.1/power/control两个文件可以用来管理两个设备的电源控制方式,如果文件的内容为 auto,那么设备的电源会根据系统的状态自动进行管理,为on则表示打开。我们首先确定电源控制方式为自动,可以使用下面的命令进行确认。

貌似只跑一个linux的内核不行捏

没有挂起命令,只使用串口貌似也不能挂起,那这样的话就还是用ubuntu吧

 

 

接下来将 Ubuntu 系统挂起

喵的恢复不了了

 

试试用vm的挂起能不能起到效果

还是不行下面我修改一下驱动程序看看能不能在挂起后立刻自动恢复
 

 

 看看上面的选项是不是y并且确定他有唤醒方式

 

 

API和版本貌似对不上换一下试试

 

 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/platform_device.h>
#include <linux/pm.h>static int pdrv_suspend(struct device *dev)
{printk("pdev: suspend\n");// 在这里添加自动唤醒的代码pm_wakeup_event(dev, 0);printk("pdev: resume\n");return 0;
}static int pdrv_resume(struct device *dev)
{printk("pdev: resume\n");return 0;
}static const struct dev_pm_ops pdrv_pm_ops = {.suspend = pdrv_suspend,.resume  = pdrv_resume,
};static int pdrv_probe(struct platform_device *pdev)
{return 0;
}static int pdrv_remove(struct platform_device *pdev)
{return 0;
}struct platform_driver pdrv = {.driver = {.name    = "pdev",.owner   = THIS_MODULE,.pm      = &pdrv_pm_ops,},.probe   = pdrv_probe,.remove  = pdrv_remove,
};module_platform_driver(pdrv);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple platform driver");
MODULE_ALIAS("platform:pdev");

我又修改了一下代码

 这次没挂起直接关机了

使用命令后挂起成功了但是又是黑屏状态估计哪没配置好

服了,这里我环境不行就当成功验证了吧,后面我看看怎么修改一下环境或者直接在板子上跑这样很容易恢复。

系统挂起后,再重新恢复系统,使用 dmesg 命令可以看到,驱动中的 suspend 和 resume函数先后都被调用了两次。
# dmesg
[ 171.396323] pdev: suspend
[ 171.396325] pdev; suspend
......

176.699954] pdev:resume

[ 176.699959] pdev: resume


五、udev 和驱动的自动加载


        在上面的例子中,我们可以通过加载模块来向系统添加两个设备,也可以通过移除模块来删除这两个设备。对于这样的操作,我们想使设备被添加到系统后,其驱动能够自动被加载,这对于实际的可支持热插拔的硬件来说更有必要。比如,我们插入一个USB无线网卡,那么对应的驱动就应该自动加载,而不是由用户来手动加载。要做到这一点,就必须利用到一个工具-udev,在入式系统中通常使用 mdev,其功能比 udev 要弱很多,但也可以移植 udev 到嵌入式系统上。
        使用了 Linux 设备模型后,任何设备的添加、删除或状态修改都会导致内核向用户空间发送相应的事件,这个事件叫 uevent,和 kobiect 密切关联。这样用户空间就可以捕获这些事件来自动完成某些操作,如自动加载驱动、自动创建和删除设备节点、修改权限、创建软链接、修改网络设备的名字等。目前实现这个功能的工具就是 udev(或 mdev)这是一个用户空间的应用程序,捕获来自内核空间发来的事件,然后根据其规则文件进行操作。udev的规则文件为/etc/udev/rules.d 目录下后缀为.rules 的文件。
        udev 规则文件用#来注释,除此之外的就是一条一条的规则。每条规则至少包含一个键值对,键分为匹配和赋值两种类型。如果内核发来的事件匹配了规则中的所有匹配键的值,那么这条规则就可以得到应用,并且赋值键被赋予指定的值。一条规则包含了一个或多个键值对,这些键值对用逗号隔开,每个键由操作符规定一个操作,合法的操作符如下。
        ==和!=       :判等,用于匹配键。
        =、+=和:= : 赋值,用于赋值键,=和:=的区别是前者允许用新值来覆盖原来的值后者则不允许。+=则是追加赋值。
        常见的键如下。
        ACTION:事件动作的名字,如add 表示添加
        DEVPATH:事件设备的路径。
        KERNEL:事件设备的名字。
        NAME:节点或网络接口的名字
        SUBSYSTEM:事件设备子系统
        DRIVER:事件设备驱动的名字。
        ENV{key}:设备的属性。
        OWNER、GROUP、MODE:设备节点的权限。
        RUN:添加一个和设备相关的命令到一个命令列表中。

        IMPORT{type):导入一组设备属性的变量,依赖于类型 type。

        上面的键有的是匹配键,有的是赋值键,还有的既是匹配键又是赋值键。另外,还有很多其他的键,在此不一一罗列,详细信息请参见 udev 的 man 手册。


        

 

        值还可以使用?、*和来[]进行通配,这和正则表达式中的含义是一样的。接下来来看一个例子。

ACTION--"add", SUBSYSTEM=="scsi_device", RUN+="/sbin/modprobe sg"


        它表示当向 SCSI子系统添加任意设备后都要添加一个命令“/sbin/modprobe sg”到命令列表中,这个命令就是为相应的设备加载 sg 驱动模块。
        在 Ubuntu 中自动加载驱动的规则如下,请将这条规则添加到/etc/udev/rules.d/40-modprobe.rules 文件中,如果没有这个文件请新建一个。

ENV{MODALIAS}=="?*",RUN+="/sbin/modprobe $env(MODALIAS}"


        它表示根据模块的别名信息,用 modprobe 命令加载对应的内核模块。为此,我们要给平台驱动一个别名,如 pltdrv.c 文件中代码的第 49 行。pdev 要和驱动中用于匹配平台设备的名字保持一致。

49 MODULE_ALIAS("platform:pdev");


        添加了这一条规则后,加载 pltdev 模块就可以自动加载平台 pltdrv 驱动
#IsmodI grep plt

# modprobe pltdev

#lsmod I grep plt
pltdrvpltdev

但是我的ubuntu的modprobe不能用,并且开发板还不支持udev所以后面再说。


六、使用平台设备的LED 驱动


        前面我们说过,之前的驱动最大的问题就是没有把设备和驱动分离开,这使得驱动的通用性很差。只要硬件有任何改动(比如换一个管脚,增加或删除 LED 灯),都会导致驱动代码的修改。有了 Linux 设备模型以及平台总线后,我们可以把设备的信息用平台设备来实现,这就大大提高了驱动的通用性。接下来的任务就是把前面的 LED 驱动改造成基于平台总线的设备和驱动。首先是平台设备,代码如下
 

/*fsdev.c*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/platform_device.h>static void fsdev_release(struct device *dev)
{
}static struct resource led2_resources[] = {[0] = DEFINE_RES_MEM(0x11000C40, 4),
};static struct resource led3_resources[] = {[0] = DEFINE_RES_MEM(0x11000C20, 4),
};static struct resource led4_resources[] = {[0] = DEFINE_RES_MEM(0x114001E0, 4),
};static struct resource led5_resources[] = {[0] = DEFINE_RES_MEM(0x114001E0, 4),
};unsigned int led2pin = 7;
unsigned int led3pin = 0;
unsigned int led4pin = 4;
unsigned int led5pin = 5;struct platform_device fsled2 = {.name = "fsled",.id = 2,.num_resources = ARRAY_SIZE(led2_resources),.resource = led2_resources,.dev = {.release = fsdev_release,.platform_data = &led2pin,},
};struct platform_device fsled3 = {.name = "fsled",.id = 3,.num_resources = ARRAY_SIZE(led3_resources),.resource = led3_resources,.dev = {.release = fsdev_release,.platform_data = &led3pin,},
};struct platform_device fsled4 = {.name = "fsled",.id = 4,.num_resources = ARRAY_SIZE(led4_resources),.resource = led4_resources,.dev = {.release = fsdev_release,.platform_data = &led4pin,},
};struct platform_device fsled5 = {.name = "fsled",.id = 5,.num_resources = ARRAY_SIZE(led5_resources),.resource = led5_resources,.dev = {.release = fsdev_release,.platform_data = &led5pin,},
};static struct platform_device *fsled_devices[]  = {&fsled2,&fsled3,&fsled4,&fsled5,
};static int __init fsdev_init(void)
{return platform_add_devices(fsled_devices, ARRAY_SIZE(fsled_devices));
}static void __exit fsdev_exit(void)
{platform_device_unregister(&fsled5);platform_device_unregister(&fsled4);platform_device_unregister(&fsled3);platform_device_unregister(&fsled2);
}module_init(fsdev_init);
module_exit(fsdev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("register LED devices");

         由上可知,我们分别定义了 4 个平台设备,每一个平台设备代表一个LED 灯,之所以要这样做,是因为可以任意增加或删除一个 LED 灯。4 个平台设备都有一个IORESOURCE_MEM资源,用来描述2个寄存器所占用的内存空间;名字都为 fsled,用来和平台驱动匹配;id 分别为 2、3、4、5,用来区别不同的设备。还给每个平台设备的platform_data成员赋了值,platform_data 的类型是 void*,用来向驱动传递更多的信息,在这里传递的是每个LED 灯使用的管脚号,因为只有I/O内存是不能够控制一个具体的管脚的。这些平台设备放在 fsled_devices 数组中,在模块初始化函数中使用platform_add_devices 一次注册到平台总线上。在模块的清除函数中,则使用 platform_device_unregister 来注销。
        再来看看平台驱动。

fsled.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>#include "fsled.h"#define FSLED_MAJOR	256
#define FSLED_DEV_NAME	"fsled"struct fsled_dev {unsigned int __iomem *con;unsigned int __iomem *dat;unsigned int pin;atomic_t available;struct cdev cdev;
};static int fsled_open(struct inode *inode, struct file *filp)
{struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);filp->private_data = fsled;if (atomic_dec_and_test(&fsled->available))return 0;else {atomic_inc(&fsled->available);return -EBUSY;}
}static int fsled_release(struct inode *inode, struct file *filp)
{struct fsled_dev *fsled = filp->private_data;writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);atomic_inc(&fsled->available);return 0;
}static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct fsled_dev *fsled = filp->private_data;if (_IOC_TYPE(cmd) != FSLED_MAGIC)return -ENOTTY;switch (cmd) {case FSLED_ON:writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);break;case FSLED_OFF:writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);break;default:return -ENOTTY;}return 0;
}static struct file_operations fsled_ops = {.owner = THIS_MODULE,.open = fsled_open,.release = fsled_release,.unlocked_ioctl = fsled_ioctl,
};static int fsled_probe(struct platform_device *pdev)
{int ret;dev_t dev;struct fsled_dev *fsled;struct resource *res;unsigned int pin = *(unsigned int*)pdev->dev.platform_data;dev = MKDEV(FSLED_MAJOR, pdev->id);ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);if (ret)goto reg_err;fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);if (!fsled) {ret = -ENOMEM;goto mem_err;}cdev_init(&fsled->cdev, &fsled_ops);fsled->cdev.owner = THIS_MODULE;ret = cdev_add(&fsled->cdev, dev, 1);if (ret)goto add_err;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {ret = -ENOENT;goto res_err;}fsled->con = ioremap(res->start, resource_size(res));if (!fsled->con) {ret = -EBUSY;goto map_err;}fsled->dat = fsled->con + 1;fsled->pin = pin;atomic_set(&fsled->available, 1);writel((readl(fsled->con) & ~(0xF  << 4 * fsled->pin)) | (0x1  << 4 * fsled->pin), fsled->con);writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);platform_set_drvdata(pdev, fsled);return 0;map_err:
res_err:cdev_del(&fsled->cdev);
add_err:kfree(fsled);
mem_err:unregister_chrdev_region(dev, 1);
reg_err:return ret;
}static int fsled_remove(struct platform_device *pdev)
{dev_t dev;struct fsled_dev *fsled = platform_get_drvdata(pdev);dev = MKDEV(FSLED_MAJOR, pdev->id);iounmap(fsled->con);cdev_del(&fsled->cdev);kfree(fsled);unregister_chrdev_region(dev, 1);return 0;
}struct platform_driver fsled_drv = { .driver = { .name    = "fsled",.owner   = THIS_MODULE,},  .probe   = fsled_probe,.remove  = fsled_remove,
};module_platform_driver(fsled_drv);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");

fsled.h

#ifndef _FSLED_H
#define _FSLED_H#define FSLED_MAGIC	'f'#define FSLED_ON	_IO(FSLED_MAGIC, 0)
#define FSLED_OFF	_IO(FSLED_MAGIC, 1)#endif


        代码第 152 行至第 159 行定义了一个平台驱动 fsled_drv,名字叫 fsled,和平台设备匹配。代码第 161 行是平台驱动注册和注销的简化宏。
        在 fsled_probe 函数中,代码第 86 行首先通过 platform_data 取了管脚号。代码第88 行以平台设备中的 id 为次设备号。代码第 93 行动态分配了 struct fsled_dev 结构对象代码第 105行使用 platform_get_resource 获取了I/O内存的资源,这样要操作 GPIO管脚的两个信息就都获得了,一个是管脚号,一个是 I/O 内存地址。代码第 122 行使用platform set_drvdata 将动态分配得到的 fsled 保存到了平台设备中,便于之后的代码能从平台设备中获取 struct fsled_dev 结构对象的地址,是经常会使用到的一种技巧,也是个驱动支持多个设备的关键。
        函数 fsled_remove 中使用了 platform_get_drvdata 得到了对应的 struct fsled_dev 结构对象的地址,其他操作则是函数 fsled_probe 的反操作。
        函数fsled_open 也使用了 container_of宏得到了对应的struct fsled_dev 结构对象的地址,并保存在 filp->private_data 中,这也是我前面谈到的一个驱动支持多个设备的技巧。
        函数 fsled_ioctl 相比于以前则要简单一些,因为只控制一个对应的LED灯。
        测试的应用代码则是分别打开了 4个 LED 设备文件,然后再分别控制,代码比较简单,这里就不再赘述。测试方法和前面基本一致,只是要创建 4 个设备文件,用到 4个不同的次设备号 2、3、4、5。


七、自动创建设备节点


        前面谈到,内核中设备的添加、删除或修改都会向应用层发送热插拔事件,应用程序可以捕获这些事件来自动完成某些操作,如自动加载驱动、自动创建设备节点等。接下来以mdev为例,来说明如何自动创建设备节点。
        mdev 创建设备节点有两种方法,一种是运行 mdev -s 命令,一种是实时捕获热插拔事件。mdev-s 命令通常在根文件系统挂载完成后运行一次,它将递归扫描/sys/block 目录和/sys/class 目录下的文件,根据文件的内容来调用 make device 自动创建设备文件,这在busybox中的 mdev 源码中展现得非常清楚。

int mdev_main(int argc UNUSED_PARAM,char **argv)
{
......if (argv[1] && strcmp(argv[1],"-s") == 0) {/** Scan:mdev-s*/
......recursive_action("/sys/block",ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,fileAction,dirAction, temp,0);}recursive_action("/sys/class",ACTION_RECURSE | ACTION_FOLLOWLINKS,fileAction,dirAction,temp,0);
......


        另外一种情况则是当内核发生了热插拔事件后,mdev会自动被调用,这体现在根文件系统中的/etc/init.d/reS 初始化脚本文件中。

        echo /sbin/mdev > /proc/sys/kernel/hotplug


        内核有一种在发生热插拔事件后调用应用程序的方式,那就是执行/proc/sys/kernel/hotplug 文件中的程序,因为这种方式比较简单,所以常用在嵌入式系统之中。而之前说的udev 使用的则是 netlink 机制。发生热插拔事件时,调用 mdev 程序会将热插拔信息放在环境变量和参数当中,mdev 程序利用这些信息就可以自动创建设备节点,在 mdev 的源码中也有清晰的体现。

int mdev_main(int argc UNUSED_PARAM,char **argv)
{
......env_devname = getenv("DEVNAME");/* can be NULL */G.subsystem = getenv("SUBSYSTEM");action = getenv("ACTION");env_devpath = getenv("DEVPATH");
......op =index in strings(keywords,action);
......snprintf(temp,PATH_MAX,"/sys%s", env_devpath);if (op == OP_remove) {
......if (!fw)make_device(env_devname, temp,op);}else {make_device(env_devname, temp,op);if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {if (op== OP_add && fw)load_firmware(fw, temp);}}
......


        上面的代码的总体思路是根据 ACTION 键的值来决定 op 是增加还是移除操作,最终调用 make_device 来自动创建或删除设备节点。

        了解了应用层自动创建设备节点的方式后,接下来就需要讨论在驱动中如何实现了.既然自动设备节点的创建要依靠热插拔事件和 sysfs 文件系统,那这和我们之前讨论的kobjet 就是分不开的,mdev 扫描/sys/class 目录暗示我们要创建类,并且在类下面应该有具体的设备。为此,内核提供了相应的 API。

    class_create(owner,name)void class_destroy(struct class *cls);struct device *device_create(struct class *class, struct device *parent, dev_t
devt, void *drvdata, const char *fmt,...);void device_destroy(struct class *class, dev_t devt);


        class_create: 创建类,owner 是所属的模块对象指针,name 是类的名字,返回 struct
class 对象指针,返回值通过IS_ERR 宏来判断是否失败,通过 PTR_ERR 宏来获得错误码。
        class_destroy:销毁cls 类。
        device_create:在类class 下创建设备,parent 是父设备,没有则为 NULL。devt 是设备的主次设备号,drvdata 是驱动数据,没有则为 NULL。fmt 是格式化字符串,使用方法类似于printk。
        device_destroy:销毁 class 类下面主次设备号为 devt 的设备。返回值的检查方式同class_create。
        添加了自动创建设备的驱动的主要代码如下

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>#include "fsled.h"#define FSLED_MAJOR	256
#define FSLED_DEV_NAME	"fsled"struct fsled_dev {unsigned int __iomem *con;unsigned int __iomem *dat;unsigned int pin;atomic_t available;struct cdev cdev;struct device *dev;
};struct class *fsled_cls;static int fsled_open(struct inode *inode, struct file *filp)
{struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);filp->private_data = fsled;if (atomic_dec_and_test(&fsled->available))return 0;else {atomic_inc(&fsled->available);return -EBUSY;}
}static int fsled_release(struct inode *inode, struct file *filp)
{struct fsled_dev *fsled = filp->private_data;writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);atomic_inc(&fsled->available);return 0;
}static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct fsled_dev *fsled = filp->private_data;if (_IOC_TYPE(cmd) != FSLED_MAGIC)return -ENOTTY;switch (cmd) {case FSLED_ON:writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);break;case FSLED_OFF:writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);break;default:return -ENOTTY;}return 0;
}static struct file_operations fsled_ops = {.owner = THIS_MODULE,.open = fsled_open,.release = fsled_release,.unlocked_ioctl = fsled_ioctl,
};static int fsled_probe(struct platform_device *pdev)
{int ret;dev_t dev;struct fsled_dev *fsled;struct resource *res;unsigned int pin = *(unsigned int*)pdev->dev.platform_data;dev = MKDEV(FSLED_MAJOR, pdev->id);ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);if (ret)goto reg_err;fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);if (!fsled) {ret = -ENOMEM;goto mem_err;}cdev_init(&fsled->cdev, &fsled_ops);fsled->cdev.owner = THIS_MODULE;ret = cdev_add(&fsled->cdev, dev, 1);if (ret)goto add_err;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {ret = -ENOENT;goto res_err;}fsled->con = ioremap(res->start, resource_size(res));if (!fsled->con) {ret = -EBUSY;goto map_err;}fsled->dat = fsled->con + 1;fsled->pin = pin;atomic_set(&fsled->available, 1);writel((readl(fsled->con) & ~(0xF  << 4 * fsled->pin)) | (0x1  << 4 * fsled->pin), fsled->con);writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);platform_set_drvdata(pdev, fsled);fsled->dev = device_create(fsled_cls, NULL, dev, NULL, "led%d", pdev->id);if (IS_ERR(fsled->dev)) {ret = PTR_ERR(fsled->dev);goto dev_err;}return 0;dev_err:iounmap(fsled->con);
map_err:
res_err:cdev_del(&fsled->cdev);
add_err:kfree(fsled);
mem_err:unregister_chrdev_region(dev, 1);
reg_err:return ret;
}static int fsled_remove(struct platform_device *pdev)
{dev_t dev;struct fsled_dev *fsled = platform_get_drvdata(pdev);dev = MKDEV(FSLED_MAJOR, pdev->id);device_destroy(fsled_cls, dev);iounmap(fsled->con);cdev_del(&fsled->cdev);kfree(fsled);unregister_chrdev_region(dev, 1);return 0;
}struct platform_driver fsled_drv = { .driver = { .name    = "fsled",.owner   = THIS_MODULE,},  .probe   = fsled_probe,.remove  = fsled_remove,
};static int __init fsled_init(void)
{int ret;fsled_cls = class_create(THIS_MODULE, "fsled");if (IS_ERR(fsled_cls))return PTR_ERR(fsled_cls);ret = platform_driver_register(&fsled_drv);if (ret)class_destroy(fsled_cls);return ret;
}static void __exit fsled_exit(void)
{platform_driver_unregister(&fsled_drv);class_destroy(fsled_cls);
}module_init(fsled_init);
module_exit(fsled_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");

        代码第177行使用class_create 创建了名叫 fsled的类。代码第127行使用device_create在 fsled 类下面创建了 led%d 的设备,%d 用平台设备的id 来替代。在创建过程中,内核会发送热插拔事件给 mdev,mdev 利用这些信息就可以创建设备节点,因为设备的名字和设备号都传递给了 device_create,而内核又会利用这些参数生成热插拔信息。

        使用上面的驱动且驱动加载成功后,设备节点就自动被创建了,不需要再手动创建.整个测试过程和前面的例子类似,这里就不再重复了     

   

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>#include "fsled.h"int main(int argc, char *argv[])
{int fd[4];int ret;int num = 0;fd[0] = open("/dev/led2", O_RDWR);if (fd[0] == -1)goto fail;fd[1] = open("/dev/led3", O_RDWR);if (fd[1] == -1)goto fail;fd[2] = open("/dev/led4", O_RDWR);if (fd[2] == -1)goto fail;fd[3] = open("/dev/led5", O_RDWR);if (fd[3] == -1)goto fail;while (1) {ret = ioctl(fd[num], FSLED_ON);if (ret == -1)goto fail;usleep(500000);ret = ioctl(fd[num], FSLED_OFF);if (ret == -1)goto fail;usleep(500000);num = (num + 1) % 4;}
fail:perror("led test");exit(EXIT_FAILURE);
}

测试程序是个流水灯不多说了。

---------------------------------------------------------------------------------------------------------------------------------

        这周感觉更累了,已经没有太多的精力继续学习驱动了,但是周末我们软件部门聚餐时听着那些四五十的大佬说着人生经历讲着做人道理时我幡然醒悟。一时的强弱代表不了什么态度才是最重要的。只要你有终身学习的态度不会成为不了强者的,在公司做工作也是一种学习,即便有时做起来不是很愉快,但往往向着难受的方向学习,才能发展的更加全面。现在来公司已经三周了,我刚刚敢叫大家的名字,因为大家都是英文花名,我是农村的从小英语就不好,一直不敢张口叫大家的名字,只能尴尬的这个同学那个同学,后来发现没人在意你的,大概意思对就行了。

        以前在学校听说能画四层板的就能找到工作能画六层板就是大佬,偶然和硬件部的大佬交流了一下,喵的我们公司底板28层,我丢啊,玩个锤子。晚上吃饭的时候,听着大家都是各大名牌大学的研究生。其中不乏浙大这种顶尖学府的人才。顿时一个二本还没毕业的大三学生的我自卑感油然而生。突然萌生了考研的念头。可是真的很讨厌英语。如果以后有机会一定要试试另一种方式读研。去国外读研只需要一封介绍信。但是需要你是某个领域的顶尖人才。我们公司有个华三挖来的大佬。什么都会,整个FPGA都是他做的,这么多年问题不超过7个,或许大家绝对7个很多,但是真正工作时你就知道这个含金量了,我们软件的问题单貌似有70多个还是10多个人哦。重要的是他还精通python、通信、uboot。其它的我就不了解了,一个人可以全方位都牛,成为一个架构师我想这是每个专心搞技术的人的最高成就了吧。

        我们组有个很阳光的大哥,人巨好,我们老大太严厉了有时我都不敢问,但是这个邻家哥哥一样的同事每次问他东西都给我耐心解决。这周刚好赶上版本验证,第一次经历这种大规模代码发布新版本前的验证工作。挺有意思的,因为我是实习生可能活少吧,大半天就完事了,他们几个忙的都见不到人了,过了巨无聊的一天,第二天他们还在验证,我就自己去解决一些新的问题单。其实看着那些署了自己名字的代码进入公司的版本成为其中的一部分,还是很有成就感的,以后可以吹牛说你用的手机芯片是我写的程序验证出来的啦哈哈。我的老大之前有点不理解他,后来吃饭才知道他原来已经结婚了,怪不得赚那么多,还每天那么节俭,在领导面前被迫改变自己的性格。结婚后要学会的第一件事或许就是责任吧。

        我还发现了一种名叫领导力和人格魅力的东西。曾经在学校时我在一个大我两届的学长身上见到过。如今在这家公司我又见到了。一个长者,大概是公司的二号人物。真的很有人格魅力。有种即便做错了也想跟着他的感觉。

        吃完饭去唱歌时玩游戏输了,不得不喝了两口酒,我根本就不能喝酒,吃饭时领导敬酒我都是喝水的,有点不好意思哈哈。但是没办法输了就要接受惩罚。有个也是东北的姐姐问到我为什么会来杭州。我楞了一下,回了两个字,舔狗。是啊,北京一个月实习期能拿一万多的不去,深圳一个月5000供吃住,常州4500管住都没去,来了杭州一个月也就4000块还不管吃住。最后呢,人家还不是很愿意搭理你,不是舔狗又是什么呢。

        她并不是很惊艳的那种美丽,也没有很好的身材。但是就有一种奇特的魔力吸引我,或许是种别样的人格魅力吧。智慧型吸引?又或许是因为曾在我最需要帮助时帮了我。呼,累。但是说出来舒服了很多。喵的在CSDN写日常不会被和谐吧哈哈。后面技术更扎实了解决问题速度提高后就开始写写小说,现在已经看完两千多本了,感觉能看下去的很少了,所以以后自己写哈哈。就到这里咯,以后尽量保证每周更新一篇博客,学习些新东西,顺便记录一下一周的故事,就当是日记了。以后也有可以翻一翻的东西。

相关文章:

Linux——平台设备及其驱动

目录 前言 一、平台设备 二、平台驱动 三、平台驱动简单实例 四、 电源管理 五、udev 和驱动的自动加载 六、使用平台设备的LED 驱动 七、自动创建设备节点 前言 要满足 Linux 设备模型&#xff0c;就必须有总线、设备和驱动。但是有的设备并没有对应的物理总线&#x…...

【C语言技巧】三种多组输入的写法

文章目录 第一种&#xff1a;直接与1判断第二种&#xff1a;与EOF判断第三种&#xff1a;巧用按位取反符号“~”写在最后 在代码的实际运用中&#xff0c;我们经常会遇到需要多组输入的情况&#xff0c;那么今天博主就带大家一起盘点三种常见的多组输入的写法 第一种&#xff1…...

DB2数据库巡检脚本

DB2数据库巡检脚本的示例&#xff1a; #!/bin/bash# 设置DB2登录凭证 DB2_USER"your_username" DB2_PASSWORD"your_password"# 设置巡检结果输出文件路径 OUTPUT_FILE"/path/to/output.log"# 获取DB2版本信息 version_info$(db2 connect to you…...

Eureka 学习笔记3:EurekaHttpClient

版本 awsVersion ‘1.11.277’ EurekaTransport 用于客户端和服务端之间进行通信&#xff0c;封装了以下接口的实现&#xff1a; ClosableResolver 接口实现TransportClientFactory 接口实现EurekaHttpClient 接口实现及其对应的 EurekaHttpClientFactory 接口实现 private …...

Android Framework 之 启动流程

Android 系统的启动流程 Android 系统的启动流程可以分为以下几个主要步骤&#xff1a; 引导加载器&#xff08;Bootloader&#xff09;启动&#xff1a;当你打开一个 Android 设备时&#xff0c;首先启动的是引导加载器。引导加载器负责启动 Android 的核心操作系统。 Linux…...

Qt、C/C++环境中内嵌LUA脚本、实现LUA函数的调用执行

Qt、C/C环境中内嵌LUA脚本、实现LUA函数的调用执行 Chapter1. Qt、C/C环境中内嵌LUA脚本、实现LUA函数的调用执行1、LUA简介2、LUA脚本的解释器和编译器3、C环境中内嵌LUA执行LUA函数调用4、Qt内嵌LUA执行LUA函数调用5、运行结果6、内嵌LUA脚本在实际项目中的案例应用 Chapter1…...

超详细 | 模拟退火算法及其MATLAB实现

模拟退火算法(simulated annealing&#xff0c;SA)是20世纪80年代初期发展起来的一种求解大规模组合优化问题的随机性方法。它以优化问题的求解与物理系统退火过程的相似性为基础&#xff0c;利用Metropolis算法并适当地控制温度的下降过程实现模拟退火&#xff0c;从而达到求解…...

在线餐饮油烟实时监测系统的设计与实现

安科瑞 华楠 摘 要&#xff1a;为了解决传统油烟检测方法中成本高、效率低、实时性差等问题&#xff0c;设计开发了一种在线油烟实时监测系统&#xff1b;系统由采集、通讯、服务器和用户交互四个模块组成&#xff1b;采集模块采集油烟数据&#xff0c;通过GPRS通讯技术将数据发…...

7-2 凯撒密码 (20分)

7-2 凯撒密码 (20分) 为了防止信息被别人轻易窃取&#xff0c;需要把电码明文通过加密方式变换成为密文。输入一个以回车符为结束标志的字符串&#xff08;少于80个字符&#xff09;&#xff0c;再输入一个整数offset&#xff0c;用凯撒密码将其加密后输出。恺撒密码是一种简单…...

LeetCode_贪心算法_中等_763.划分字母区间

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍…...

【算法提高:动态规划】1.5 状态压缩DP TODO

文章目录 状态压缩DP例题列表棋盘式1064. 小国王⭐&#x1f402;&#xff08;好题&#xff01;&#xff09;做题套路总结 327. 玉米田&#xff08;好题&#xff01;&#x1f402; 和1064. 小国王差不多的题目&#xff09;292. 炮兵阵地&#xff08;和上面两道题差不多&#xff…...

建网站一般使用Windows还是liunx好?

建网站一般使用Windows还是liunx好&#xff1f; 1&#xff1b;服务器配置比较低时&#xff0c;最好使用linux系统。 对于一个电脑新手&#xff0c;刚开始做网站时&#xff0c;都会选择入门级的服务器&#xff0c;我刚开始做网站时&#xff0c;就是这样的。我购买了一台入门级服…...

NodeJs后端项目使用docker打包部署

docker安装看之前的文章 默认已经安装好docker并且配置没有问题 拉取项目 https://gitee.com/coder-msc/docker-node 本地跑一个看看 pnpm install pnpm start 本地访问 http://localhost:1301/getname?name%E5%93%88%E5%88%A9%E6%B3%A2%E7%89%B9项目整个上传服务器 查看…...

ARM单片机中断处理过程解析

前言 中断&#xff0c;在单片机开发中再常见不过了。当然对于中断的原理和执行流程都了然于胸&#xff0c;那么对于ARM单片机中断的具体处理行为&#xff0c;你真的搞清楚了吗&#xff1f; 今天来简单聊一聊&#xff0c;ARM单片机中断处理过程中的具体行为是什么样的&#xf…...

关于SEDEX会员与平台的相关问题汇总

【关于SEDEX会员与平台的相关问题汇总】 01.会员资格有效期是多久&#xff1f; Sedex会员资格有效期为12个月&#xff0c;您也可以选择更长期的会员资格。您支付会员年费时&#xff0c;在“订阅信息”框下的“延长订阅期限”中输入年数&#xff0c;即可获得更长的会员资格时效。…...

解读Spring-context的property-placeholder

在spring中&#xff0c;如果要给程序定义一些参数&#xff0c;可以放在application.properties中&#xff0c;通过<context:property-placeholder>加载这个属性文件&#xff0c;然后就可以通过value给我们的变量自动赋值&#xff0c;如果你们的程序可能运行在多个环境中&…...

【Rust】枚举类型创建单链表以及常见的链表操作方法

目录 单链表 用枚举表达链表 枚举enum Box容器 创建节点 1. 创建并打印 2. match 匹配 3. 节点初始化 4.节点嵌套 追加节点 1. 尾插法 2. 链表追加方法 3. 头插法 4. 改写成单链表方法 遍历链表 1. 递归法 2. 递推法 3. 改写成单链表方法 自定义Display tr…...

Excel 两列数据中相同的数据进行同行显示

一、要求 假设您有两个列&#xff0c;分别是A列和B列&#xff0c;需要在C列中找出A列对应的B列的值。 二、方案 方法1&#xff1a;寻常思路 凸显重复项对A列单独进行筛选–按颜色进行排序&#xff0c;然后升序对B列重复上述操作即可 方法2&#xff1a;两个公式 VLOOKUP 纵向查找…...

Windows本地安装配置Qcadoo MES系统

简介 Qcadoo MES是一款功能强大且灵活的开源MES&#xff08;制造执行系统&#xff09;&#xff0c;旨在为制造业务提供全面的管理和监控解决方案。本篇博客将教您如何在Windows操作系统上安装和配置Qcadoo MES系统&#xff0c;以便您能够轻松管理和监控制造过程。 环境要求 …...

涛思数据与拾贝云达成战略合作,携手赋能工业数字化转型

2023 年 7 月 27 日&#xff0c;北京涛思数据科技有限公司&#xff08;以下简称“涛思数据”&#xff09;与广州拾贝云科技有限公司&#xff08;以下简称“拾贝云”&#xff09;于广州签署战略合作协议。双方围绕电力行业的需求与痛点展开积极讨论&#xff0c;就如何量身打造最…...

nginx 配置多域名多站点 Ubuntu

nginx 配置多域名多站点 Ubuntu 一、安装 nginx apt install nginx二、配置文件说明 nginx 的配置文件在 /etc/nginx 目录下&#xff0c;它的默认内容是这样的 root2bd0:/etc/nginx# ll total 72 drwxr-xr-x 8 root root 4096 Jul 31 15:21 ./ drwxr-xr-x 104 root root …...

Docker实践:使用Docker搭建个人开发环境(极简版)

文章目录 说明教程1. 编写 Dockerfile2. 编写 docker-compose.yml3. 使用容器创建容器启动容器进入容器命令行VSCode 4. 关闭容器5. 备份容器导出导入 6. 重置容器 相关资料文章合集详细了解本文在个人电脑上安装 Docker容器使用 NVIDIA 显卡托管镜像运行GUI程序 说明 本文是在…...

SQL从三个表中根据时间分别查询并汇总数量一行展示

需求&#xff1a;如果您要从三个表中根据时间分别查询并汇总数量&#xff0c;然后将结果以时间和数量一行展示&#xff0c;可以使用子查询和条件聚合。 入库主表 入库明细表 出库主表 出库明细表 退货主表 退货明细表 SQL代码 SELECT time,sum(a.inQty) as inQty,sum(a.outQty…...

同样是跨端框架,React会不会被VUE取代?

看到知乎上有比较多的类似问题&#xff0c;正好这两个框架在以往的一些项目中都有实践过&#xff0c;就借着本篇文章说说我个人的看法。 先摆个结论&#xff1a;不会&#xff0c;毕竟各有千秋&#xff0c;除非跨端框架有被更好的概念所替代&#xff0c;又或者App已经彻底过气了…...

Excel·VBA定量装箱、凑数值金额、组合求和问题

如图&#xff1a;对图中A-C列数据&#xff0c;根据C列数量按照一定的取值范围&#xff0c;组成一个分组装箱&#xff0c;要求如下&#xff1a; 1&#xff0c;每箱数量最好凑足50&#xff0c;否则为47-56之间&#xff1b; 2&#xff0c;图中每行数据不得拆分&#xff1b; 3&…...

通过Jmeter压测存储过程

目录 一、存储过程准备&#xff1a; 二、测试工具准备&#xff1a; 三、工具配置及执行&#xff1a; 1、配置JDBC Connection Configuration&#xff1a; 2、配置吞吐量控制器&#xff08;可跳过&#xff09;&#xff1a; 3、配置JDBC Request&#xff1a; 对于存储过程…...

Spring笔记之Spring对IoC的实现

文章目录 IoC控制反转依赖注入set注入注入外部Bean注入内部Bean注入简单类型通过注入方式实现javax.sql.DateSource接口测试简单类型 级联属性赋值&#xff08;了解&#xff09;注入数组注入List集合注入Set集合注入Map集合注入Properties注入null和空字符串不给属性赋值使用 注…...

【eNSP】Telnet远程登录

Telnet远程登录 eNSP软件TelnetTelnet远程登录-路由连接关闭防火墙eNSP根据图1画图路线配置路由端口IP配置路由R1改名配置接口IP 配置路由R2 配置R2的远程登录设置登录用户授权级别退出登录超时时间 Telnet测试 eNSP软件 eNSP(Enterprise Network Simulation Platform)是一款由…...

SOP/详解*和**/python数据结构(iter,list,tuple,dict)/ 解包

一、错误解决合集 1. > combined_seq.named_children() 2. isinstance 2th parameter : must be a type or tuple of types > 改为tuple&#xff0c;不要用列表。改为 LLLayer (nn.Conv2d,nn.Linear) 3. File “test.py”, line 90, in calculate_fin_fout print(“hi”…...

使用webdriver-manager解决浏览器与驱动不匹配所带来自动化无法执行的问题

1、前言 在我们使用 Selenium 进行 UI 自动化测试时&#xff0c;常常会因为浏览器驱动与浏览器版本不匹配&#xff0c;而导致自动化测试无法执行&#xff0c;需要手动去下载对应的驱动版本&#xff0c;并替换原有的驱动&#xff0c;可能还会遇到跨操作系统进行测试的时候&…...