Linux设备驱动开发 - 虚拟时钟Clock驱动示例

news2024/11/14 20:09:48

By: fulinux
E-mail: fulinux@sina.com
Blog: https://blog.csdn.net/fulinus
喜欢的盆友欢迎点赞和订阅!
你的喜欢就是我写作的动力!

在这里插入图片描述

目录

  • 1. 概述
  • 2. virtual clock设计
  • 3. 虚拟时钟驱动
    • 3.1. provider驱动
      • 3.1.1. provider platform device部分
      • 3.1.2. provider platform driver部分
    • 3.2. consumer驱动
      • 3.2.1. consumer platform device驱动
      • 3.2.2. consumer platform driver驱动
  • 4. 结束语

1. 概述

很多设备里面系统时钟架构极其复杂,让学习Clock驱动的盆友头大。这里我参考S3C2440的clock驱动写了一个virtual clock,即虚拟时钟驱动,分别包含clock的provider和consumer。
因为本文驱动示例对环境没有要求,所以本文就是在Ubuntu环境下进行。

2. virtual clock设计

设计一个虚拟的时钟引脚环境,尽量包括晶振osc、锁相环mpll(main pll),分频器divider, 选择器mux和开关gate。这里我们预设osc时钟频率为12MHz,
mpll为400MHz,分频器可以获得100MHz、133Mhz、66MHz、50M。他们的连接方式如下:
在这里插入图片描述
虚拟时钟框架就是这么简单。

3. 虚拟时钟驱动

下一步就是实现虚拟时钟驱动程序,时钟驱动程序分为provider和consumer。以下分别介绍

3.1. provider驱动

provider驱动我们使用platform驱动框架,因此可以分两部分,一个是platform device部分,另一个是platform driver部分;

3.1.1. provider platform device部分

这一部分的代码较为简单如下所示:

//device/vir_clk_device.c 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "vir_clk_device.h"

static struct vir_clk_platform_data vir_clk_info = {
    .data = 0,
    .index = 2333,
};

static void vir_clk_release(struct device *dev)
{
}

static struct platform_device vir_clk_device = {
    .name = "vir_clk",
    .id = -1,
    .dev = {
        .platform_data = &vir_clk_info,
        .release = vir_clk_release,
    }
};

static int __init vir_clk_dev_init(void)
{
    int ret = 0;
    printk("[%s:%d]\n", __func__, __LINE__);
    ret = platform_device_register(&vir_clk_device);
    return ret;
}

static void __exit vir_clk_dev_exit(void)
{
    printk("[%s:%d]\n", __func__, __LINE__);
    platform_device_unregister(&vir_clk_device);
}

module_init(vir_clk_dev_init);
module_exit(vir_clk_dev_exit);
MODULE_DESCRIPTION("virtual clk platform device");
MODULE_LICENSE("GPL");

同时包含一个头文件:

//device/vir_clk_device.h 
#ifndef __VIR_CLK_DEVICE_H__
#define __VIR_CLK_DEVICE_H__

struct vir_clk_platform_data {
    int data;
    int index;
};
#endif

它的Makefile文件如下:

OBJ_NAME := vir_clk_device
TARGET := $(OBJ_NAME).ko
obj-m := $(OBJ_NAME).o
PWD := $(shell pwd)

KERNEL_SOURCE=/lib/modules/$(shell uname -r)/build
#KBUILD_EXTRA_SYMBOLS := $(KDIR)Module.symvers
all:
        $(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules

clean:
        rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers

.PHONY: all clean install

为了方便查看内核信息,我们先讲dmesg信息清空:

sudo dmesg -c

编译和安装驱动:

$ sudo insmod vir_clk_device.ko 

查看下对应的运行信息:

$ dmesg 
[  723.980546] vir_clk_device: loading out-of-tree module taints kernel.
[  723.980574] vir_clk_device: module verification failed: signature and/or required key missing - tainting kernel
[  723.981650] [vir_clk_dev_init:27]

3.1.2. provider platform driver部分

//driver/vir_clk_driver.c 
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>

#include "../device/vir_clk_device.h"

static const char *osc_name[] = {"osc"};
static const char *mpll_p[] = {"div_mpll_4", "div_mpll_3"};
static const char *mpll_name[] = {"mpll"};
static const char *hclk_name[] = {"hclk"};
static unsigned int muxreg = 0;
static void __iomem *muxconf = &muxreg;
static unsigned int divreg = 0;
static void __iomem *divconf = &divreg;
static DEFINE_SPINLOCK(vir_clk_lock);

struct virtual_clock {
    unsigned int endisable;
    struct clk_hw hw;
};

#define to_virtual_clock(_hw) container_of(_hw, struct virtual_clock, hw)

static int vir_clk_register_osc(struct platform_device *pdev)
{
    int ret = 0;
    struct clk *clk;

    clk = clk_register_fixed_rate(&pdev->dev, "osc", NULL, 0, 12000000);
    if (IS_ERR(clk)) {
        printk("[%s:%d] failed to register osc clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
        return -1;
    }

    ret = clk_register_clkdev(clk, "alias_osc", "osc");
    if (ret)
        printk("[%s:%d] failed to register clock lookup for osc\n", __func__, __LINE__);

    return ret;
}

static unsigned long mpll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
    printk("[%s:%d] parent_rate = %ld\n", __func__, __LINE__, parent_rate);

    return 400000000;//400M
}

static int mpll_enable(struct clk_hw *hw)
{
    printk("[%s:%d] enable\n", __func__, __LINE__);
    return 0;
}

static void mpll_disable(struct clk_hw *hw)
{
    printk("[%s:%d] enable\n", __func__, __LINE__);
}

static long mpll_round_rate(struct clk_hw *hw,
        unsigned long drate, unsigned long *prate)
{
    printk("[%s:%d] drate = %ld\n", __func__, __LINE__, drate);

    return drate;
}

static int mpll_set_rate(struct clk_hw *hw,
        unsigned long drate, unsigned long prate)
{
    printk("[%s:%d] drate = %ld, prate = %ld\n", __func__, __LINE__, drate, prate);

    return 0;
}

static const struct clk_ops mpll_clk_ops = {
    .recalc_rate = mpll_recalc_rate,
    .enable     = mpll_enable,
    .disable    = mpll_disable,
    .round_rate = mpll_round_rate,
    .set_rate   = mpll_set_rate,
};

static int vir_clk_register_pll(struct platform_device *pdev)
{
    int ret = 0;
    struct clk *clk;
    struct clk_hw hw;
    struct clk_init_data init;

    init.name = mpll_name[0];
    init.flags = CLK_GET_RATE_NOCACHE;
    init.parent_names = osc_name;
    init.num_parents = 1;
    init.ops = &mpll_clk_ops;

    hw.init = &init;

    clk = clk_register(&pdev->dev, &hw);
    if (IS_ERR(clk)) {
        printk("[%s:%d] mpll: failed to register pll clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
        return -1;
    }

    ret = clk_register_clkdev(clk, "fclk", "mpll");
    if (ret) {
        printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
        return ret;
    }

    return 0;
}

static struct clk_div_table div_mpll_4_d[] = {
    { .val = 0, .div = 4},
    { .val = 1, .div = 8},
    {/* sentinel */ },
};

static struct clk_div_table div_mpll_3_d[] = {
    { .val = 0, .div = 3},
    { .val = 1, .div = 6},
    {/* sentinel */ },
};

static int vir_clk_register_div(struct platform_device *pdev)
{
    int ret;
    struct clk *clk;

    clk = clk_register_divider_table(&pdev->dev,
            "div_mpll_4", "mpll", 0, divconf, 0, 1, 0, div_mpll_4_d, &vir_clk_lock);
    if (IS_ERR(clk)) {
        printk("[%s:%d] failed to register divides clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
        return -1;
    }

    ret = clk_register_clkdev(clk, "div_mpll_4", "div_mpll_4");
    if (ret) {
        printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
        return ret;
    }

    clk = clk_register_divider_table(&pdev->dev,
            "div_mpll_3", "mpll", 0, divconf, 0, 1, 0, div_mpll_3_d, &vir_clk_lock);
    if (IS_ERR(clk)) {
        printk("[%s:%d] failed to register divides clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
        return -1;
    }

    ret = clk_register_clkdev(clk, "div_mpll_4", "div_mpll_3");
    if (ret) {
        printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
        return ret;
    }

    return 0;
}

static int vir_clk_register_muxes(struct platform_device *pdev)
{
    int ret;
    struct clk *clk;

    clk = clk_register_mux(&pdev->dev, hclk_name[0], mpll_p, 2, 0, muxconf, 1, 2, 0, &vir_clk_lock);
    if (IS_ERR(clk)) {
        printk("[%s:%d] failed to register mux clock %ld\n", __func__, __LINE__, PTR_ERR(clk));
        return -1;
    }

    ret = clk_register_clkdev(clk, "alias_muxclk", "muxclk");
    if (ret) {
        printk("[%s:%d] mpll: failed to register lookup for %d\n", __func__, __LINE__, ret);
        return ret;
    }

    return 0;
}

static int virtual_clock_enable(struct clk_hw *hw)
{
    struct virtual_clock *virtual_clock_ptr = to_virtual_clock(hw);
    virtual_clock_ptr->endisable = 1;
    printk("%s %d gate enable\n", __func__, __LINE__);
    return 0;
}

static void virtual_clock_disable(struct clk_hw *hw)
{
    struct virtual_clock *virtual_clock_ptr = to_virtual_clock(hw);
    virtual_clock_ptr->endisable = 0;
    printk("%s %d gate disable\n", __func__, __LINE__);
}

static int virtual_clock_is_enabled(struct clk_hw *hw)
{
    struct virtual_clock *virtual_clock_ptr = to_virtual_clock(hw);
    printk("%s %d gate endisable = %d\n", __func__, __LINE__, virtual_clock_ptr->endisable);
    return virtual_clock_ptr->endisable;
}

static struct clk_ops virtual_clock_ops = {
    .enable = virtual_clock_enable,
    .disable = virtual_clock_disable,
    .is_enabled = virtual_clock_is_enabled,
};

static int vir_clk_register_gate(struct platform_device *pdev)
{
    int ret = 0;
    struct virtual_clock *clk_gate_ptr = NULL;
    struct clk_init_data init_data;
    int gate_flag = 0;
    struct clk *clk;
    struct vir_clk_platform_data *pdata = (struct vir_clk_platform_data *)(pdev->dev.platform_data);

    clk_gate_ptr = devm_kzalloc(&pdev->dev, sizeof(struct virtual_clock), GFP_KERNEL);

    if (!clk_gate_ptr)
        return -ENOMEM;

    printk("%s %d\n", __func__, __LINE__);
    memset(&init_data, 0, sizeof(init_data));

    init_data.parent_names = hclk_name;
    init_data.num_parents = 1;
    init_data.ops = &virtual_clock_ops;
    init_data.name = pdev->name;
    //init_data_flags = CLK_IS_ROOT;

    if (pdata != NULL) {
        clk_gate_ptr->endisable = 0;
        clk_gate_ptr->hw.init = &init_data;

        printk("%s %d\n", __func__, __LINE__);
        if (pdata->data)
            gate_flag = 1;
        else
            gate_flag = 2;
        printk("%s %d gate_flag = %d\n", __func__, __LINE__, gate_flag);
    }

    printk("%s %d gate index = %d\n", __func__, __LINE__, pdata->index);

    clk = devm_clk_register(&pdev->dev, &clk_gate_ptr->hw);
    if (IS_ERR(clk))
        return -EINVAL;

    printk("%s %d\n", __func__, __LINE__);
    if (pdev->dev.of_node != NULL)
        ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
    else
        ret = clk_register_clkdev(clk, "vir_clock", NULL);

    return ret;

}

static int vir_clk_probe(struct platform_device *pdev)
{
    int ret;

    ret = vir_clk_register_osc(pdev);

    printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);

    ret = vir_clk_register_pll(pdev);
    printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);

    ret = vir_clk_register_div(pdev);
    printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);

    ret = vir_clk_register_muxes(pdev);

    printk("[%s:%d] ret = %d\n", __func__, __LINE__, ret);

    ret = vir_clk_register_gate(pdev);
    printk("[%s:%d] ret=%d\n", __func__, __LINE__, ret);

    return 0;
}

static int vir_clk_remove(struct platform_device *pdev)
{
    printk("%s %d\n", __func__, __LINE__);

    return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id of_vir_clk_match[] = {
    { .compatible = "vir_clk", },
    {},
};
#endif

static struct platform_driver vir_clk_driver = {
    .probe      = vir_clk_probe,
    .remove     = vir_clk_remove,
    .driver     = {
        .name   = "vir_clk",
        .owner  = THIS_MODULE,
        .of_match_table = of_match_ptr(of_vir_clk_match),
    },
};

module_platform_driver(vir_clk_driver);

MODULE_AUTHOR("fulinux");
MODULE_DESCRIPTION("virtual clk driver");
MODULE_LICENSE("GPL");

对应的Makefile文件:

OBJ_NAME := vir_clk_driver
TARGET := $(OBJ_NAME).ko
obj-m := $(OBJ_NAME).o
PWD := $(shell pwd)

KERNEL_SOURCE=/lib/modules/$(shell uname -r)/build
#KBUILD_EXTRA_SYMBOLS := $(KDIR)Module.symvers
all:
        $(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules

clean:
        rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers

.PHONY: all clean install

编译和安装驱动:

$ sudo insmod vir_clk_driver.ko 

查看内核打印信息:

[ 1009.333889] [vir_clk_probe:280] ret = 0
[ 1009.333891] [mpll_recalc_rate:59] parent_rate = 12000000
[ 1009.333895] [vir_clk_probe:283] ret = 0
[ 1009.333904] [vir_clk_probe:286] ret = 0
[ 1009.333909] [vir_clk_probe:290] ret = 0
[ 1009.333909] vir_clk_register_gate 237
[ 1009.333910] vir_clk_register_gate 250
[ 1009.333910] vir_clk_register_gate 255 gate_flag = 2
[ 1009.333910] vir_clk_register_gate 258 gate index = 2333
[ 1009.333914] vir_clk_register_gate 264
[ 1009.333914] [vir_clk_probe:293] ret=0

此时可以看到时钟的总览信息:
在这里插入图片描述

3.2. consumer驱动

consumer驱动我们使用platform驱动框架,因此也可以分两部分,一个是platform device部分,另一个是platform driver部分;

3.2.1. consumer platform device驱动

platform device源文件如下:

//device/vir_consumer_device.c 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "vir_consumer_device.h"

static struct vir_clk_consumer_data data_info = {
    .con_id = "vir_clock",
};

static void vir_clk_gate_consumer_release(struct device *dev)
{
}

static struct platform_device vir_clk_gate_consumer_device = {
    .name = "vir_clk_consumer",
    .id = -1,
    .dev = {
        .platform_data = &data_info,
        .release = vir_clk_gate_consumer_release,
    }
};

static int __init vir_clk_gate_consumer_init(void)
{
    int ret = 0;
    printk("%s:%d\n\n", __func__, __LINE__);
    ret = platform_device_register(&vir_clk_gate_consumer_device);
    return ret;
}

static void __exit vir_clk_gate_consumer_exit(void)
{
    printk("%s:%d\n\n", __func__, __LINE__);
    platform_device_unregister(&vir_clk_gate_consumer_device);
}

module_init(vir_clk_gate_consumer_init);
module_exit(vir_clk_gate_consumer_exit);
MODULE_DESCRIPTION("virtual clk gate platform device");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fulinux");

还有一个头文件:

//device/vir_consumer_device.h 
#ifndef __VIR_CONSUMER_DEVICE_H__
#define __VIR_CONSUMER_DEVICE_H__

struct vir_clk_consumer_data {
    const char *con_id;
};

#endif

对应的Makefile文件:

OBJ_NAME := vir_consumer_device
TARGET := $(OBJ_NAME).ko
obj-m := $(OBJ_NAME).o
PWD := $(shell pwd)

KERNEL_SOURCE=/lib/modules/$(shell uname -r)/build
#KBUILD_EXTRA_SYMBOLS := $(KDIR)Module.symvers
all:
        $(MAKE) -C $(KERNEL_SOURCE) M=$(PWD) modules

clean:
        rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions *.order *symvers *Module.markers

.PHONY: all clean install

3.2.2. consumer platform driver驱动

platform driver源文件如下:

//driver/vir_consumer_driver.c 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/clk.h>
#include "../device/vir_consumer_device.h"

struct vir_clk_consumer {
    struct clk *clk;
};

static int vir_clk_consumer_probe(struct platform_device *pdev)
{
    struct vir_clk_consumer_data *pdata = (struct vir_clk_consumer_data *)(pdev->dev.platform_data);
    struct vir_clk_consumer *consumer_ptr = NULL;

    printk("[%s:%d]\n", __func__, __LINE__);
    consumer_ptr = devm_kzalloc(&pdev->dev, sizeof(struct vir_clk_consumer), GFP_KERNEL);

    if (!consumer_ptr)
        return -ENOMEM;

    printk("[%s:%d] clk_get 1\n", __func__, __LINE__);
    consumer_ptr->clk = clk_get(&pdev->dev, pdata->con_id);
    printk("[%s:%d] clk_get 2\n", __func__, __LINE__);
    if (IS_ERR(consumer_ptr->clk)) {
        printk("[%s:%d]\n", __func__, __LINE__);
        return PTR_ERR(consumer_ptr->clk);
    }

    platform_set_drvdata(pdev, consumer_ptr);
    printk("[%s:%d] clk_prepare_enable 1\n", __func__, __LINE__);
    clk_prepare_enable(consumer_ptr->clk);
    printk("[%s:%d] clk_prepare_enable 2\n", __func__, __LINE__);
    return 0;
}

static int vir_clk_consumer_remove(struct platform_device *pdev)
{
    struct vir_clk_consumer *consumer_ptr = platform_get_drvdata(pdev);

    printk("[%s:%d] clk_disable_unprepare 1\n", __func__, __LINE__);
    clk_disable_unprepare(consumer_ptr->clk);
    printk("[%s:%d] clk_disable_unprepare 2\n", __func__, __LINE__);
    printk("[%s:%d] clk_put 1\n", __func__, __LINE__);
    clk_put(consumer_ptr->clk);
    printk("[%s:%d] clk_put 2\n", __func__, __LINE__);
    return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id of_vir_clk_consumer_match[] = {
    { .compatible = "vir_clk_consumer"},
    {},
};
#endif

static struct platform_driver vir_clk_gate_driver = {
    .probe      = vir_clk_consumer_probe,
    .remove     = vir_clk_consumer_remove,
    .driver     = {
            .name   = "vir_clk_consumer",
            .owner  = THIS_MODULE,
            .of_match_table = of_match_ptr(of_vir_clk_consumer_match),
    },
};

module_platform_driver(vir_clk_gate_driver);

MODULE_DESCRIPTION("virtual clk gate platform driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fulinux");

编译后安装:

sudo insmod vir_consumer_driver.ko 

查看内核dmesg信息:

[ 5653.909066] [vir_clk_consumer_probe:18]
[ 5653.909067] [vir_clk_consumer_probe:24] clk_get 1
[ 5653.909068] [vir_clk_consumer_probe:26] clk_get 2
[ 5653.909069] [vir_clk_consumer_probe:33] clk_prepare_enable 1
[ 5653.909071] [mpll_enable:66] enable
[ 5653.909071] [virtual_clock_enable:199] gate enable
[ 5653.909072] [vir_clk_consumer_probe:35] clk_prepare_enable 2

在这里插入图片描述
同时我们查看下clock的总览信息:
在这里插入图片描述
可以看到这里除了div_mpll3没有被使用之外,其他几个都被prepare和enable了。而且也能够看到频率的依赖关系。

4. 结束语

最终的文件和目录结构:
在这里插入图片描述

需要注意:
因为clock provider驱动框架没有提供释放的api接口函数,所以这里如果驱动出现异常或者修改,都需要重启系统方可释放时钟资源。

参考:https://jerry-cheng.blog.csdn.net/article/details/107804007

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

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

相关文章

645. 错误的集合|||697. 数组的度|||448. 找到所有数组中消失的数字

645. 错误的集合 题目 集合 s 包含从 1 到 n 的整数。不幸的是&#xff0c;因为数据错误&#xff0c;导致集合里面某一个数字复制了成了集合里面的另外一个数字的值&#xff0c;导致集合 丢失了一个数字 并且 有一个数字重复 。 给定一个数组 nums 代表了集合 S 发生错误后的…

JVM系列(七) JVM 垃圾收集器

我们知道JVM会回收垃圾,但是每种垃圾收集器的收集机制和收集的方法都不一样,今天我们讨论下几种垃圾回收机制 1.按照垃圾区域划分垃圾收集器 我们可以按照垃圾存在的区域来划分垃圾收集器,垃圾在堆内的区域分为 新生代垃圾老年代垃圾新生代老年代混合垃圾 按照这三种区域类…

一个Linux驱动工程师必知的内核模块知识

最简单的驱动 #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h>static int __init my_init(void) {printk("my_init\n");return 0; }static void __exit my_exit(void) {printk("my_exit\n"); }module_in…

数据结构总结——Java

1 链表(Linked List) 1.1 单项链表(Singly Linked List) 1.1.1 图例 1.1.2 Java实现 public class ListNode {// 保存值int val;// 保存指针ListNode next;// 构造函数们public ListNode() {}public ListNode(int val) {this.val val;}public ListNode(int val, ListNode n…

Linux基础命令-scp远程复制文件

Linux基础命令-seq打印数字序列 前言 有时候不可避免的需要将文件复制到另外一台服务器上&#xff0c;那么这时就可以使用scp命令远程拷贝文件&#xff0c;scp命令是基于SSH协议&#xff0c;在复制的过程中数据都是加密过的&#xff0c;会比明文传输更为安全。 一.命令介绍 …

Vue ElementUI Axios 前后端案例(day02) 之 ElementUI

ElementUI Element&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库 组件 1.Layout 布局 通过基础的 24 分栏&#xff0c;迅速简便地创建布局。 就是这样分了24个格子 基础布局 使用单一分栏创建基础的栅格布局。 通过 row 和 col 组件&…

keepalived+nginx安装

欢迎使用ShowDoc&#xff01; 1、安装基础包&#xff1a; yum -y install libnl libnl-devel 2、上传包&#xff1a; tar -zxvf keepalived-2.0.20.tar.gz -C /data/imas/base_soft mkdir -p /data/imas/base_soft/keepalived cd /data/imas/base_soft/keepalived-2.0.20 .…

基于Netty开发IM即时通讯之群聊功能

本篇涉及的群聊核心功能&#xff0c;大致如下所示&#xff1a; 1&#xff09;登录&#xff1a;每个客户端连接服务端的时候&#xff0c;都需要输入自己的账号信息&#xff0c;以便和连接通道进行绑定&#xff1b;2&#xff09;创建群组&#xff1a;输入群组 ID 和群组名称进行…

【云原生进阶之容器】第六章容器网络6.5.2--Calico网络架构详述

《云原生进阶之容器》专题索引: 第一章Docker核心技术1.1节——Docker综述第一章Docker核心技术1.2节——Linux容器LXC第一章Docker核心技术1.3节——命名空间Namespace第一章Docker核心技术1.4节——chroot技术第一章Docker核心技术1.5.1节——cgroup综述

从 Dev 和 Ops 视角出发,聊聊 DevSecOps 的 What / Why / How

近日&#xff0c;极小狐和 TA 的朋友们相聚上海&#xff0c;开展了一场技术 Meetup&#xff0c;从 DevSecOps 的 What、Why、How 出发&#xff0c;通过分享真实应用案例&#xff0c;与参会者交流 DevSecOps 的实践过程和落地经验。 本文整理自极狐(GitLab) 资深云原生架构师郭旭…

爬虫日常-selenium登录12306,绕过验证

文章目录 前言代码设计 前言 hello兄弟们&#xff0c;这里是无聊的网友。愉快的周末过去了&#xff0c;欢迎回到学习频道。书接上文&#xff0c;我们说到了再用selenium登录12306时遇到了滑块验证的问题。当前的网站几乎每家都会在登录模块添加一个认证&#xff0c;来规避各种…

js 同步与异步

一、js 执行机制 JavaScript语言的一大特点就是单线程&#xff0c;即&#xff08;同一时间只能做一件事情&#xff09;。因为JavaScript是为了处理页面中用户的交互&#xff0c;以及操作DOM而诞生的。比如对某个DOM元素进行添加和删除操作。不能同时进行&#xff0c;应该先进行…

一文带你通俗理解23种软件设计模式(推荐收藏,适合小白学习,附带C++例程完整源码)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、设计模式是什么&#xff1f; 设计模式是为了解决在软件开发过程中遇到的某些问题而形成的思想。同一场景有多种设计模式可以应…

第18章_JDK8-17新特性(上)

第18章_JDK8-17新特性&#xff08;上&#xff09; 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. Java版本迭代概述 1.1 发布特点&#xff08;小步快跑&#xff0c;快速迭代…

安装TortoiseGit后桌面文件夹和用户文件夹中显示红色叹号

✨ TortoiseGit作为一个很好用的git图形化工具&#xff0c;能够很方便的进行版本控制。但在安装这个软件之后就遇到了一个问题。 &#x1f440;问题描述&#xff1a;我们知道&#xff0c;安装TortoiseGit之后,当自己版本库中文件发生更改&#xff0c;但还没有提交到受控库时&am…

信息系统管理工程师好考吗?如何去备考呢?

信息系统管理工程师有点难度&#xff0c;侧重于IT技术的中级资格考试&#xff0c;主要适合系统管理员等专业技术人员去考 一、信息系统管理工程师考试介绍&#xff1a; 考试科目有两科&#xff0c;且成绩不延续&#xff0c;考试都合格后的证书是永久有效的。 报考条件&#x…

文件改名,如何将文件复制到指定文件夹里,并设置自动编号同名文件

在工作中&#xff0c;我们经常会遇到要将文件进行备份的时候&#xff0c;那么文件名称相同的情况下要如何批量备份呢&#xff1f;又如何自动编号同名文件&#xff1f;今天小编就给大家分享一下我的操作办法。 首先&#xff0c;第一步我们要进入“文件批量改名高手”的主页面并…

pot lib:optimal transport python库

文章目录 transport1. [计算最优传输&#xff08;Computational Optimal Transport&#xff09;](https://zhuanlan.zhihu.com/p/94978686)2. 离散测度 (Discrete measures), 蒙日(Monge)问题, Kantorovich Relaxation (松弛的蒙日问题)3. scipy.stats.wasserstein_distance 距…

CVPR2023活体检测Instance-Aware Domain Generalization for Face Anti-Spoofing学习笔记

论文链接&#xff1a;https://arxiv.org/pdf/2304.05640.pdf 代码链接&#xff1a;GitHub - qianyuzqy/IADG: (CVPR 2023) Instance-Aware Domain Generalization for Face Anti-Spoofing&#xff08;尚未公布&#xff09; 研究动机 此前的基于域泛化&#xff08;domain gen…

信号完整性分析:关于传输线的三十个问题解答(二)

11.对于 50 欧姆带状线的纵横比&#xff0c;什么是好的经验法则&#xff1f;(What is a good rule of thumb for the aspect ratio of a 50-Ohm stripline?) 在带状线几何形状和 FR4 基板中&#xff0c;线宽和平面之间的电介质间距的纵横比为 。由于有两个平面&#xff0c;带…