linux通用时钟框架(CCF)

目录

    • 前言
    • CCF 介绍
      • 提供者和消费者的概念
      • CCF 框架组成关系
      • CCF 程序关键结构体
    • CCF 重要组成
      • 注册时钟
      • 未使用设备树的时钟注册操作
      • 使用设备树的时钟注册操作
        • 设备树分析与使用
          • clocks 分析举例
          • 时钟输出的名称
          • of_parse_phandle_with_args 的示例说明
          • __of_clk_get_from_provider 的分析
          • CCF 时钟获取机制的总结
    • 编写时钟提供者驱动
    • 实际使用示例

前言

linux 内核版本 v4.19
嵌入式平台rv1109 , 文中代码出处。

CCF 介绍

提供者和消费者的概念

CCF背后的主要思想是统一和抽象分布在不同SoC时钟驱动程序中的类似代码。这种标准化的方法引入了时钟提供者时钟消费者的概念:

  • 提供者是Linux内核驱动程序,它连接到框架并提供对硬件的访问,从而根据SoC数据表提供(使这些对消费者可用)时钟树(由于可以转储整个时钟树);

  • 消费者是通过公共API访问框架的Linux内核驱动程序或子系统;

也就是说,驱动程序既可以是提供者,也可以是消费者(然后它可以使用它提供的一个或多个时钟,也可以使用其他人提供的一个或多个时钟)。

CCF 框架组成关系

在使用CCF之前,需要通过CONFIG_COMMON_CLK选项将其支持加入内核,CCF 本身分为两部分:

  • 公共时钟框架核心:这是框架的核心,当您添加新的驱动程序并提供struct clk的公共定义时,不应该修改它,它统一了框架级代码和传统的依赖于平台的实现,这些实现过去常常在各种平台上复制。这一半还允许我们将消费者接口(也称为clk实现)包装在结构体clk_ops之上,该结构体必须由每个时钟提供程序提供。

  • 特定于硬件的那一半:它的目标是必须为每个新的硬件时钟写入的时钟设备。这需要驱动程序提供clk_ops结构体,该结构体对应于用于对底层硬件进行操作的回调函数(这些回调函数由时钟的核心实现调用),以及包装和抽象时钟硬件的相应硬件特定结构。

这两部分通过struct clk_hw连接在一起。

CCF 程序关键结构体

struct clk_hw是CCF中每种时钟类型的基本结构。它可以看作是一个句柄,用于从struct clk遍历到相应的特定于硬件的结构。

include/linux/clk-provider.h
struct clk_hw {struct clk_core *core;struct clk *clk; //时钟的消费者表示,每个消费者API都依赖于这个结构体。const struct clk_init_data *init;
};
  • clk 它由时钟框架分配和维护,并在需要时提供给时钟使用者。每当消费者通过clk_get启动对CCF中的时钟设备(即clk_core)的访问时,它都需要获得一个句柄,即clk.

  • init 在初始化底层时钟提供程序驱动程序的过程中,调用clk_register()接口来注册时钟硬件。在此之前,需要设置一些初始数据,这些初始数据被抽象为struct clk_init_data数据结构。在初始化过程中,clk_init_data中的数据用于初始化clk_core数据结构,该数据结构对应于clk_hw。初始化完成后,clk_init_data没有任何意义。

CCF 重要组成

注册时钟

struct clk *clk_register(struct device *dev, struct clk_hw *hw)
int clk_hw_register(struct device *dev, struct clk_hw *hw)
  • 调用clk_hw_register()(它在内部调用__clk_core_init()来初始化时钟)时,如果这个时钟有一个有效的父时钟,它将在父时钟的子列表中结束。另一方面,如果num_parent为0,则将其放在clk_root_list中。否则,它将挂起在clk_orphan_list中,这意味着它没有有效的父节点。
  • 此外,每当一个新的时钟被clk_init时,CCF将遍历clk_orphan_list(孤儿时钟列表),并重新父化当前正在初始化的时钟的子时钟。这就是CCF保持时钟树与硬件拓扑一致的方式。
  • 另一方面,struct clk是时钟设备的消费者端实例。
    基本上,所有用户对时钟设备的访问都会创建一个结构clk类型的访问句柄。当不同的用户访问相同的时钟设备时,尽管在底层使用相同的struct clk_core实例,但他们访问的句柄(struct clk)是不同的。

clk_hw_register 封装了clk_register,只是为了兼容,推荐使用clk_hw_register, (不应该直接使用clk_reregister(),因为clk_reregister返回结构clk。这可能会导致混乱,并打破提供者和使用者接口之间的严格分离)。

clk_hw_register / clk_register 的实现逻辑如下(clk/clk.c 代码略):

  • 分配struct clk_core空间(clk_hw->core):
  1. 根据struct clk_hw指针提供的信息初始化clk的字段名称、ops、hw、flags、num_parents和parents_name。
  2. 调用内核接口__clk_core_init()来执行后续初始化操作,包括构建时钟树层次结构。
  • 通过内部内核接口clk_create_clk()分配struct clk空间(clk_hw->clk),并返回此结构clk变量。

CCF框架负责建立整个抽象时钟树的树结构并维护其数据,因此它通过drivers/clk/clk.c中定义的两个静态链表来实现这一点,如下所示:

static HLIST_HEAD(clk_root_list);
static HLIST_HEAD(clk_orphan_list);

每当您在时钟hw上调用clk_hw_register()(它在内部调用__clk_core_int()来初始化时钟)时,如果该时钟有一个有效的父级,它将最终出现在父级的子级列表中。另一方面,若num_parent为0,则将其放置在clk_root_list中。否则,它将挂在clk_orpan_list中,这意味着它没有有效的父级。此外,每次新的clk为clk_init时,CCF都会遍历clk_orpan_list(孤立时钟的列表),并为当前正在初始化的时钟的子级重新设置父级。这就是CCF保持时钟树与硬件拓扑一致的方式。

未使用设备树的时钟注册操作

  1. 由于知道clk_register()的目的只是注册到公共时钟框架,因此消费者无法知道如何定位clk。因此,对于底层时钟提供程序驱动程序,除了调用clk_register()函数以注册到公共时钟框架之外,还必须在clk_register()之后立即调用clk_register_clkdev(),以便用名称绑定时钟(否则,时钟使用者将不知道如何定位时钟)。因此,内核使用struct clk_lookup(顾名思义)来查找可用的时钟。
  2. 为了使用基于hw的API强制实现提供者和使用者代码之间的分离,代码中的clk_hw_register_clkdev()和clk_register_clkdev。

clk_lookup 结构

struct clk_lookup {struct list_head	node;const char		*dev_id;const char		*con_id;struct clk		*clk;struct clk_hw		*clk_hw;
};

dev_id和con_id用于识别/查找适当的clk。这个clk是相应的底层时钟。node是挂在全局时钟列表中

clk_hw_register_clkdev --> _clkdev_add

static void __clkdev_add(struct clk_lookup *cl)
{mutex_lock(&clocks_mutex);list_add_tail(&cl->node, &clocks);mutex_unlock(&clocks_mutex);
}void clkdev_add(struct clk_lookup *cl)
{if (!cl->clk_hw)cl->clk_hw = __clk_get_hw(cl->clk);__clkdev_add(cl);
}
EXPORT_SYMBOL(clkdev_add);

使用设备树的时钟注册操作

使用设备树后,每个时钟提供程序都成为DTS中的一个节点;也就是说,每个clk在其对应的设备树中都有一个设备节点。在这种情况下,与其将clk和名称绑定在一起,不如通过一个新的数据结构struct of_clk_provider来绑定clk和你的设备节点。具体数据结构如下:

struct of_clk_provider {struct list_head link;//Entry in global list of clock providersstruct device_node *node;//表示时钟设备的DTS节点struct clk *(*get)(struct of_phandle_args *clkspec, void *data);struct clk_hw *(*get_hw)(struct of_phandle_args *clkspec, void *data);void *data;
};

of_clk_provider 注释中的 “Entry” 有 “条目,账目,记录” 的意思,可理解为条目。
get_hw是时钟的回调。对于设备(使用者),通过clk_get() 调用它来返回与节点相关联的时钟或NULL。这里后会代码会讲解
get 为老的API(兼容老的驱动代码),与get_hw功能一样。

CCF 引入了一个新的list 帮助管理所有DTS节点和时钟之间的对应关系

static LIST_HEAD(of_clk_providers);

of_clk_add_hw_provider 代替 clk_hw_register_clkdev

/*** of_clk_add_hw_provider() - Register a clock provider for a node* @np: Device node pointer associated with clock provider* @get: callback for decoding clk_hw* @data: context pointer for @get callback.*/
int of_clk_add_hw_provider(struct device_node *np,struct clk_hw *(*get)(struct of_phandle_args *clkspec,void *data),void *data)

这在后面的例子中会看到of_clk_add_hw_provider 的使用

设备树分析与使用

clocks 分析举例

ti芯片的cdce706 为例

	clocks {clk54: clk54 {#clock-cells = <0>;compatible = "fixed-clock";clock-frequency = <54000000>;clock-output-names = 'osc';};};i2c0: i2c-master@0d090000 {......cdce706: clock-synth@69 {compatible = "ti,cdce706";#clock-cells = <1>;reg = <0x69>;clocks = <&clk54>;clock-names = "clk_in0";};};

时钟是通过clocks属性分配给使用者的,时钟提供者也可以是消费者。clk54是一个固定时钟;cdce706是一个时钟提供者,它也使用clk54(在clocks属性中作为phandle给出)。

时钟提供程序节点需要指定的最重要的信息是 #clock-cells 属性,它决定了时钟说明符的长度:

  1. 当它为0时,这意味着只需要将该提供程序的phandle属性提供给使用者
  2. 当它为1(或更大)时,这意味着phandle属性具有多个输出,并且需要提供附加信息,例如指示需要使用什么输出的ID。此ID直接由立即值表示。最好在头文件中定义系统中所有时钟的ID。设备树可以包括这个头文件,例如clocks=<&clock CLK_SPI0>,其中CLK_SPI0是在头文件中定义的宏。
时钟输出的名称

让我们来看看时钟输出名称。这是一个可选但推荐的属性,应该是与输出(即提供的)时钟线名称相对应的字符串列表

osc {#clock-cells = <1>;clock-output-names = 'ckout1', 'ckout2'; //注意这里的clock-output-names  output 名字固定,表明输出时钟
};

定义了一个设备,该设备提供两条时钟输出线,分别命名为ckout1和ckout2

消费者的节点不应直接使用这些clout1 等名称来引用这些时钟线,应该使用适应的时钟说明符(即 提供者的#clock-cells ),根据设备的需求命名输入时钟线,如下

device {clocks = <&osc 0>, <&osc 1>;	clock-names = 'baud', 'register'; //消费者时钟名clock-names
};

当一条时钟线被分配给一个消费者设备时,当该消费者的驱动程序调用clk_get(或用于获取时钟的类似接口)时,该接口调用of_clk_get_by_name(),后者反过来调用__of_clk_get()。

static struct clk *__of_clk_get(struct device_node *np, int index,const char *dev_id, const char *con_id)
{struct of_phandle_args clkspec;struct clk *clk;int rc;rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,&clkspec);if (rc)return ERR_PTR(rc);clk = __of_clk_get_from_provider(&clkspec, dev_id, con_id, true);of_node_put(clkspec.np);return clk;
}

关注 一下of_parse_phandle_with_args 函数中的clkspec

//of.h
#define MAX_PHANDLE_ARGS 16
struct of_phandle_args {struct device_node *np;int args_count;uint32_t args[MAX_PHANDLE_ARGS];
};

在struct of_phandle_args中,np元素是指向与phandle属性相对应的节点的指针。在时钟说明符中,它将是时钟提供程序的设备树节点。args_count元素对应于说明符中phandle后面的单元格数。它可以用于遍历args,args是一个包含有内容的参数的数组。

of_parse_phandle_with_args 的示例说明

上面的文字描述难以说明白,下面用代码举例说明of_parse_phandle_with_args

phandle1: node1 {#gpio-cells = <2>;
};phandle2: node2 {#list-cells = <1>;
};node3 {list = <&phandle1 1 2 &phandle2 3>;
};//或者下面的写法
node3 {list = <&phandle1 1 2>, <&phandle2 3>;
}

这里,node3是一个消费者。要获得指向node2节点的设备节点指针,可以调用of_parse_phandle_with_args(node3 ,‘list’,‘#list-cells’,1,&args);。由于&phandle2位于列表(list)中的索引1(从0开始),因此我们在index参数中指定了1。

同样,要获取node1节点的关联设备节点,可以调用of_parse_phandle_with_args(node3,‘list’,‘#gpio-cells’,0,&args);。对于第二种情况,如果我们查看args输出参数,我们将看到args->np对应于node3,args->args_count的值为2(因为这个说明符需要2个参数),args–>args[0]的值为1,args->args[1]的值为2中,这将对应于说明符中的2个参数。

__of_clk_get_from_provider 的分析

对于of_parse_phandle_with_args 的理解到这里,接下来看一下 __of_clk_get_from_provider

struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,const char *dev_id, const char *con_id,bool with_orphans)
{struct of_clk_provider *provider;struct clk *clk = ERR_PTR(-EPROBE_DEFER);struct clk_hw *hw;if (!clkspec)return ERR_PTR(-EINVAL);/* Check if we have such a provider in our array */mutex_lock(&of_clk_mutex);list_for_each_entry(provider, &of_clk_providers, link) {if (provider->node == clkspec->np) {hw = __of_clk_get_hw_from_provider(provider, clkspec);clk = __clk_create_clk(hw, dev_id, con_id,with_orphans);}if (!IS_ERR(clk)) {if (!__clk_get(clk)) {__clk_free_clk(clk);clk = ERR_PTR(-ENOENT);}break;}}mutex_unlock(&of_clk_mutex);return clk;
}

这个函数只是遍历时钟提供程序(在of_clk_providers列表中),当找到合适的提供程序时,它会调用作为of_clk_add_provider()的第二个参数给定的底层回调,以解码底层时钟。这里,of_parse_phandle_with_args()返回的时钟说明符作为参数给出(上文中的of_phandle_args 结构体参数)。当你必须向其他设备公开时钟提供程序时,我们不得不使用_clk_add_hw_provider()。作为第二个参数,每当使用者调用clk_get()时,该接口接受CCF用来解码底层时钟的回调。

此回调的结构如下中的clk_src_get 回调

int of_clk_add_provider(struct device_node *np,struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,void *data),void *data)
CCF 时钟获取机制的总结

当消费者调用clk_get()时,CCF内部调用__of_clk_get(struct device_node *np, int index,const char *dev_id, const char *con_id)。这是作为该使用者的设备节点属性的第一个参数给出的,因此CCF可以获取时钟说明符并找到与提供程序对应的设备节点特性(通过of_parse_phandle_with_args() )。然后它以of_phandle_args的形式返回这个值。这个of_phandle_args对应于时钟说明符,并作为参数提供给__of_clk_ get_from_provider(),该参数只是将of_phandle_args(即of_phandler_args->np)中提供程序的设备节点属性与of_clk_provider中存在的属性进行比较,后者是设备树时钟提供程序的列表。一旦找到匹配,就会调用该提供程序的相应of_clk_provider->get()回调,并返回底层时钟。

尽管可以编写自己的回调,但CCF框架提供了两个通用的解码回调,涵盖了大多数情况。它们分别是of_clk_src_onecell_get() 和of_clk_src_simple_get()

of_clk_hw_simple_get() 用于简单的时钟提供程序,其中除了时钟本身之外,不需要特殊的上下文数据结构,例如时钟gpio驱动程序(在drivers/clk/clk-gpio.c中)。

struct clk_hw *of_clk_hw_simple_get(struct of_phandle_args *clkspec, void *data)
{return data;
}
EXPORT_SYMBOL_GPL(of_clk_hw_simple_get);

of_clk_src_onecell_get 的分析略

编写时钟提供者驱动

略… 后面的文章中再写

实际使用示例

前面提到的 of_clk_add_hw_provider 使用示例,下面为rk809 pmic 中clk的部分代码

struct rk808_clkout {struct rk808 *rk808;struct clk_hw		clkout1_hw;struct clk_hw		clkout2_hw;
};static struct clk_hw *
of_clk_rk808_get(struct of_phandle_args *clkspec, void *data)
{struct rk808_clkout *rk808_clkout = data;unsigned int idx = clkspec->args[0];if (idx >= 2) {pr_err("%s: invalid index %u\n", __func__, idx);return ERR_PTR(-EINVAL);}return idx ? &rk808_clkout->clkout2_hw : &rk808_clkout->clkout1_hw;
}static int rk808_clkout_probe(struct platform_device *pdev)
{struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);struct i2c_client *client = rk808->i2c;struct device_node *node = client->dev.of_node;struct clk_init_data init = {};struct rk808_clkout *rk808_clkout;int ret;rk808_clkout = devm_kzalloc(&client->dev,sizeof(*rk808_clkout), GFP_KERNEL);if (!rk808_clkout)return -ENOMEM;rk808_clkout->rk808 = rk808;init.parent_names = NULL;init.num_parents = 0;init.name = "rk808-clkout1";init.ops = &rk808_clkout1_ops;rk808_clkout->clkout1_hw.init = &init;/* optional override of the clockname */of_property_read_string_index(node, "clock-output-names",0, &init.name);ret = devm_clk_hw_register(&client->dev, &rk808_clkout->clkout1_hw);if (ret)return ret;init.name = "rk808-clkout2";init.ops = rkpmic_get_ops(rk808->variant);rk808_clkout->clkout2_hw.init = &init;/* optional override of the clockname */of_property_read_string_index(node, "clock-output-names",1, &init.name);ret = devm_clk_hw_register(&client->dev, &rk808_clkout->clkout2_hw);if (ret)return ret;return of_clk_add_hw_provider(node, of_clk_rk808_get, rk808_clkout);
}static int rk808_clkout_remove(struct platform_device *pdev)
{struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);struct i2c_client *client = rk808->i2c;struct device_node *node = client->dev.of_node;of_clk_del_provider(node);return 0;
}static struct platform_driver rk808_clkout_driver = {.probe = rk808_clkout_probe,.remove = rk808_clkout_remove,.driver		= {.name	= "rk808-clkout",},
};module_platform_driver(rk808_clkout_driver);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高德地图通过画面中的一个覆盖物设置图中心点和zoom

需要将这个覆盖物置于地图中间且不超过地图边界的放至最大 计算覆盖物中心点&#xff0c;定为地图中心点 计算覆盖物的最大经纬度&#xff0c;和最小经纬度&#xff0c;测算出实际最长距离&#xff0c;根据距离与zoom对应关系设置zoom function getAreaCenter(params, info)…

企业架构LNMP学习笔记27

Keepalived的配置补充&#xff1a; 脑裂&#xff08;裂脑&#xff09;&#xff1a;vip出现在了多台机器上。网络不通畅&#xff0c;禁用了数据包&#xff0c;主备服务器没法通讯&#xff0c;造成备服务器认为主服务器不可用&#xff0c;绑定VIP&#xff0c;主服务器VIP不会释放…

DAV--接口

/dashboard/getDatasetList 获取工作表 两个参数&#xff1a; 大屏id 工作表id ------------------ 一般情况下&#xff0c;只需上传一个参数&#xff0c;或者不传参数 1. 获取登录用户 String userId SecurityUtil.getUserId(); 2. 获取某个大屏的用户工作表集合 &#x…

laravel系列(二) Dcat admin框架开发工具使用

开发工具可以非常好的帮助我们去快速的开发CURD等操作,但也是有部分框架有些不是太便捷操作,这篇博客主要为大家介绍Dcat admin的开发工具详细使用. 如何创建页面: 在联表我们首先要去.env文件中去找连接数据库方法: APP_NAMELaravel APP_ENVlocal APP_KEYbase64:thO0lOVlzj0…

VR数字工厂,为企业工厂打造竞争新优势

工业经济中大部分行业都是制造业&#xff0c;为了合力助推工业经济提质增效&#xff0c;谋划推进制造业数字化转型就显得尤为重要了。用VR赋能工厂数字升级&#xff0c;打造VR数字工厂&#xff0c;满足各行各业沉浸式营销展示需求。 VR数字工厂是一种全新的工业模式&#xff0c…

【数据结构-队列】双端队列

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

Leangoo领歌scrum敏捷工具全面免费666

​ 转发自&#xff1a;Leangoo.com 尊敬的各位leangoo领歌用户&#xff0c;您好&#xff01; 非常感谢您一直以来对leangoo领歌的支持&#xff0c;我们本着体验第一&#xff0c;用户至上的精神&#xff0c;为用户提供最敏捷的研发管理工具&#xff0c;至今获得了广大用户的一…

【前端开发】JS Vue React中的通用递归函数

文章目录 前言一、递归函数的由来二、功能实现1.后台数据2.处理数据3.整体代码 总结 前言 大家好&#xff0c;今天和大家分享一下在前端开发中js&#xff0c;vue&#xff0c;react的通用递归方法。 递归是指一个函数在执行过程中调用自身的行为。通过递归&#xff0c;可以将一…

特殊矩阵的压缩存储(对称矩阵,三角矩阵和三对角矩阵)

目录 1.对阵矩阵 2.三角矩阵 3.三对角矩阵&#xff08;带状矩阵&#xff09; 均假设数组的下标从0开始 1.对阵矩阵 定义&#xff1a;若对一个n阶矩阵A中的任意一个元素 aᵢ,ⱼ 都有aᵢ,ⱼaⱼ,ᵢ &#xff08;1≤i,j≤n&#xff09;&#xff0c;则称其为对称矩阵。 存储策略…

Cookie、Session和Token三者区别以及各自应用场景

一、三者区别 存储位置&#xff1a;Session和Cookie分别存储在服务器端和客户端&#xff0c;而Token则是在客户端和服务器端之间传递的。安全性&#xff1a;Session相对于Cookie来说更安全&#xff0c;因为Session存储在服务器端&#xff0c;不容易被恶意攻击者获取。而Cookie…

在C/C++中使用vcpkg

文章目录 介绍vcpkg 入门安装vcpkg为您的项目安装库将 vcpkg 与 CMake 结合使用 介绍 如今&#xff0c;现代语言&#xff08;例如Go&#xff09;通常提供集成的包管理来提取库的所有依赖项。然而&#xff0c;许多软件都是用 C/C 创建和维护的&#xff0c;并且没有现成的包管理…

【canal系】canal集群异常Could not find first log file name in binary log index file

这里先说明下这边使用的canal版本号为1.1.5 在描述这个问题之前&#xff0c;首先需要简单对于canal架构有个基本的了解 canal工作原理 canal 模拟 MySQL slave 的交互协议&#xff0c;伪装自己为 MySQL slave &#xff0c;向 MySQL master 发送dump 协议MySQL master 收到 dum…

【精读Uboot】SPL阶段的board_init_r详细分析

对于i.MX平台上的SPL来说&#xff0c;其不会直接跳转到Uboot&#xff0c;而是在SPL阶段借助BOOTROM跳转到ATF&#xff0c;然后再通过ATF跳转到Uboot。 board_init_f会初始化设备相关的硬件&#xff0c;最后进入board_init_r为镜像跳转做准备。下面是board_init_r调用的核心函数…

C++ 多线程

目录 目录 多线程的创建与执行 多线程的互斥 1. 创建一个mutex对象&#xff0c;lock(),unlock() 2. 借助lock_guard 3. unique_lock: lock_guard的升级加强版 多线程的创建与执行 1. C借助标准库<thread>来实现。将函数名放入线程中即可&#xff0c;如果函数有参数…

Element--生成不定列的表格

1、对于一些场景&#xff0c;前端可能需要展示不定列数的数据&#xff1b;譬如考勤&#xff0c;可能有的人是一天一次上下班打卡&#xff0c;有的人是一天两次上下班打卡。这个时候统计就需要更具人员做不同的展示&#xff0c;不能固定在前端写死列的属性。 2、代码示例 &…

Vue + Element UI 前端篇(五):国际化实现

Vue Element UI 实现权限管理系统 前端篇&#xff08;五&#xff09;&#xff1a;国际化实现 国际化支持 1.安装依赖 执行以下命令&#xff0c;安装 i18n 依赖。 yarn add vue-i18n $ yarn add vue-i18n yarn add v1.9.4 warning package-lock.json found. Your project …

重磅:百度李彦宏、中科院曾毅入选,《时代周刊》AI最有影响力100人!

2023年9月8日&#xff0c;《时代周刊》发布了“2023年AI领域最有影响力100人” 榜单。 榜单权威吗&#xff1f; 有必要介绍下《时代周刊》。 《Time》&#xff08;时代周刊&#xff09;,1923年创刊于纽约&#xff0c;是美国公认的最重要的新闻杂志之一。《时代周刊》以报道精彩…

实例 | Python 实现 RSA 加解密

大家好&#xff0c;欢迎来到编程教室 &#xff01; 前阵子看到一篇英文文章[1]&#xff0c;展示了如何用 Python 来实现 RSA 算法。不太熟悉 RSA 的朋友可以看一下一文搞懂 RSA 算法&#xff0c;里面对什么是 RSA&#xff0c;RSA 的数学原理进行了说明&#xff0c;并举了一个简…

CTreeCtrl自绘

CSWTreeCtrl.h&#xff09; #pragma once#define _OWNER_DRAWN_TREE // 自绘CTreeCtrl&#xff0c;可支持背景图片显示功能class CSWTreeCtrl : public CTreeCtrl {DECLARE_DYNAMIC(CSWTreeCtrl)// 成员私有结构定义// 构造/析构函数 public:CSWTreeCtrl();virtual ~CSWTreeC…

pip和conda的环境管理,二者到底应该如何使用

关于pip与conda是否能混用的问题&#xff0c;Anaconda官方早就给出了回答 先说结论&#xff0c;如果conda和pip在相同环境下掺杂使用&#xff0c;尤其是频繁使用这两个工具进行包的安装&#xff0c;可能会导致环境状态混乱 就像其他包管理器一样&#xff0c;大部分这些问题均…