Linux驱动开发(速记版)--热插拔

news2024/10/5 22:51:40

第九十六章 热插拔简介

        热插拔是指在设备运行时安全地插入或拔出硬件,无需关闭或重启系统。

        它提供了方便性和灵活性,允许快速更换或添加硬件而无需中断任务。

        以下是一些应用场景及支持热插拔所需的条件:

应用场景:

        USB设备(鼠标、键盘、打印机、存储设备等)。

        硬盘驱动器(扩展存储容量或替换故障驱动器)。

        扩展卡(显卡、网卡、声卡等)。

支持条件:

        硬件接口设计需支持热插拔。

        系统需有相应的驱动程序和管理功能。

热插拔机制:

        内核与用户空间通过用户空间程序(如hotplug、udev、mdev)交互。

        Linux内核支持USB、PCI、CPU等部件的动态插入和拔出。

设备文件系统:

        devfs(已过时):基于内核的动态设备文件系统,从Linux 2.6.13版本开始被移除。

        mdev:轻量级热插拔设备文件系统,使用uevent_helper机制。        

        udev:广泛使用的热插拔设备文件系统,基于netlink机制监听uevent动态创建和管理设备节点

        udev是目前应用最广泛的设备文件系统,提供丰富的配置选项,

        mdev主要用于嵌入式系统,提供轻量级设备管理功能。

        这些设备文件系统管理设备文件,使用户空间程序能够与底层硬件交互。

——————————————————————————————————————————

        在Linux系统中,udev(Userspace DEV)是一个设备管理器,它负责在内核识别到新设备时,动态地管理这些设备的节点(即设备文件,通常位于/dev目录下)

        当你使用命令udevadm monitor &在后台启动udevadm工具的监视模式时,它会实时显示系统中发生的uevent(用户空间事件)事件。

        这些事件通常与设备的添加、移除、更改等状态变化相关。

        在驱动加载之后,如果udev接收到一个add动作,这意味着内核已经识别到了一个新设备,并且已经发送了一个uevent事件来通知用户空间。这个add动作是udev规则处理流程的一部分,它告诉udev需要为这个新设备创建或更新设备节点。

第九十七章 内核如何发送事件到用户空间

97.1 相关接口函数

   kobject_uevent() 用于生成和发送 uevent 事件,实现内核与用户空间(如 udev)的通信。

/*生成和发送uevent事件*/ 
int kobject_uevent(struct kobject *kobj, //kobj对象
            enum kobject_action action); //事件类型,如添加、移除、属性变化等。

/*常见 action 参数包括:
KOBJ_ADD:设备添加。
KOBJ_REMOVE:设备移除。
KOBJ_CHANGE:设备属性变化。
KOBJ_MOVE:设备移动。
KOBJ_ONLINE:设备上线。
KOBJ_OFFLINE:设备离线。
KOBJ_BIND / KOBJ_UNBIND:设备绑定/解绑。*/

97.2 udevadm 命令

        udevadm 是 Linux 中与 udev 设备管理器交互的工具,

        常用子命令包括:

        udevadm info:       获取设备详细信息。

        udevadm monitor: 监视系统中的 uevent 事件。

        udevadm trigger:  手动触发设备事件。

        udevadm settle:    等待 udev 处理所有事件。

        udevadm control:  控制 udev 守护进程行为。 

        udevadm test:测试 udev 规则匹配。

udevadm info --query=all --name=/dev/sdX     //获取设备详细信息
udevadm monitor --udev                       //监视系统中的uevent事件
udevadm trigger --action=add --path=/dev/sdX //手动触发设备事件
udevadm settle                               //等待udev处理所有的事件
udevadm control --reload-rules               //控制udev守护进程的行为
udevadm test /dev/sdX                        //测试udev规则匹配

        在 iTOP-RK3568 开发板上烧写 buildroot 系统,输入“udevadm monitor &”可以监视和显示当前系统中的 uevent 事件,在之后的实验中,我们要使用这个方法。

监视和显示当前系统中的uevent事件
struct kobject *mykobject01;
struct kset *mykset;
struct kobj_type mytype;

// 模块的初始化函数
static int mykobj_init(void)
{
    int ret;

    // 创建并添加一个 kset
    mykset = kset_create_and_add("mykset", NULL, NULL);

    // 分配并初始化一个 kobject
    mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    mykobject01->kset = mykset;

    // 初始化并添加 kobject 到内核
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");

    // 触发一个 uevent 事件,表示 kobject 的属性发生了变化
    ret = kobject_uevent(mykobject01, KOBJ_CHANGE);
    return 0;
}

// 模块退出函数
static void mykobj_exit(void)
{
    // 释放 kobject
    kobject_put(mykobject01);
}

        开发板启动之后,使用命令“udevadm monitor &”监视和显示当前系统中的 uevent 事件。

        然后使用 insmode加载模块。

        驱动加载之后,如图所示 udev 接收到 change 动作,说明 uevent 事件已经发送成功了。

        /mykset/mykobject01 是 kobject 在根目录/sys/下的路径。

   同时,需要注意,如果没有正确创建 kset会导致 uevent事件无法被正确发送到用户空间

97.3 kobject_uevent发送通知流程

        在Linux内核中,kobject_uevent()函数用于通知用户空间发生了某个事件

        这个函数依赖于kobject(内核对象)所属的 kset(内核对象集合)来正确发送事件。

1、        kobject_uevent() 函数用于通知用户空间uevent事件的发生。

2、       调用 kobject_uevent_env(),并传递NULL作为环境变量。

/*通知用户空间 uevent事件的发生*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action)  
{  
    return kobject_uevent_env(kobj, action, NULL);  
}

        kobject_uevent_env() 函数:

        查找kobject所属的kset

        如果没有找到kset,则无法发送事件,返回错误

3、检查是否跳过事件:

        如果设置了 kobj->uevent_suppress,则跳过事件。

        如果存在 uevent_ops->filter 且返回false,则跳过事件。

4、获取子系统名称:

        尝试从uevent_ops->name获取子系统名称,否则使用 kset的名称

5、准备并发送事件:

        分配并填充 kobj_uevent_env结构体。

struct kobj_uevent_env {  
    char *argv[3];       /* 用户空间可执行文件的路径及其参数 */  
    char *envp[UEVENT_NUM_ENVP]; /* 环境变量的地址数组 */  
    int envp_idx;        /* 已使用的环境变量数量 */  
    char buf[UEVENT_BUFFER_SIZE]; /* 存储环境变量内容的缓冲区 */  
    int buflen;          /* 缓冲区中已使用的长度或即将存储字符串的位置 */  
};
/*当内核中的设备状态发生变化时(如设备添加、移除、更改等),
内核会通过 uevent 机制向用户空间发送通知。
此时,kobj_uevent_env 结构体被用来封装与事件相关的环境变量和参数。*/
//用户空间的应用程序可以监听这些 uevent 事件,并根据事件中的环境变量和参数来执行相应的操作。

        添加标准环境变量如 ACTION, DEVPATH, SUBSYSTEM。

        调用 kobject_uevent_net_broadcast()在系统中的所有网络命名空间中广播事件。

6、调用用户空间的 uevent_helper(如果配置):

        在早期启动阶段,调用用户空间的程序来处理事件。

        如果kobject没有所属的kset,内核无法正确设置事件的环境变量,也无法找到适当的子系统名称,因此无法有效地通知用户空间。

        这就是为什么没有创建kset会导致用户空间无法接收事件的原因。

第九十八章 完善 kset_uevent_ops 结构体

struct kobject *mykobject01;
struct kobject *mykobject02;
struct kset *mykset;
struct kobj_type mytype;
// 定义一个回调函数,返回 kset 的名称
const char *myname(struct kset *kset, struct kobject *kobj)
{
    return "my_kset";
};

// 定义一个回调函数,处理 kset 的 uevent 事件
int myevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)
{
    //向设备事件相关的环境变量中添加一个新的变量
    add_uevent_var(env, "MYDEVICE=%s", "TOPEET");
    return 0;
};

// 定义一个回调函数,用于过滤 kset 中的 kobject
int myfilter(struct kset *kset, struct kobject *kobj)
{
    if (strcmp(kobj->name, "mykobject01") == 0){
        return 0; // 返回 0 表示通过过滤
    }else{
        return 1; // 返回 1 表示过滤掉
    }
};

/*填充 kset_uevent_opsm,包括过滤函数、uevent函数、和name函数*/
struct kset_uevent_ops my_uevent_ops = {
    .filter = myfilter,
    .uevent = myevent,
    .name = myname, };

// 模块的初始化函数
static int mykobj_init(void)
{
    int ret;

    // 创建并添加一个 kset
    mykset = kset_create_and_add("mykset", &my_uevent_ops, NULL);

    // 分配并初始化一个 kobject
    mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    mykobject01->kset = mykset;

    // 初始化并添加 kobject 到 kset
    ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");

    // 分配并初始化一个 kobject
    mykobject02 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    mykobject02->kset = mykset;

    // 初始化并添加 kobject 到 kset
    ret = kobject_init_and_add(mykobject02, &mytype, NULL, "%s", "mykobject02");

    // 触发一个 uevent 事件,表示 mykobject01 的属性发生了变化
    ret = kobject_uevent(mykobject01, KOBJ_CHANGE);

    // 触发一个 uevent 事件,表示 mykobject02 被添加
    ret = kobject_uevent(mykobject02, KOBJ_ADD);
    return 0;
}

// 模块退出函数
static void mykobj_exit(void)
{
    // 释放 kobject
    kobject_put(mykobject01);
    kobject_put(mykobject02);
    kset_unregister(mykset);
}

        开发板启动之后,使用命令“udevadm monitor &”监视和显示当前系统中的 uevent 事件。

        驱动加载之后,如下图所示 udev 接收到 add 动作,说明 uevent 事件已经发送成功了。

        /mykset/mykobject02 是 kobject 在根目录/sys/下的路径。

        由于 kset_uevent_ops.filter过滤了 kobj01因此kobj01发送事件不成功。

第九十九章 netlink 监听广播信息

99.1 netlink 机制介绍

Netlink 是 Linux 内核与用户空间之间的一种双工通信机制基于 socket,具备以下特点:

        双工通信:内核与用户空间可双向通信。

        可靠性:通过确认和重传机制保证消息可靠传递。

        异步通信:双方独立发送和接收消息,无需同步等待。

        多播支持:可向多个进程或套接字广播消息。

        有序传输:保证消息按发送顺序接收。

Netlink 常见应用包括:

        系统管理工具:如 ifconfigip 工具,用于获取和配置网络接口信息。

        进程间通信:实现跨进程数据交换和协调。

        内核模块与用户空间通信:内核模块向用户空间发送通知或接收指令。

99.2 netlink 的使用

99.2.1 创建 socket

        在 Linux 中,创建套接字是网络编程的首要步骤,它充当了应用程序与网络间的桥梁。

        要创建 Netlink 套接字,需包含头文件 <sys/types.h> 和 <sys/socket.h>,并

        使用 socket() 系统调用创建 Netlink 套接字。

        协议族:设为 AF_NETLINK,指定使用 Netlink 协议族,用于内核与用户空间通信。

        套接字类型:选择 SOCK_RAW,表示创建原始套接字,直接访问 Netlink 底层协议。

        协议类型:指定为 NETLINK_KOBJECT_UEVENT,用于接收内核对象事件通知。

#include <sys/types.h>  
#include <sys/socket.h>  
  
/*创建socket套接字,使用socket系统调用*/
int socket_fd = socket(AF_NETLINK, //协议族,netlink协议族
                         SOCK_RAW, //套接字类型,原始套接字
           NETLINK_KOBJECT_UEVENT);//协议类型,netlink_kobject_uevent类型,
                                   //用于接收内核对象事件通知

        此代码创建了一个 Netlink 套接字,用于接收内核中的 kobject 对象变化事件通知

99.2.2 绑定套接字

        在 Linux 中,创建套接字后通过 bind() 系统调用将其与地址绑定,以便接收网络事件。

        对于 Netlink 套接字,使用 sockaddr_nl 结构体指定地址信息。

#include <sys/types.h>  
#include <sys/socket.h>  
#include <string.h> // for bzero  
  
int socket_fd = /* ... socket() call ... */;  
  
struct sockaddr_nl nl;  
memset(&nl, 0, sizeof(nl)); // 初始化结构体为0  
nl.nl_family = AF_NETLINK;            //协议族,netlink协议族
nl.nl_pid = 0;                        // 0代表广播给所有进程,或设为当前进程PID  
nl.nl_groups = 1;                     // 1代表接收基本组事件  
  
int ret = bind(socket_fd, (struct sockaddr *)&nl, sizeof(nl));  
if (ret < 0) {  
    perror("bind error");  
    return -1;  
}

        这里的“基本组”是一个特定的多播组,它通常包含了内核生成的一些基础或通用的事件。 

99.2.3 接收数据

        Netlink 套接字接收数据时无需调用 listen,直接使用 recv() 函数即可

#include <sys/types.h>  
#include <sys/socket.h>  
  
ssize_t recv(int sockfd,    //套接字描述符
              void *buf,    //数据缓冲区指针
             size_t len,    //数据缓冲区长度
              int flags);   //接收操作的行为
/*
MSG_PEEK:查看数据但不从缓冲区移除
MSG_WAITALL:等待直到接收到完整的数据长度
MSG_OOB: 接受紧急带外数据
MSG_DONTWAIT:调用完立刻返回,不阻塞
0意味着使用该函数的默认行为,即阻塞接收、尝试完整读取、数据移除以及无特殊处理。
*/
#include <sys/types.h>  
#include <sys/socket.h>  
#include <string.h> // for bzero  
#include <stdio.h>  // for printf  
#include <errno.h>  // for errno  
  
int socket_fd = /* ... Netlink socket creation and setup ... */;  
char buf[4096];  
  
while (1) {  
    memset(buf, 0, sizeof(buf)); // 初始化缓冲区  
    ssize_t len = recv(socket_fd, buf, sizeof(buf), 0);  
    if (len > 0) {  
        // 替换 '\0' 为 '\n' 并打印接收到的数据  
        for (ssize_t i = 0; i < len; i++) {  
            if (buf[i] == '\0') {  
                buf[i] = '\n';  
            }  
        }  
        printf("%s", buf);  
    } else if (len == 0) {  
        // 连接关闭(对 Netlink 套接字不常见)  
        printf("Connection closed\n");  
        break;  
    } else {  
        // 错误发生  
        perror("recv error");  
        break;  
    }  
}

        编译完后生成可执行程序,当驱动模块通过 kobject_uevent()发送 uevent事件时,可执行程序可以接收到 uevent事件执行相应处理。

第一百章 uevent_helper 

        前面我们介绍了通过 kobject_uevent()广播内核事件的方法,

        下面我们介绍通过可执行程序发送事件到用户空间的方法。

        uevent_helper 是 Linux 内核中用于处理热插拔(hotplug)事件的一个机制。

        当内核检测到设备连接或断开等事件时,会生成一个 uevent(用户空间事件),并通过 uevent_helper 指定的程序来处理这个事件。

100.1 设置 uevent_helper

        kobject_uevent()经历了查找kset、处理 filter、处理name、处理kobject_uevent_env结构体,

        调用kobject_uevent_net_broadcast()在网络空间中广播事件。

        在Linux内核中,uevent_helper用于在系统启动时处理设备事件

        它的路径可以在内核配置时通过CONFIG_UEVENT_HELPER_PATH宏定义,但通常这个宏是空的,需要在其他地方设置。

        例如可以在内核源码的 menuconfnig界面配置支持 uevent_helper()。

        在上面的配置 1 中设置了 uevent helper 和相对应的路径,这就是配置方法 1,但是这种方式需要重新编译内核,使用起来较为麻烦。其他配置方法请参考开发文档,此处不赘述。

uevent_helper的定义和存储

char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
//CONFIG_UEVENT_HELPER_PATH通常为空,所以需要在运行时设置。

sysfs接口(kernel/ksysfs.c)

        提供了 uevent_helper的读写接口。

#ifdef CONFIG_UEVENT_HELPER  // 如果定义了CONFIG_UEVENT_HELPER宏  
  
// 显示uevent_helper的路径  
static ssize_t uevent_helper_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)    
{    
    // 将uevent_helper的路径复制到buf中,并返回复制的字节数  
    return sprintf(buf, "%s\n", uevent_helper);    
}    
  
// 设置uevent_helper的路径  
static ssize_t uevent_helper_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)    
{    
    // 如果buf的长度超过最大允许长度,则返回错误  
    if (count + 1 > UEVENT_HELPER_PATH_LEN) return -ENOENT;    
    // 将buf的内容复制到uevent_helper中,并添加字符串结束符'\0'  
    memcpy(uevent_helper, buf, count);    
    uevent_helper[count] = '\0';    
    // 如果buf的最后一个字符是换行符,则将其替换为字符串结束符'\0'  
    if (count && uevent_helper[count - 1] == '\n') uevent_helper[count - 1] = '\0';    
    // 返回成功复制的字节数  
    return count;    
}    
  
// 定义一个可读写的内核属性,属性名为"uevent_helper"  
KERNEL_ATTR_RW(uevent_helper);    
  
#endif // 结束宏定义的检查

proc接口(kernel/sysctl.c)

        在/proc/sys/kernel/下提供了一个名为 hotplug的文件,用于设置uevent_helper。

#ifdef CONFIG_UEVENT_HELPER  // 如果定义了CONFIG_UEVENT_HELPER宏  
  
// 定义一个与/proc文件系统交互的内核参数结构体  
{    
    // 参数在/proc文件系统中的名称,用户空间可以通过/proc/hotplug访问  
    .procname = "hotplug",  
      
    // 指向要操作的数据的指针,这里是uevent_helper的路径  
    .data = &uevent_helper,

    // 允许的最大长度,确保不会超出uevent_helper的存储空间  
    .maxlen = UEVENT_HELPER_PATH_LEN, 

     // 文件的权限,0644表示所有者可以读写,组和其他用户只能读  
    .mode = 0644,     
    
    // 处理读写请求的函数,proc_dostring用于处理字符串类型的参数         
    .proc_handler = proc_dostring, 
}  
  
#endif // 结束宏定义的检查

一百零一章 使用 udev 挂载 U 盘和 T 卡实验

101.1 配置 buildroot 文件系统支持 udev

         烧写buildroot文件系统镜像后,可通过

ps -aux | grep -nR udev

ps //显示当前系统中的进程状态
-a //显示所有用户的进程
u  //以用户为中心的格式显示进程信息
x  //显示没有控制中断的进程

|  //管道操作符   前一个命令的输出作为后一个命令的输入
grep //正则表达式搜索字符串 ,在管道操作符后用于过滤前一个命令输出的内容
-n  // 显示匹配行的行号
r   // 递归搜索
udev //搜索包含 'udev'字符串的内容

        查看 udev是否已支持。

         检查到/sbin/udevd 进程就表示当前系统使用的是 udev。

101.2 使用 udev 挂载 U 盘

        在上一小节中配置 buildroot 使能了 udev,而要想使用 udev 来实现 U 盘的自动挂载,还需在开发板的/etc/udev/rules.d 目录下创建相应的规则文件

        (/etc/udev/rules.d 目录不存在可以手动创建,一般都已经存在了),

        这里我们创建一个名为 001.rules 的文件,

         然后向该文件中添加以下内容:

/*这段配置是用于 Linux 系统中的 udev 规则,
它定义了当特定类型的设备被添加到系统或从系统中移除时应该执行哪些脚本。
udev 是 Linux 内核的设备管理器,
负责在设备连接或断开时动态地管理设备节点*/

/*指定了设备内核名称的匹配模式*/
KERNEL=="sd[a-z][0-9]",   //sd开头,后面跟一个a~z,再跟一个0~9

SUBSYSTEM=="block",       //设备必须属于block子系统.
                          //block 子系统包含所有块设备,这些设备以块为单位读写数据,
                          //如硬盘、SSD、USB 存储设备等。

/*当设备被添加到系统时触发规则。*/
ACTION=="add",            //add 动作通常发生在设备首次连接到系统时

/*这部分指定了当上述条件都满足时应该执行的脚本*/
RUN+="/etc/udev/rules.d/usb/usb-add.sh %k"  //RUN+= 表示将指定的命令添加到已有的 RUN 列表中
                                            //%k 是一个占位符,它会被替换为设备的内核名称(如 sda),
                                            //这个名称随后被传递给 usb-add.sh 脚本。

/*下面是另一个规则*/
SUBSYSTEM=="block",             //设备类型
ACTION=="remove",               //发生时机 
RUN+="/etc/udev/rules.d/usb/usb-remove.sh //要执行的脚本

        确保你的脚本 /etc/udev/rules.d/usb/usb-add.sh 和 /etc/udev/rules.d/usb/usb-remove.sh 是可执行的,并且具有适当的权限来执行它们需要完成的任务(如挂载和卸载文件系统)。

        可以注意到当块设备被添加的时候会执行/etc/udev/rules.d/usb/usb-add.sh 脚本,块设备被删除的时候会执行/etc/udev/rules.d/usb/usb-remove.sh 脚本。

        所以接下来我们要完善这两个脚本内容,首先在 /etc/udev/rules.d/目录下创建名为 usb 的文件夹,并在这个创建 usb-add.sh 和 usb-remove.sh 脚本。

//usb-add.sh

//这是脚本的 shebang(或 hashbang、pound bang)行,它告诉系统这个脚本应该使用哪个解释器来执行。
#!/bin/sh                        

//这行是脚本的主要命令,它调用 mount 程序来挂载一个文件系统。
/bin/mount -t vfat /dev/$1 /mnt

/*
/bin/mount 这是mount程序的路径
-t vfat    指定了要挂载的文件系统类型
/dev/$1    是要挂载的设备的路径,其中 $1 是脚本的第一个参数。
           当脚本被调用时,你应该传递一个设备名称(如 sda1),
           它会被替换到 /dev/ 后面形成完整的设备路径(如 /dev/sda1)。
/mnt       是挂载点的路径,即设备内容将被访问的目录。
*/
//usb-remove.sh

#!/bin/sh
sync                    //sync 命令用于将所有未写入磁盘的缓存数据刷新到磁盘上
/bin/umount -l /mnt

        添加完成之后还需要使用 chmod 命令赋予两个脚本的可执行权限。

        至此关于 udev 自动挂载 U 盘的相关配置文件完成了。     

        首先输入以下 df 命令查看当前的挂载情况,

        可以看到当前并没有关于 U 盘相关的挂载信息,然后插入 U 盘,相关打印如下,

        然后重新使用 df 命令查看当前的挂载情况,可以看到 U 盘 sda1 就成功挂载到了/mnt 目录,然后拔掉 U 盘,重新使用 df 命令查看当前挂载情况,会发现/dev/sda1 设备已经消失了。

        使用 udev 实现 TF 卡的自动挂载类似,将 TF 卡挂载到/mnt 目录下,在不做任何修改的情况下,直接插入 TF 卡,会发现 TF 卡直接挂载到了/mnt/sdcard 目录,这是因为在/lib/udev/rules.d 目录下已经帮我们添加了很多的 udev 规则,这里的规则文件跟我们前面自己创建的规则文件所实现的作用是相同的,只是 /etc/udev/rules.d/目录的规则文件比/lib/udev/rules.d 目录的规则文件优先级高。

        使用要 使用 mdev设备管理器挂载u盘或者 tf卡等其他设备,也需要buildroot支持mdev,其他地方则是规则文件的路径不同。

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

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

相关文章

python中,try-except捕获异常的意义(通过ai智库学习)

python中&#xff0c;不但可以用try-except捕获异常&#xff0c; 还可以自定义异常提示字符串&#xff0c;更可以自定义捕获异常后的处置。 (笔记模板由python脚本于2024年10月03日 06:47:06创建&#xff0c;本篇笔记适合喜欢研究python的coder翻阅) 【学习的细节是欢悦的历程】…

交叠型双重差分法

交叠型双重差分法&#xff08;Staggered Difference-in-Differences, Staggered DiD&#xff09;是一种扩展的双重差分&#xff08;Difference-in-Differences, DiD&#xff09;方法&#xff0c;用于处理多个时间点的政策干预或处理组&#xff08;treatment group&#xff09;并…

每日读则推(四)

Whats this...? | An invitation letter n.邀请函 n.邀请(invite v.邀请) Can a tool grasp the meaning in a song? v. 握紧,理解 n.紧握,理解(力) Can it feel the melody, where emotions belong? …

C++ union的运用

// // Created by 徐昌真 on 2024/10/5. // #include <iostream> #include <cstring> using namespace std;//定义一个结构体(类) struct Info{char _name[20];int _role; //老师是0 同学是1union { //用union存放score和course 节省内存int score;char course[2…

全球十大独角兽(完整榜单),你猜中国占几席?

全球十大独角兽 10月3日&#xff0c;OpenAI 宣布已完成 66 亿美元融资&#xff0c;估值达 1570 亿美元&#xff0c;成为全球第三的独角兽。 给新来的读者重温一下"独角兽"的定义&#xff1a;估值超过10亿美元的未上市企业。 你可能会好奇&#xff0c;OpenAI 是第三&a…

Linux·进程概念(下)

1. 进程优先级 优先级就是获得某种资源的先后顺序&#xff0c;因为CPU资源是有限的&#xff0c;因此各个进程之间要去争取CPU的资源。 那么针对Linux操作系统下的PCB中&#xff0c;也就是task_struct结构体中&#xff0c;使用了int类型的变量记录了每个进程的优先级属性&#x…

WIFI网速不够是不是光猫的“路由模式”和“桥接模式”配置错了?

光猫&#xff08;光纤调制解调器&#xff09;是一种用于将光纤信号转换为数字信号的设备&#xff0c;通常用于家庭或企业网络中。光猫可以在不同的工作模式下运行&#xff0c;其中最常见的两种模式是“路由模式”和“桥接模式”。以下是这两种模式的详细解释及其优缺点。 一、路…

python实现单例模式的常用三种方法-基于__new__/使用装饰器以及Python中的值类型、引用类型以及类的静态变量、读取进程和线程ID

一、python实现单例模式的常用三种方法-基于__new__,使用装饰器 涉及到类的使用就会有类的实例化&#xff0c;就会有类单例实现的需求&#xff0c;因为重复实例化会浪费资源。python中的单例模式与别的语言相比&#xff0c;单例实现的方法更丰富。虽然python实现单例的模式的方…

MobaXterm使用

Linux连接工具MobaXterm详细使用教程-CSDN博客

Elasticsearch学习笔记(五)Elastic stack安全配置二

一、手动配置http层SSL 通过前面的配置&#xff0c;我们为集群传输层手动配置了TLS&#xff0c;集群内部节点之间的通信使用手动配置的证书进行加密&#xff0c;但是集群与外部客户端的http层目前还是使用的自动配置&#xff0c;集群中HTTP的通信目前仍然使用自动生成的证书ht…

【韩顺平Java笔记】第7章:面向对象编程(基础部分)【227-261】

文章目录 227. 重载介绍228. 重载快速入门229. 重载使用细节230. 重载课堂练习1231. 232. 重载课堂练习2,3233. 可变参数使用233.1 基本概念233.2 基本语法233.3 快速入门案例 234. 可变参数细节235. 可变参数练习236. 作用域基本使用237. 作用域使用细节1238. 作用域使用细节2…

Docker安装部署和常用命令

Docker 是一种开源的平台&#xff0c;旨在帮助开发者和运维人员更轻松地创建、部署和运行应用程序。通过将应用程序及其依赖项打包到一个名为容器的标准化单位中&#xff0c;Docker 提供了一种轻量级的虚拟化解决方案。与传统虚拟机相比&#xff0c;Docker 容器可以在同一主机上…

GoogleNet原理与实战

在2014年的ImageNet图像识别挑战赛中&#xff0c;一个名叫GoogLeNet 的网络架构大放异彩。以前流行的网络使用小到11&#xff0c;大到77的卷积核。本文的一个观点是&#xff0c;有时使用不同大小的卷积核组合是有利的。 回到他那个图里面你会发现,这里的一个通过我们最大的池化…

12条职场经验总结

01 事干得好不好尚且不说&#xff0c;但是话一定要说得漂亮。 比如&#xff0c;当领导给你安排工作的时候&#xff0c;你一定要非常积极地响应&#xff0c;拍着胸脯跟领导说“放心吧”。至于后续到底怎么干&#xff0c;再结合实际情况有的放矢地把握。 02 当别人夸奖你的时…

记录使用crypto-js、jsencrypt实现js加密的方法

实用为主&#xff0c;直接上干货。 使用工具&#xff1a;pycharm专业版2020.3.2。 记录通过crypto-js模块、jsencrypt模块两种方式实现加密。 本文在pycharm中新建一个项目&#xff0c;一步一步记录实现步骤。 一、新建pycharm项目并新建两个js文件&#xff0c;分别命名为c…

Python 工具库每日推荐 【Requests】

文章目录 引言Python网络库的重要性今日推荐:Requests工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例1:获取天气信息案例分析案例2:文件上传案例分析高级特性会话和Cookie处理自定义请求头超时设置代理设置扩展阅读与资源优缺点分析优点:缺…

Markdown 语法详解大全(超级版)(三)——甘特图语法详解

Markdown 语法详解大全(超级版)&#xff08;三&#xff09;——甘特图语法详解 Markdown 语法详解大全(超级版)&#xff08;一&#xff09; Markdown 语法详解大全(超级版)&#xff08;二&#xff09; Markdown 语法详解大全(超级版)&#xff08;三&#xff09; Markdown 语法…

[Linux#61][UDP] port | netstat | udp缓冲区 | stm32

目录 0. 预备知识 1. 端口号的划分范围 2. 认识知名端口号 3. netstat 命令 4. pidof 命令 二.UDP 0.协议的学习思路 1. UDP 协议报文格式 报头与端口映射&#xff1a; 2. UDP 的特点 面向数据报&#xff1a; 3. UDP 的缓冲区 4. UDP 使用注意事项 5. 基于 UDP 的…

栈的介绍与实现

一. 概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out的原则。 压栈&#xff1a;栈的插…

平面电磁波(解麦克斯韦方程)电场相位是复数的积分常数,电场矢量每个分量都有一个相位。磁场相位和电场一样,这是因为无损介质中实数的波阻抗

注意无源代表你立方程那个点xyzt处没有源&#xff0c;电场磁场也是这个点的。 j电流面密度&#xff0c;电流除以单位面积&#xff0c;ρ电荷体密度&#xff0c;电荷除以单位体积。 j方程组有16个未知数&#xff0c;每个矢量有三个xyz分量&#xff0c;即三个未知数&#xff0c;…