Linux 内核:设备驱动模型(6)设备资源管理

背景

不要总是用Linux 2.6的风格来写驱动代码了,也该与时俱进一下。

参考:http://www.wowotech.net/device_model/device_resource_management.html

前言

每当driver probe一个具体的device实例的时候,都需要建立一些私有的数据结构来保存该device的一些具体的硬件信息。

在过去,驱动工程师多半使用kmalloc或者kzalloc来分配内存,但这会带来一些潜在的问题。例如:在初始化过程中,有各种各样可能的失败情况,这时候就依靠driver工程师小心的撰写代码,释放之前分配的内存。

当然,初始化过程中,除了memory,driver会为probe的device分配各种资源,例如IRQ 号,io memory map、DMA等等。当初始化需要管理这么多的资源分配和释放的时候,很多驱动程序都出现了资源管理的issue。

而且,由于这些issue是异常路径上的issue,不是那么容易测试出来,更加重了解决这个issue的必要性。

内核解决这个问题的模式(所谓解决一类问题的设计方法就叫做设计模式)是Devres,即device resource management模块。

先看一个例子

// drivers/media/platform/soc_camera/mx1_camera.c
static int __init mx1_camera_probe(struct platform_device *pdev)
{
// ... res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!res || (int)irq <= 0) {
err = -ENODEV;
goto exit;
} clk = clk_get(&pdev->dev, "csi_clk");
if (IS_ERR(clk)) {
err = PTR_ERR(clk);
goto exit;
} pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
if (!pcdev) {
dev_err(&pdev->dev, "Could not allocate pcdev\n");
err = -ENOMEM;
goto exit_put_clk;
} // ... /*
* Request the regions.
*/
if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
err = -EBUSY;
goto exit_kfree;
} base = ioremap(res->start, resource_size(res));
if (!base) {
err = -ENOMEM;
goto exit_release;
}
// ... /* request dma */
pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
if (pcdev->dma_chan < 0) {
dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
err = -EBUSY;
goto exit_iounmap;
}
// ... /* request irq */
err = claim_fiq(&fh);
if (err) {
dev_err(&pdev->dev, "Camera interrupt register failed\n");
goto exit_free_dma;
} // ...
err = soc_camera_host_register(&pcdev->soc_host);
if (err)
goto exit_free_irq; dev_info(&pdev->dev, "MX1 Camera driver loaded\n"); return 0; exit_free_irq:
disable_fiq(irq);
mxc_set_irq_fiq(irq, 0);
release_fiq(&fh);
exit_free_dma:
imx_dma_free(pcdev->dma_chan);
exit_iounmap:
iounmap(base);
exit_release:
release_mem_region(res->start, resource_size(res));
exit_kfree:
kfree(pcdev);
exit_put_clk:
clk_put(clk);
exit:
return err;
}

相信每一个写过Linux driver的工程师,都在probe函数中遇到过上面的困惑:

  • 要顺序申请多种资源(IRQ、Clock、memory、regions、ioremap、dma、等等),只要任意一种资源申请失败,就要回滚释放之前申请的所有资源。
  • 于是函数的最后,一定会出现很多的goto标签(如上面的exit_free_irq、exit_free_dma、等等),并在申请资源出错时,小心翼翼的goto到正确的标签上,以便释放已申请资源。

正像上面代码一样,整个函数被大段的、重复的“if (condition) { err = xxx; goto xxx; }”充斥,浪费精力,容易出错,不美观。

最终,Linux设备模型借助device resource management(设备资源管理),帮我们解决了这个问题。driver你只管申请就行了,不用考虑释放,我设备模型帮你释放。既然你驱动需要用的资源都是是设备的资源,那么资源的管理归于device,也就是说不需要driver过多的参与。当device和driver detach的时候,device会自动的释放其所有的资源。

最终,我们的driver可以这样写:

static int __init mx1_camera_probe(struct platform_device *pdev)
{
// ... res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = platform_get_irq(pdev, 0);
if (!res || (int)irq <= 0) {
return -ENODEV;
} clk = devm_clk_get(&pdev->dev, "csi_clk");
if (IS_ERR(clk)) {
return PTR_ERR(clk);
} pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
if (!pcdev) {
dev_err(&pdev->dev, "Could not allocate pcdev\n");
return -ENOMEM;
} // ... /*
* Request the regions.
*/
if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), DRIVER_NAME)) {
return -EBUSY;
} base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!base) {
return -ENOMEM;
}
// ... /* request dma */
pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
if (pcdev->dma_chan < 0) {
dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
return -EBUSY;
}
// ... /* request irq */
err = claim_fiq(&fh);
if (err) {
dev_err(&pdev->dev, "Camera interrupt register failed\n");
return err;
} // ...
err = soc_camera_host_register(&pcdev->soc_host);
if (err)
return err; dev_info(&pdev->dev, "MX1 Camera driver loaded\n"); return 0;
}

怎么做到呢?注意上面“devm_”开头的接口,答案就在那里。

不要再使用那些常规的资源申请接口,用devm_xxx的接口代替。

为了保持兼容,这些新接口和旧接口的参数保持一致,只是名字前加了“devm_”,并多加一个struct device指针。

devm_xxx接口

下面列举一些常用的资源申请接口,它们由各个framework(如clock、regulator、gpio、等等)基于device resource management实现。

使用时,直接忽略“devm_”的前缀,后面剩下的部分,driver工程师都很熟悉。

只需记住一点,driver可以只申请,不释放,设备模型会帮忙释放。

不过如果为了严谨,在driver remove时,可以主动释放(也有相应的接口,这里没有列出)。

extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);

void __iomem *devm_ioremap_resource(struct device *dev,
struct resource *res);
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
unsigned long size); struct clk *devm_clk_get(struct device *dev, const char *id); int devm_gpio_request(struct device *dev, unsigned gpio,
const char *label); static inline struct pinctrl * devm_pinctrl_get_select(
struct device *dev, const char *name) static inline struct pwm_device *devm_pwm_get(struct device *dev,
const char *consumer); struct regulator *devm_regulator_get(struct device *dev, const char *id); static inline int devm_request_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, unsigned long irqflags,
const char *devname, void *dev_id); struct reset_control *devm_reset_control_get(struct device *dev,
const char *id);

设备资源

一个设备能工作,需要依赖很多的外部条件,如供电、时钟等等,这些外部条件称作设备资源(device resouce)。

对于现代计算机的体系结构,可能的资源包括:

  • power,供电。
  • clock,时钟。
  • memory,内存,在kernel中一般使用kzalloc分配。
  • GPIO,用户和CPU交换简单控制、状态等信息。
  • IRQ,触发中断。
  • DMA,无CPU参与情况下进行数据传输。
  • 虚拟地址空间,一般使用ioremap、request_region等分配。

而在Linux kernel的眼中,“资源”的定义更为广义,比如PWM、RTC、Reset,都可以抽象为资源,供driver使用。

在较早的kernel中,系统还不是特别复杂,且各个framework还没有成型,因此大多的资源都由driver自行维护。但随着系统复杂度的增加,driver之间共用资源的情况越来越多,同时电源管理的需求也越来越迫切。

于是kernel就将各个resource的管理权收回,基于“device resource management”的框架,由各个framework统一管理,包括分配和回收。

device resource management的软件框架

位于“drivers/base/devres.c”中,它的实现非常简单,为什么呢?因为资源的种类有很多,表现形式也多种多样,而devres不可能一一知情,也就不能进行具体的分配和回收。

因此,devres能做的(也是它的唯一功能),就是:

  • 提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。

而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。

其它的driver,位于这些framework之上,使用它们提供的机制和接口,开发起来就非常方便了。

device原型中的devres_head

先从struct device开始吧!该结构中有一个名称为“devres_head”的链表头,用于保存该设备申请的所有资源。

devres device resource

struct device {
// ...
spinlock_t devres_lock;
struct list_head devres_head;
//...
}

devres原型

devres代表了资源的数据结构。

不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的。

换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)!

当然了,有关的接口还是公开的

// drivers/base/devres.c
struct devres {
struct devres_node node;
/* -- 3 pointers */
unsigned long long data[]; /* guarantee ull alignment */
};

咋一看非常简单,一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。

  • data是一个零长数组,用于存放所申请的不定长内存;因为整个memory空间是连续的,因此可以通过释devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。

  • 而node用于将devres组织起来,方便插入到device结构的devres_head链表中

devres_node原型

// base/devres.c
struct devres_node {
struct list_head entry;
dr_release_t release;
#ifdef CONFIG_DEBUG_DEVRES
const char *name;
size_t size;
#endif
};

entry:刚刚说了,devres使用node用于将devres组织起来,方便插入到device结构的devres_head链表中

release:资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源。

抛开用于debug的变量不说,也很简单,一个entry list_head,一个release回调函数。看不出怎么抽象资源啊!别急,奥妙都在data这个零长度数组上面呢。

向上层framework提供的接口

其实有两对:

  • devres_alloc/devres_free
  • devres_add/devres_remove

devres_alloc/devres_free

// drivers/base/devres.c
/**
* devres_alloc - Allocate device resource data
* @release: Release function devres will be associated with
* @size: Allocation size
* @gfp: Allocation flags
*
* Allocate devres of @size bytes. The allocated area is zeroed, then
* associated with @release. The returned pointer can be passed to
* other devres_*() functions.
*
* RETURNS:
* Pointer to allocated devres on success, NULL on failure.
*/
void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
{
struct devres *dr; dr = alloc_dr(release, size, gfp | __GFP_ZERO);
if (unlikely(!dr))
return NULL;
return dr->data;
}
EXPORT_SYMBOL_GPL(devres_alloc); /**
* devres_free - Free device resource data
* @res: Pointer to devres data to free
*
* Free devres created with devres_alloc().
*/
void devres_free(void *res)
{
if (res) {
struct devres *dr = container_of(res, struct devres, data); BUG_ON(!list_empty(&dr->node.entry));
kfree(dr);
}
}
EXPORT_SYMBOL_GPL(devres_free);

devres_alloc调用alloc_dr,分配一个struct devres类型的变量,并返回其中的data指针(data变量实际上是资源的代表)。

在alloc_dr中初始化

static __always_inline struct devres * alloc_dr(dr_release_t release,
size_t size, gfp_t gfp)
{
size_t tot_size = sizeof(struct devres) + size;
struct devres *dr; dr = kmalloc_track_caller(tot_size, gfp);
if (unlikely(!dr))
return NULL; memset(dr, 0, offsetof(struct devres, data)); INIT_LIST_HEAD(&dr->node.entry);
dr->node.release = release;
return dr;
}

看第一句就可以了,在资源size之前,加一个struct devres的size,就是total分配的空间。

除去struct devres的,就是资源的(由data指针访问)。

之后是初始化struct devres变量的node,可以看到,devres_alloc指定的release方法,便于在适当的时机执行。

devres_add/devres_remove

void devres_add(struct device *dev, void *res)
{
struct devres *dr = container_of(res, struct devres, data);
unsigned long flags; spin_lock_irqsave(&dev->devres_lock, flags);
// 将资源添加到设备的资源链表头(devres_head)中。
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
}

从资源指针中,取出完整的struct devres指针,调用add_dr接口。

使用add_dr挂入devers链表中

将资源添加到设备的资源链表头(devres_head)中。

add_dr也很简单,把struct devres指针挂到设备的devres_head中即可

static void add_dr(struct device *dev, struct devres_node *node)
{
devres_log(dev, node, "ADD");
BUG_ON(!list_empty(&node->entry));
list_add_tail(&node->entry, &dev->devres_head);
}

devres_destroy

/**
* devres_destroy - Find a device resource and destroy it
* @dev: Device to find resource from
* @release: Look for resources associated with this release function
* @match: Match function (optional)
* @match_data: Data for the match function
*
* Find the latest devres of @dev associated with @release and for
* which @match returns 1. If @match is NULL, it's considered to
* match all. If found, the resource is removed atomically and freed.
*
* Note that the release function for the resource will not be called,
* only the devres-allocated data will be freed. The caller becomes
* responsible for freeing any other data.
*
* RETURNS:
* 0 if devres is found and freed, -ENOENT if not found.
*/
int devres_destroy(struct device *dev, dr_release_t release,
dr_match_t match, void *match_data)
{
void *res; res = devres_remove(dev, release, match, match_data);
if (unlikely(!res))
return -ENOENT; devres_free(res);
return 0;
}
EXPORT_SYMBOL_GPL(devres_destroy);

从设备中找出对应的资源,并摧毁。

以IRQ模块为例看看如何使用资源管理

先看一个使用device resource management的例子(IRQ模块):

/* include/linux/interrupt.h */
static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
devname, dev_id);
} /* kernel/irq/devres.c */
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id)
{
struct irq_devres *dr;
int rc; // 申请设备资源
dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
GFP_KERNEL);
if (!dr)
return -ENOMEM; // 使用设备资源做自己的事情
rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
dev_id);
// 如果失败,可以通过devres_free接口释放资源占用的空间
if (rc) {
devres_free(dr);
return rc;
}
dr->irq = irq;
dr->dev_id = dev_id; // 注册所使用的设备资源
devres_add(dev, dr); return 0;
}
EXPORT_SYMBOL(devm_request_threaded_irq); void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
{
struct irq_devres match_data = { irq, dev_id }; WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
&match_data));
free_irq(irq, dev_id);
}
EXPORT_SYMBOL(devm_free_irq);

前面我们提过,上层的IRQ framework,会提供两个和request_irq/free_irq基本兼容的接口,这两个接口的实现非常简单,就是在原有的实现之上,封装一层devres的操作。

irq_devres原型

用于保存和resource有关的信息(对中断来说,就是IRQ num)

/*
* Device resource management aware IRQ request/free implementation.
*/
struct irq_devres {
unsigned int irq;
void *dev_id;
};

devm_irq_release

用于release resource的回调函数(这里的release,和memory无关,例如free IRQ)

static void devm_irq_release(struct device *dev, void *res)
{
struct irq_devres *this = res; free_irq(this->irq, this->dev_id);
}

因为回调函数是由devres模块调用的,由它的参数可知,struct irq_devres变量就是实际的“资源”,但对devres而言,它并不知道该资源的实际形态,因而是void类型指针。也只有这样,devres模块才可以统一的处理所有类型的资源。

申请设备资源

    dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
GFP_KERNEL);
if (!dr)
return -ENOMEM;

以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。

使用设备资源做自己的事情

    // 使用设备资源做自己的事情
rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
dev_id);
// 如果失败,可以通过devres_free接口释放资源占用的空间
if (rc) {
devres_free(dr);
return rc;
}

调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。

如果失败了,可以通过devres_free接口释放资源占用的空间。

注册所使用的设备资源

注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头

    devres_add(dev, dr);

到这里,设备资源管理框架就可以:用来在不需要使用的时候摧毁资源了。

用完以后摧毁资源

irq系统中,我们会调用devm_free_irq来释放中断。

void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
{
struct irq_devres match_data = { irq, dev_id }; WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
&match_data));
free_irq(irq, dev_id);
}

而其中就会调用devres_destroy接口,将devresdevres_head中移除,并释放资源。

向设备模型提供的接口

向设备模型提供的接口:devres_release_all

这里是重点,用于自动释放资源。

devres_release_all

int devres_release_all(struct device *dev)
{
unsigned long flags; /* Looks like an uninitialized device structure */
if (WARN_ON(dev->devres_head.next == NULL))
return -ENODEV;
spin_lock_irqsave(&dev->devres_lock, flags);
return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
flags);
}

以设备指针为参数,直接调用release_nodes

static int release_nodes(struct device *dev, struct list_head *first,
struct list_head *end, unsigned long flags)
__releases(&dev->devres_lock)
{
LIST_HEAD(todo);
int cnt;
struct devres *dr, *tmp; // 将设备所有的`devres`从设备的`devres_head`中移除
cnt = remove_nodes(dev, first, end, &todo); spin_unlock_irqrestore(&dev->devres_lock, flags); /* Release. Note that both devres and devres_group are
* handled as devres in the following loop. This is safe.
*/ list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
devres_log(dev, &dr->node, "REL");
// 调用所有资源的release回调函数(例如上面`devm_irq_release`),
// 回调函数会回收具体的资源(如`free_irq`)。
dr->node.release(dev, dr->data);
// 最后,调用free,释放devres以及资源所占的空间
kfree(dr);
} return cnt;
}

调用时机

先回忆一下设备模型中probe的流程,devres_release_all接口被调用的时机有两个:

  • really_probe失败
  • 设备与驱动分离时:deriver dettach时(就是driver remove时)

really_probe失败

probe调用过程为(就不详细的贴代码了):__driver_attach/__device_attach-->driver_probe_device—>really_probe

really_probe调用driver或者bus的probe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all

static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0; atomic_inc(&probe_count); dev->driver = drv; /* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed; if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
} if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
} driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done; probe_failed:
devres_release_all(dev);
// ...
return ret;
}

设备与驱动分离时

另外一个时机是在,deriver dettach时(就是driver remove时):driver_detach/bus_remove_device-->__device_release_driver-->devres_release_all

Linux 内核:设备驱动模型(6)设备资源管理的更多相关文章

  1. Linux设备驱动模型底层架构及组织方式

    1.什么是设备驱动模型? 设备驱动模型,说实话这个概念真的不好解释,他是一个比较抽象的概念,我在网上也是没有找到关于设备驱动模型的一个定义,那么今天就我所学.所了解 到的,我对设备驱动模型的一个理解: ...

  2. Linux设备驱动模型简述(源码剖析)

    1. Linux设备驱动模型和sysfs文件系统 Linux内核在2.6版本中引入设备驱动模型,简化了驱动程序的编写.Linux设备驱动模型包含设备(device).总线(bus).类(class)和 ...

  3. Linux内核驱动学习(四)Platform设备驱动模型

    Linux platform设备驱动模型 文章目录 Linux platform设备驱动模型 前言 框架 设备与驱动的分离 设备(device) 驱动(driver) 匹配(match) 参考 前言 ...

  4. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  5. Linux 字符设备驱动模型

    一.使用字符设备驱动程序 1. 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块 2. 创建设备文件 通 ...

  6. linux设备驱动模型

    尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要. Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统 ...

  7. linux设备驱动模型(kobject与kset)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...

  8. Linux 总线、设备、驱动模型 与 设备树

    1.总线.设备.驱动模型 本着高内聚.低耦合的原则,Linux 把设备驱动模型分为了总线.设备和驱动三个实体,这三个实体在内核里的职责分别如下: 设备和驱动向总线进行注册,总线负责把设备和对应的驱动绑 ...

  9. Linux驱动之平台设备驱动模型简析(驱动分离分层概念的建立)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而有一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...

  10. Linux设备驱动模型之platform(平台)总线详解

    /********************************************************/ 内核版本:2.6.35.7 运行平台:三星s5pv210 /*********** ...

随机推荐

  1. ADOBE FORM的一些相关资料

    虽然很多人觉得打印程序的开发很无聊(我也这么想),但在实际工作中,打印算是比较有意义的工作,所以还是值得学习的. 之前翻译过几篇Adobe Form的文章,其中的内容,可以帮助创建一些简单的打印示例, ...

  2. 03.redis 事务

    课程学习地址: https://www.bilibili.com/video/BV1S54y1R7SB?p=23 中间手册地址: http://www.redis.cn/ Redis事务本质:一组命令 ...

  3. 《Modern C++ Design》之上篇

    如下内容是在看侯捷老师翻译的<Modern C++ Design>书籍时,整理的code和摘要,用于不断地温故知新. 第一章 1. 运用 Template Template 参数实作 Po ...

  4. 数据库—SQL语言学习

    文章目录 SQL 数据类型 重要的关键字 定义数据库 数据库的文件 table创建与删除 表的定义 表的alter 表的删除 视图 定义视图 删除视图 更新视图 插入视图 视图总结 索引 SQL单表查 ...

  5. 5款超好用的AI换脸软件,一键视频直播换脸(附下载链接)

    随着AIGC的火爆,AI换脸技术也被广泛应用于娱乐.广告.电影制作等领域,本期文章系统介绍了市面上超火的5款AI软件 换脸整合包收录了全部5款AI工具,请按照需要选择下载: 百度网盘:https:// ...

  6. java练习项目——记账本

    包含登录.注册.记账.每日账单查看.每月报表.添加记账类型这些功能.数据存储采用的是txt文档+xml文档.程序是一个控制台程序,用IntelliJ IDEA+jdk8开发.涉及到的知识有List集合 ...

  7. 基于 Go 的 Web 框架调研

    基于 Go 的 Web 框架调研 概述 调研总体目标 找出适合企业应用后台研发的 Go Web 框架 调研考察方向 项目完善程度: 功能完善: 路由, 模板, 插件/扩展, ORM, 命令行工具, 日 ...

  8. js 中你不知道的各种循环测速

    在前端 js 中,有着多种数组循环的方式: for 循环: while 和 do-while 循环: forEach.map.reduce.filter 循环: for-of 循环: for-in 循 ...

  9. c# - 如何在圆角 WPF 窗体中创建圆角矩形?

    我正在 WPF 中创建一个应用程序,我想要圆角.收到.现在窗体是无边框的,我正在尝试创建一个圆角矩形并将其放在顶部,使其看起来像 Windows 应用程序的顶部栏. 我做不到. 这是我的代码: < ...

  10. 一文教你如何调用Ascend C算子

    本文分享自华为云社区<一文教你如何调用Ascend C算子>,作者: 昇腾CANN. Ascend C是CANN针对算子开发场景推出的编程语言,原生支持C和C++标准规范,兼具开发效率和运 ...