嵌入式Linux驱动开发之platform

关键词:rk3399 嵌入式驱动 Linux platform

前言

前面的嵌入式Linux驱动都是描述从特定的SOC与特定设备之间的直接两两通信。而Linux不是为单一某一SOC结构而设计的操作系统,它可以运行在X86、ARM等多种架构多种SOC平台上,如果驱动程序按照SOC与设备之间直接通信的方式实现的话,其效果如下所示:

A SOC平台
A设备
B SOC平台
B设备
C SOC平台
C设备

从Linux源码的角度来看,源码会变得极其臃肿并且充斥的大量的重复代码,比如A SOC平台和C SOC平台都需要各自与一个B设备通信,即A SOC连接了一个B设备,C SOC也连接了一个B设备,Linux系统实现直接两两通信,Linux源码中就会出现重复的对B设备的驱动代码
举个例子,多个人到超市去采购商品,每个人采购多个品类的商品,每个品类的商品都有一个售货员,如果每个购买者按照自己的采购清单直接跟售货员沟通,那么就会出现每个购买者跟多个售货员沟通,每个售货员跟多个购买者沟通的现象,场面就比较混乱,对应到Linux系统源码而言就显得特别无序,售货员多次往返取货会浪费时间,对应到Linux系统源码而言就是代码的重复
为了避免上述提到的问题,提高Linux设备驱动的跨平台能力,Linux系统采取了驱动分离与分层的策略,如下为Linxu驱动分离后的驱动框架示意:

A SOC平台的主机驱动
统一接口
B SOC平台的主机驱动
C SOC平台的主机驱动
A设备驱动
B设备驱动
C设备驱动

其中:
主机驱动:linux系统内核能够利用SOC的接口驱动,例如,Linux系统内核对RK3399的I2C接口的驱动,就是实现让RK3399的引脚按照I2C功能工作
设备驱动:连接到SOC接口的设备的驱动,例如,RK3399的I2C接口上连接了一个MPU6050,设备驱动就是MPU6050自己的驱动
统一接口的作用如同在上面的例中,在超市门口建立一个采购平台,购买者将清单给平台,平台给售货员发送提货单,这样超市就看起来井井有条,某个售货员也可以一次将多个购买者的同类商品取出,不必往返多次,避免重复

这就是Linxu系统中的总线(Bus)、驱动(driver)和设备(device)模型。如下:

主机驱动1
总线
主机驱动2
主机驱动3
设备
设备
设备

总线负责传递设备信息等。当向系统注册一个驱动时,总线会在设备中查找,如果有匹配的设备,便将两者联系起来;同样,向系统注册一个设备的时候,总线会在驱动中查找,如果有匹配的驱动,便将两者联系起来

以上是为什么采用驱动分离的策略

platform

Linux驱动分离使用了总线(Bus)、驱动(driver)和设备(device)模型,对于SOC的某些外设,是没有总线一说的,因此Linux提出了platform这种虚拟总线,相应的就有驱动platform_driver和设备platform_device

platform总线

Linux系统内使用bus_type结构体表示总线,其定义在Linxu源代码的/include/linux/device.h中,platform总线是bus_type的一个实例,定义在Linux源代码的/drivers/base/platform.c中,如下

struct bus_tpye platform_bus_type = {
	.name       = "platform",
	.dev_groups = platform_dev_groups,
	.match      = platform_match,
	.uevent     = platform_uevent,
	.pm         = &platform_dev_pm_ops,
};

其中 platform_match就是匹配函数,总线就是使用匹配函数类根据注册的设备来查找驱动的,或根据注册的驱动来查找设备,paltform_match()函数定义在文件/drivers/base/platform.c中,如下:

static int platform_match(struct device *dev,
							struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/*When driver_override is set,only bind to the matching driver*/
	if (pdev -> driver_override)
		return !strcmp(pdev -> driver_override,drv -> name);

	/* Attemp an OF style match first */
	if(of_driver_match_device(dev,drv))
		return 1;

	/*Then try ACPI style match */
	if(acpi_driver_match_device(dev,drv))
		return 1;

	/* Then try to match against the id table */
	if(pdrv -> id_table)
		return platform_match_id(pdrv -> id_table,pdev) != NULL;

	/* fall-back to driver name match */
	return(strcmp(pdev -> name,drv -> name) == 0);
}

可以看出,函数内提供了4中匹配方法:

  • OF函数匹配,即设备树匹配方式,函数of_deiver_match_device()定义在Linux源代码文件/include/linux/of_device.h中,device_driver结构体中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的compatibal属性会和of_match_table表中的所有成员比较,如果有则表示设备会和此驱动匹配
  • ACPI匹配
  • id_table匹配,每个platform_driver结构体有一个id_table成员变量,其中保存了很多id信息,这些id信息包含paltform_driver所支持的驱动类型
  • name字段匹配
    对于支持设备树的Linux版本,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式,即OF函数匹配都会存在,id_table匹配和name字段匹配只存在一种就可以了,比较nama字段的匹配比较简单,用的多

paltform总线,即platform_bus_type的主要作用就是匹配驱动和设备

platform驱动platform_driver

platform驱动,由platform_driver结构体表示,此结构体定义在Linux源代码的/include/linux/platform_device.h中,内容如下:

struct platform_driver {
	int (*probe)(struct platform_device *);

	/*
	 * Traditionally the remove callback returned an int which however is
	 * ignored by the driver core. This led to wrong expectations by driver
	 * authors who thought returning an error code was a valid error
	 * handling strategy. To convert to a callback returning void, new
	 * drivers should implement .remove_new() until the conversion it done
	 * that eventually makes .remove() return void.
	 */
	int (*remove)(struct platform_device *);
	void (*remove_new)(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;
	/*
	 * For most device drivers, no need to care about this flag as long as
	 * all DMAs are handled through the kernel DMA API. For some special
	 * ones, for example VFIO drivers, they know how to manage the DMA
	 * themselves and set this flag so that the IOMMU layer will allow them
	 * to setup and manage their own I/O address space.
	 */
	bool driver_managed_dma;
};

其中

  • probe函数当设备与驱动匹配成功后会执行
  • id_table就是platform总线中匹配驱动和设备的一个匹配方式,id_table是一个数组,每个元素的类型为platform_device_id,其结构体如下
struct platform_device_id{
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};
  • driver 是device_driver结构体变量,该结构体定义在Linux源代码的/include/linux/device.h中,如下:
struct device_driver {
    const char		    *name;
    struct bus_type		*bus;

    struct module		*owner;
    const char		*mod_name;	/* used for built-in modules */

    bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
    enum probe_type probe_type;

    const struct of_device_id	*of_match_table;
    const struct acpi_device_id	*acpi_match_table;

    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;

    const struct dev_pm_ops *pm;

    struct driver_private *p;
};

其中of_match_table是采用设备树式paltform驱动使用的匹配表,是of_device_id类型,of_device_id类型结构体数据定义在Linux源代码的/include/linux/mod_devicetable.h中,如下:

struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

其中compatible就是设备树中节点的compatible属性,of_match_table中的compatible属性和设备树节点的compatible相同则代表驱动和设备匹配成功

在编写platform驱动的时候,首先定义一个paltform_device结构体变量,然后实现其成员变量,重点是实现匹配方法及probe()函数,具体的驱动程序在probe()函数中编写
定义并初始化好platform_driver后,需要在驱动入口函数(参考[[01嵌入式Linux驱动开发之点亮LED(非设备树)]]的内核模块部分)中调用platform_driver_register()函数,向Linux内核注册一个platform驱动,相应的,注销platform驱动可以用platform_driver_unregister()

见下面测试代码查看platform_driver的应用

platform设备platform_device

准备好platform驱动后,还需要准备pltform设备,由platform_device结构体表示platform设备,结构体定义在Linux源码的/include/linux/platform_device.h中,如下

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u64		platform_dma_mask;
	struct device_dma_parameters dma_parms;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	/*
	 * Driver name to force a match.  Do not set directly, because core
	 * frees it.  Use driver_set_override() to set or clear it.
	 */
	const char *driver_override;

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

如果Linux内核版本支持设备树,就不需要再用platform_device来描述设备了,改用设备树去描述本文这里不再梳理platform_device,见下面测试代码了解设备树描述platform设备

设备树下platform驱动实验

platform驱动分为总线、驱动和设备,总线是由linux内核提供的,在支持设备树的Linux内核版本下,可以在设备树中描述设备,因此驱动的实现,除在设备树中添加设备外,只需要实现platform_driver即可

设备树描述设备

与前面相同,控制Firefly-RK3399开发板上电LED,引脚GPIO0B_5输出高电平点亮LED,低电平熄灭LED。在设备树中添加设备节点的操作与嵌入式Linux驱动开发之从设备树到点亮LED中相同,这里不再重复。

platform_driver实现

新建.c文件,输入以下内容(本文这里命名为platform_leddriver.c):

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>

/*************************************
 *FileName:platform_leddriver.c
 *Fuc:     RK3399,GPIO0B_5 driver
 *
 *Author:  PineLiu
 ************************************/

#define CHAR_CNT     1          //设备数量
#define LED_NAME     "gumpplatled"  //设备名称

#define LEDON        1      //点亮
#define LEDOFF       0      //熄灭

//重映射后的寄存器虚拟地址
static void __iomem *GPIO0_DR;    //数据寄存器
static void __iomem *GPIO0_DDR;   //方向寄存器
static void __iomem *GPIO0_IOMUX; //复用寄存器
static void __iomem *GPIO0_CRU;   //时钟寄存器


//声明设备结构体(该设备包含的属性)
struct platform_leddev{
    dev_t                devid;     //设备号
    struct cdev          cdev;      //字符设备
    struct class        *class;     //设备节点类 主动创建设备节点文件用
    struct device       *device;    //设备
    int                  major;     //主设备号
    int			 minor;	    //次设备号
    struct device_node  *nd;        //设备节点
};


//声明一个外部设备,本文这里是字符设备
struct platform_leddev platformled;


//LED打开/关闭
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON){
        val = readl(GPIO0_DR);
        val |= (1<<13);
        writel(val,GPIO0_DR);
    }else if(sta == LEDOFF){
        val = readl(GPIO0_DR);
        val &= ~(1<<13);
        writel(val, GPIO0_DR);
    }
}


//===========================================================================
//以下实现设备的具体操作函数:open函数、read函数、write函数和release函数
//===========================================================================

//打开设备
static int led_open(struct inode *inode, struct file *filp)
{
    filp -> private_data = &platformled;    //设置私有数据
    return 0;
}

//从设备读取数据
static ssize_t led_read(struct file *filp,char __user *buf,
            size_t cnt,loff_t *offt)
{
    return 0;
}

//向设备写数据
//filp:设备文件,表示打开的文件描述
//buf :保存着要向设备写入的数据
//cnt :要写入的数据长度
//offt:相对于文件首地址的偏移
//
static ssize_t led_write(struct file *filp,const char __user *buf,
            size_t cnt,loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf,buf,cnt);
    if(retvalue < 0){
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];   //获取状态值
    
    if(ledstat == LEDON){
        led_switch(LEDON);
    }else if(ledstat == LEDOFF){
        led_switch(LEDOFF);
    }

    return 0;
    
}

//关闭,释放设备
//filp: 要关闭的设备文件描述
//
static int led_release(struct inode *inode,struct file *filp)
{
    return 0;                                                                                                                                                
}


//==========================================================================
//以下实现的设备具体函数与内核的对应函数的映射
//==========================================================================
//映射设备操作函数
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .read  = led_read,
    .write = led_write,
    .release = led_release,
};


//==========================================================================
//内核模块相关
//==========================================================================

//probe函数
static int led_probe(struct platform_device *dev)
{
    
    u32 val = 0;
    int ret;
    u32 regdata[14];
    const char *str;
    struct property *proper;

    printk("led driver and device was matched!\r\n");

    //==============获取节点属性
    //获取节点
    platformled.nd = of_find_node_by_path("/gumpleddts");
    if(platformled.nd == NULL){
        printk("gumpleddts node can not found!\r\n");
        return -EINVAL;
    }else{
        printk("gumpleddts node has been found!\r\n");
    }

    //获取compatible属性
    proper = of_find_property(platformled.nd,"compatible",NULL);
    if(proper == NULL){
        printk("compatible property can not found!\r\n");
    }else{
        printk("compatibel = %s\r\n",(char *)proper -> value);
    }

    //获取status属性
    ret = of_property_read_string(platformled.nd,"status",&str);
    if(ret < 0){
        printk("status read failed!\r\n");
    }else{
        printk("status = %s\r\n",str);
    }

    //获取reg属性
    ret = of_property_read_u32_array(platformled.nd,"reg",regdata,8);
    if(ret < 0){
        printk("reg property read failed!\r\n");
    }else{
        u8 i = 0;
        printk("reg data:\r\n");
        for(i = 0;i<8;i++)
            printk("%#X",regdata[i]);
        printk("\r\n");
    }
    //===============初始化连接LED的引脚
    //寄存器地址映射
    #if 1
    GPIO0_CRU   = ioremap(regdata[0],regdata[1]);
    GPIO0_IOMUX = ioremap(regdata[2],regdata[3]);
    GPIO0_DDR   = ioremap(regdata[4],regdata[5]);
    GPIO0_DR    = ioremap(regdata[6],regdata[7]);
    #else
    GPIO0_CRU   = of_iomap(platformled.nd,0);
    GPIO0_IOMUX = of_iomap(platformled.nd,1);
    GPIO0_DDR   = of_iomap(platformled.nd,2);
    GPIO0_DR    = of_iomap(platformled.nd,3);
    #endif

    //使能时钟
//  GPIO0_CRU |= (1 << (3 + 16));
//  GPIO0_CRU &= ~(1 << 3);
    val = readl(GPIO0_CRU);
    val |= (1 << (3+16));
    val &= ~(1 << 3);
    writel(val,GPIO0_CRU);

    //设置GPIO0B_5引脚复用功能为GPIO
//  GPIO0_IOMUX |= (3 << (10+16));
//  GPIO0_IOMUX &= ~(3 << 10);
    val = readl(GPIO0_IOMUX);
    val |= (3 << (10+16));
    val &= ~(3 << 10);
    writel(val,GPIO0_IOMUX);

    //配置GPIO0B_5引脚为输出模式
//  GPIO0_DDR |= (1 << 13);
    val = readl(GPIO0_DDR);
    val |= (1 << 13);
    writel(val,GPIO0_DDR);
    
    //配置GPIO0B_5引脚默认关闭LED
//  GPIO0_DR &= ~(1 << 13);
    val = readl(GPIO0_DR);
    val &= ~(1 << 13);
    writel(val,GPIO0_DR);


    //===========注册设备   
    //创建设备号
    if(platformled.major){ //定义了设备号
        platformled.devid = MKDEV(platformled.major,0);
        register_chrdev_region(platformled.devid,CHAR_CNT,LED_NAME);
    }else{ //没有定义设备号,要向系统申请设备号
        alloc_chrdev_region(&(platformled.devid),0,CHAR_CNT,LED_NAME);
        platformled.major = MAJOR(platformled.devid);
        platformled.minor = MINOR(platformled.devid);
    }
    printk("platformled major = %d,minor = %d\r\n",platformled.major,platformled.minor);

    //初始化cdev
    platformled.cdev.owner = THIS_MODULE;
    cdev_init(&platformled.cdev,&led_fops);
    //向系统注册设备
    cdev_add(&platformled.cdev,platformled.devid,CHAR_CNT);
    //创建类
    platformled.class = class_create(THIS_MODULE,LED_NAME);
    if(IS_ERR(platformled.class)){
        return PTR_ERR(platformled.class);
    }
    //创建设备节点
    platformled.device = device_create(platformled.class,NULL,platformled.devid,NULL,LED_NAME);
    if(IS_ERR(platformled.device)){
        return PTR_ERR(platformled.device);
    }

    return 0;

}


//remove函数
static int led_remove(struct platform_device *dev)
{
    //取消映射
    iounmap(GPIO0_CRU);
    iounmap(GPIO0_IOMUX);
    iounmap(GPIO0_DDR);
    iounmap(GPIO0_DR);

    //注销设备
    cdev_del(&platformled.cdev);
    unregister_chrdev_region(platformled.devid,CHAR_CNT);

    device_destroy(platformled.class,platformled.devid);
    class_destroy(platformled.class);

    return 0;
}


//匹配表
static const struct of_device_id led_of_match[] = {
    {.compatible = "gump-led"},		//与设备树内该设备节点的compatibel相同
    {/*Sentinel*/}				
};

//platform_driver
static struct platform_driver led_driver = {
    .driver = {
        .name = "gumpleddts",   //设备树中节点名称
	.of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};


//驱动模块加载函数
static int __init led_init(void)
{
    return platform_driver_register(&led_driver);
}

//驱动模块卸载函数
static void __exit led_exit(void)
{
    platform_driver_unregister(&led_driver);
}


module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gump");


代码内实现了probe函数,在probe函数内实现了设备号分配,寄存器配置等,使用platform_driver_register实现驱动的注册,platform_driver_unregister实现驱动的注销
注:代码内所说的设备,是SOC内相对于cpu而言的片上外设,并不是platform_device,platform_device是指连接到这个片上外设的设备

将上面代码放到ubuntu18.04内编译为ko文件,Makefile文件内容为:

KERNELDIR:=/home/gump/rk3399/kernel-develop-4.4
CURRENT_PATH:=$(shell pwd)
obj-m:=platform_leddriver.o

build: kernel_modules

kernel_modules:
                $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
                $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译命令:(可根据自己的交叉编译工具更改)

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-`

得到ko文件放到嵌入式Linux系统内,并安装内核模块:

sudo insmod -f platform_leddriver.ko

安装后在/dev目录下会出现该设备文件夹,如图
在这里插入图片描述

测试

测试程序platformTest.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/*************************************************
 *FileName:platformTest.c
 *Func:    GPIO driver module test
 *
 *Author:  pineliu
 * **********************************************/

#define LEDOFF  0
#define LEDON   1

int main(int argc,char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3){
        printf("Error Usage!\r\n");
        return -1;
    }
    
    filename = argv[1];
    
    fd = open(filename,O_RDWR);
    
    if (fd < 0){
        printf("file %s open failed!\r\n",argv[1]);
        return -1;
    }
    databuf[0] = atol(argv[2]);

    retvalue = write(fd,databuf,sizeof(databuf));
    if(retvalue < 0){
        printf("led Control Error!\r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd);
    if(retvalue < 0){
        printf("file %s close failed!\r\n",argv[1]);
        return -1;
    }
    
    return 0;
}

将测试程序进行编译:

aarch64-linux-gnu-gcc platformTest.c -o platformTest

得到可执行文件platformTest
放到嵌入式Linux系统内,并赋予可执行权限

chmod +x platformTest

测试,点亮led:

sudo ./platformTest /dev/gumpplatled 1

实验证明可行

总结

为了提高Linux系统跨平台能力的同时,简化源代码,实现驱动代码复用,因此采用了驱动分离的策略,使用驱动、总线、设备模型。
platform驱动分为总线、驱动和设备,总线是由linux内核提供的,在支持设备树的Linux内核版本下,可以在设备树中描述设备,因此驱动的实现,除在设备树中添加设备外,只需要实现platform_driver即可
相比于基础的设备树驱动,在驱动代码方面,platform设备驱动代码更加的结构化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/956678.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

KubeSphere部署安装,接入KubeKey安装的k8s集群

KubeSphere安装接入KubeKey安装的k8s集群 文章目录 KubeSphere安装接入KubeKey安装的k8s集群 一.NFS安装配置1.服务器安装NFS服务2.下载并部署 NFS Subdir External Provisioner1).下载部署文件2).创建 NameSpace3).创建 RBAC 资源4).配置 deployment.yaml5).部署 Storage Clas…

从密码学原理与应用新方向到移动身份认证与实践

相关学习资料放下面啦&#xff01; 记得关注❤️&#xff5e;后续分享更多资料 通过百度网盘分享的文件&#xff1a;从密码学原理与应... 链接https://pan.baidu.com/s/1mHpHkvPuf8DUwReQkoYQlw?pwdGza7 提取码&#xff1a;Gza7 复制这段内容打开「百度网盘APP 即可获取」 记…

Java 特殊文件、 properties文件、xml文件

一. 属性文件.properties 1. #注释 2. 内容都是一些键值对信息&#xff0c;每行都是一个键值对&#xff1b;键不能重复&#xff1b; 3. 属性文件的后缀一般都是properties结尾 4. 使用程序读取properties属性文件里面的数据 (1) Properties&#xff1a;是一个Map集合(键值对集合…

抽象设计如何提升用户体验?

抽象设计在网页设计中可以通过多种方式提升用户体验&#xff0c;以下是具体的应用和作用&#xff1a; 一、增强视觉吸引力 视觉冲击力&#xff1a;抽象元素往往具有强烈的视觉冲击力&#xff0c;能够迅速吸引用户的注意力。通过大胆的色彩、不寻常的形状和丰富的纹理&#xff…

MATLAB中while循环例子,for循环嵌套例子

while循环例子 for循环解决斐波那契数列 for循环嵌套例子 注意最后都有 e n d end end

行人识别检测数据集,yolo格式,PASICAL VOC XML,COCO JSON,darknet等格式的标注都支持,准确识别率可达99.5%

作者简介&#xff1a; 高科&#xff0c;先后在 IBM PlatformComputing从事网格计算&#xff0c;淘米网&#xff0c;网易从事游戏服务器开发&#xff0c;拥有丰富的C&#xff0c;go等语言开发经验&#xff0c;mysql&#xff0c;mongo&#xff0c;redis等数据库&#xff0c;设计模…

【vitePress】基于github快速添加评论功能(giscus)

一.添加评论插件 使用giscus来做vitepress 的评论模块&#xff0c;使用也非常的简单&#xff0c;具体可以参考&#xff1a;giscus 文档&#xff0c;首先安装giscus npm i giscus/vue 二.giscus操作 打开giscus 文档&#xff0c;如下图所示&#xff0c;填入你的 github 用户…

JAVA使用自定义注解,在项目中实现EXCEL文件的导出

首先定义一个注解 Retention(RetentionPolicy.RUNTIME) Target(ElementType.FIELD) public interface Excel {/*** 导出时在excel中排序*/int sort() default Integer.MAX_VALUE;/*** 导出到Excel中的名字.*/String name() default "";/*** 首行字段的批注*/String …

有限元分析学习——Anasys Workbanch第一阶段笔记(14)静定与超静定问题、约束类型介绍、简支梁挠度求解和自定义材料库建立

目录 0 序言 1 静定与超静定问题 2 Workbranch中Supports介绍 3 简支梁挠度的有限元求解 4 自定义材料库建立 0 序言 静定与超静定问题、约束类型介绍、简支梁挠度求解和自定义材料库建立(内容对应视频22到24课)。 1 静定与超静定问题 在有限元分析中&#xff0c;不同的…

领域算法 - 大数据处理

大数据处理 文章目录 大数据处理一&#xff1a;hash分流二&#xff1a;双层桶1&#xff1a;什么是双层桶2&#xff1a;双层桶案例 三&#xff1a;外排序1&#xff1a;经典问题2&#xff1a;位图排序法3&#xff1a;多路归并排序 四&#xff1a;bitMap1&#xff1a;添加 -> 异…

以太网实战AD采集上传上位机——FPGA学习笔记27

一、设计目标 使用FPGA实现AD模块驱动采集模拟电压&#xff0c;通过以太网上传到电脑上位机。 二、框架设计 数据位宽转换模块&#xff08;ad_10bit_to_16bit&#xff09;&#xff1a;为了方便数据传输&#xff0c;数据位宽转换模块实现了将十位的 AD 数据转换成十六位&#…

JavaWeb 快速入门 javaScript(预测爆文) | 019

今日推荐语 人经常推翻自己&#xff0c;经常不同意昨天的自己&#xff0c;这也是常态。——纪静蓉 日期 学习内容 打卡编号2025年01月20日JavaWeb快速入门javaScript019 前言 哈喽&#xff0c;我是菜鸟阿康。 今天大概学习了下 js 的的基础知识&#xff0c;js …

[c语言日寄]内存初阶:大端字节序和小端字节序

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

【MySQL】数据库-图书管理系统(CC++实现)

一.预期功能 该图书管理系统设计提供基本的设计模版&#xff0c;涉及数据库的增删查改等操作&#xff0c;包含登录功能&#xff0c;图书管理功能&#xff0c;图书借阅功能&#xff0c;用户管理功能等基础功能&#xff0c;详细功能查看以下菜单表&#xff0c;共包含三个菜单&am…

Linux-C/C++--深入探究文件 I/O (下)(文件共享、原子操作与竞争冒险、系统调用、截断文件)

经过上一章内容的学习&#xff0c;了解了 Linux 下空洞文件的概念&#xff1b;open 函数的 O_APPEND 和 O_TRUNC 标志&#xff1b;多次打开同一文件&#xff1b;复制文件描述符&#xff1b;等内容 本章将会接着探究文件IO&#xff0c;讨论如下主题内容。  文件共享介绍&…

RabbitMQ-消息可靠性以及延迟消息

目录 消息丢失 一、发送者的可靠性 1.1 生产者重试机制 1.2 生产者确认机制 1.3 实现生产者确认 &#xff08;1&#xff09;开启生产者确认 &#xff08;2&#xff09;定义ReturnCallback &#xff08;3&#xff09;定义ConfirmCallback 二、MQ的持久化 2.1 数据持久…

springboot基于前后端分离的摄影知识网站

Spring Boot 基于前后端分离的摄影知识网站 一、项目概述 Spring Boot 基于前后端分离的摄影知识网站&#xff0c;是一个专为摄影爱好者、专业摄影师打造的知识共享与交流平台。借助 Spring Boot 强大的后端架构搭建能力&#xff0c;结合前端独立开发的灵活性&#xff0c;整合…

B站评论系统的多级存储架构

以下文章来源于哔哩哔哩技术 &#xff0c;作者业务 哔哩哔哩技术. 提供B站相关技术的介绍和讲解 1. 背景 评论是 B站生态的重要组成部分&#xff0c;涵盖了 UP 主与用户的互动、平台内容的推荐与优化、社区文化建设以及用户情感满足。B站的评论区不仅是用户互动的核心场所&…

Linux Bash 中使用重定向运算符的 5 种方法

注&#xff1a;机翻&#xff0c;未校。 Five ways to use redirect operators in Bash Posted: January 22, 2021 | by Damon Garn Redirect operators are a basic but essential part of working at the Bash command line. See how to safely redirect input and output t…

什么是三高架构?

大家好&#xff0c;我是锋哥。今天分享关于【什么是三高架构?】面试题。希望对大家有帮助&#xff1b; 什么是三高架构? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 “三高架构”通常是指高可用性&#xff08;High Availability&#xff09;、高性能&#xff…