3-内核开发-第一个字符设备模块开发案例

3-内核开发-第一个字符设备模块开发案例

目录

3-内核开发-第一个字符设备模块开发案例

(1) 字符设备背景介绍

(2) 简单版本字符设备模块

(3) 继续丰富我们的字符驱动模块,增加write,read 功能

(4) 编译执行验证

(5)总结

(6)后记

(7)参考

课程简介:
Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。

课程特点:
1. 入门级别:该课程专注于为初学者提供Linux内核开发的入门知识。无论你是否具有编程或操作系统的背景,该课程都将从最基本的概念和技术开始,逐步引导学习者深入了解Linux内核开发的核心原理。

2. 系统化学习:课程内容经过系统化的安排,涵盖了Linux内核的基础知识、内核模块编程、设备驱动程序开发等关键主题。学习者将逐步了解Linux内核的结构、功能和工作原理,并学习如何编写和调试内核模块和设备驱动程序。

3. 实践导向:该课程强调实践,通过丰富的实例和编程练习,帮助学习者将理论知识应用到实际的Linux内核开发中。学习者将有机会编写简单的内核模块和设备驱动程序,并通过实际的测试和调试来加深对Linux内核开发的理解。

4. 配套资源:为了帮助学习者更好地掌握课程内容,该课程提供了丰富的配套资源,包括教学文档、示例代码、实验指导和参考资料等。学习者可以根据自己的学习进度和需求,灵活地利用这些资源进行学习和实践。

无论你是计算机科学专业的学生、软件工程师还是对Linux内核开发感兴趣的爱好者,Linux内核开发入门课程都将为你提供一个扎实的学习平台,帮助你掌握Linux内核开发的基础知识,为进一步深入研究和应用Linux内核打下坚实的基础。

这一讲主要讲述如何开发第一个Linux字符设备驱动程序模块,动手开发代码,运行加载卸载模块

(1) 字符设备背景介绍

Linux 字符设备驱动程序是一种内核模块,它允许用户空间程序与字符设备进行交互。
字符设备是按字节而不是按块访问的设备。这意味着您可以将它们视为一组连续的字节。

字符设备驱动程序对于 Linux 系统的正常运行至关重要。如果没有字符设备驱动程序,系统将无法与各种设备进行通信。例如,以下是一些字符设备驱动程序的示例:

  • 串口驱动程序(/dev/ttyS*
  • 并口驱动程序(/dev/lp*
  • 打印机驱动程序(/dev/lp*
  • 键盘驱动程序(/dev/input/event*
  • 鼠标驱动程序(/dev/input/mouse*
  • LCD 显示器驱动程序(/dev/fb*
  • LED 驱动程序(/dev/led*
  • 文件系统驱动程序(/dev/sda*
  • 网络设备驱动程序(/dev/eth*
  • 块设备驱动程序(/dev/sda*

有了前面两节的学习,当前我们的实验环境有了,也知道了模块内核加载的流程。所以可以开始实现各种类型的设备模块开发。


在开始前,这里先我们在一起回顾下模块加载和卸载的流程。

Linux 内核模块加载过程

1. 用户空间程序调用 insmod 命令。 此命令将模块文件(以 .ko 结尾)传递给内核。
2. 内核检查模块文件。 内核检查模块文件的语法和签名。
    如果模块文件无效或未签名,则内核将拒绝加载它。
3. 内核分配内存并加载模块。 
    内核分配内存并将模块代码加载到内存中。
4. 内核解析模块符号。 
    内核解析模块符号并将其添加到内核符号表中。
    内核可以在运行时引用模块符号。
5. 内核初始化模块。 
    内核调用模块的 init() 函数来初始化模块。
    init() 函数负责执行模块的任何初始化任务,例如注册设备或创建内核线程。
6. 模块已加载。 
    一旦模块的 init() 函数返回,模块即已加载。
    加载后,内核可以使用模块提供的功能。

卸载内核模块的过程

1. 用户空间程序调用 rmmod 命令。 此命令将模块名称传递给内核。
2. 内核检查模块。 内核检查模块是否正在使用。如果模块正在使用,则内核将拒绝卸载它。
3. 内核调用模块的 exit() 函数。 exit() 函数负责执行模块的任何清理任务,例如注销设备或销毁内核线程。
4. 内核释放模块内存。 内核释放模块占用的内存。
5. 模块已卸载。 一旦模块的 exit() 函数返回,模块即已卸载。

卸载模块之前,内核将等待所有使用该模块的进程完成。这意味着如果某个进程正在使用模块提供的功能,则您可能无法立即卸载该模块。

接下来我们进行开发,我们基于上一节的简单的HelloModule 继续开发。
首先我们在干一件事情前,先思考下,干这件事情,我们有什么收获,这个知识点可以做什么,未来能做什么开发?
字符设备驱动程序通常用于与串口、并口和打印机等设备进行交互。


要编写字符设备驱动程序,您需要遵循以下步骤:

1. 创建一个新的内核模块项目。
2. 在模块中定义字符设备结构。
3. 注册字符设备(open,release)。
4. 实现字符设备操作函数(read,write)。
5. 构建并加载模块。

以下是一个编写字符设备驱动程序的示例(本课程的内核模块名称时hello,字符设备名称为mydev,执行命令时请务必不要混淆)

(2) 简单版本字符设备模块

(a)简单版本,先增加模块的open 与release 方法。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#define MAJOR_NUM 90
#define MINOR_NUM 1

#define DEVICE_NAME "mydev"

static struct cdev cdev;


MODULE_LICENSE("GPL");
MODULE_AUTHOR("kk hu");
MODULE_DESCRIPTION("A simple hello world char dev module");
MODULE_VERSION("1.0.0");

static int helloworld_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Hello world!\n");
        return 0;
}

static int helloworld_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Goodbye world!\n");
        return 0;
}

static struct file_operations helloworld_fops = {
        .owner = THIS_MODULE,
        .open = helloworld_open,
        .release = helloworld_release,
};


static int __init hellowrld_init(void)
{
        printk(KERN_INFO "hello world \n");
            int ret;

        // 注册字符设备
        ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &helloworld_fops);
        if (ret < 0) {
                printk(KERN_ERR "Failed to register chrdev\n");
                return ret;
        }

        // 初始化字符设备结构
        cdev_init(&cdev, &helloworld_fops);

        // 添加字符设备到系统
        ret = cdev_add(&cdev, MKDEV(MAJOR_NUM, MINOR_NUM), 1);
        if (ret < 0) {
                printk(KERN_ERR "Failed to add chrdev\n");
                unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
                return ret;
        }

        printk(KERN_INFO "Hello world character device driver loaded\n");
        return 0;

}


static void __exit helloworld_exit(void)
{
        // 从系统中移除字符设备
        cdev_del(&cdev);
        // 注销字符设备
        unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
        printk(KERN_INFO "Hello world character device driver unloaded\n");
}

module_init(hellowrld_init);
module_exit(helloworld_exit)

里面两个核心函数register_chrdev,unregister_chrdev 这里详细说明下:

register_chrdev 函数用于向内核注册字符设备。必须在使用字符设备之前完成。

register_chrdev 函数的原型如下:

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

其中:

  • major:字符设备的主设备号。
  • name:字符设备的名称。
  • fops:指向文件操作结构的指针。文件操作结构包含用于字符设备的各种操作的函数指针。

unregister_chrdev 函数用于从内核注销字符设备。必须在不再使用字符设备后完成。

unregister_chrdev 函数的原型如下:

int unregister_chrdev(unsigned int major, const char *name);

其中:

  • major:字符设备的主设备号。
  • name:字符设备的名称。

我们的Makefile文件不需要修改,直接复制上一节课的内容。编译及测试上述代码。

(a)简单版编译,加载,测试

peach@peach-VirtualBox:~/HelloModule$ make
make -C /lib/modules/5.15.0-105-generic/build M=/home/peach/HelloModule modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-105-generic'
  CC [M]  /home/peach/HelloModule/hello.o
/home/peach/HelloModule/hello.c: In function ‘hellowrld_init’:
/home/peach/HelloModule/hello.c:45:13: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
   45 |             int ret;
      |             ^~~
  MODPOST /home/peach/HelloModule/Module.symvers
  CC [M]  /home/peach/HelloModule/hello.mod.o
  LD [M]  /home/peach/HelloModule/hello.ko
  BTF [M] /home/peach/HelloModule/hello.ko
Skipping BTF generation for /home/peach/HelloModule/hello.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-105-generic'

(b)通过设备名称检查设备是否安装

peach@peach-VirtualBox:~/HelloModule$ cat /proc/devices |grep mydev
 90 mydev

从输出,我们也看到,已经加载成功,主设备号为 90

(c)dmesg 查看内核日志信息

peach@peach-VirtualBox:~/HelloModule$ dmesg

[79204.243936] Hello world character device driver unloaded

(3) 继续丰富我们的字符驱动模块,增加write,read 功能

helloworld_fops 增加write,read ops ,后面我们需要实现这两个函数

        .read = helloworld_read,
        .write = helloworld_write,

定义一个全局的缓冲区 static char message[256]; 后面操作我们会用到这个缓冲区,实现 read,write 方法,这两个方法都需要返回读写了多少个字节。


static ssize_t helloworld_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
        int len = strlen(message);

        if (*pos >= len) {
                return 0;
        }

        if (copy_to_user(buf, message + *pos, len - *pos)) {
                return -EFAULT;
        }

        *pos += len;

        return len;
}

static ssize_t helloworld_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
        int len = count;

        if (*pos + len > sizeof(message)) {
                return -ENOSPC;
        }

        if (copy_from_user(message + *pos, buf, len)) {
                return -EFAULT;
        }

        *pos += len;

        return len;
}

(4) 编译执行验证

(a)make 

  $make

(b)insmod ko 文件

$sudo insmod hello.ko

(c)mknod

        使用 mknod 命令创建一个字符设备文件 /dev/mydev,命令将创建字符设备文件 /dev/mydev,其主设备号为 90,次设备号为 1。为我们后面进行实验做好准备工作。

$sudo mknod /dev/mydev c 90 1

(d)测试echo 数据,这个时候,需要切换到root 

$sudo su root     #进入root 用户
$echo "12345" > /dev/mydev 

验证写入的内容:

root@peach-VirtualBox:/home/peach/HelloModule# cat /dev/mydev
12345
root@peach-VirtualBox:/home/peach/HelloModule#

测试:echo "hello" > /dev/mydev

验证成功。

dmesg 看内核消息, 显示了模块加载卸载流程。

[80080.128565] Hello world character device driver loaded
[81006.718723] Hello world!
[81006.718748] Goodbye world!
(5)总结


到此,我们实现了字符设备,本章课程所有代码(简单版本hello.v1.c,丰富版本 hello.c )

Makefile 文件


obj-m += hello.o

CFLAGS := -Wall -O2

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

简单版本 hello.v1.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#define MAJOR_NUM 90
#define MINOR_NUM 1

#define DEVICE_NAME "mydev"

static struct cdev cdev;


MODULE_LICENSE("GPL");
MODULE_AUTHOR("kk hu");
MODULE_DESCRIPTION("A simple hello world char dev module");
MODULE_VERSION("1.0.0");

static int helloworld_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Hello world!\n");
        return 0;
}

static int helloworld_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Goodbye world!\n");
        return 0;
}


static struct file_operations helloworld_fops = {
        .owner = THIS_MODULE,
        .open = helloworld_open,
        .release = helloworld_release,
};

static int __init hellowrld_init(void)
{
        printk(KERN_INFO "hello world \n");
            int ret;

        // 注册字符设备
        ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &helloworld_fops);
        if (ret < 0) {
                printk(KERN_ERR "Failed to register chrdev\n");
                return ret;
        }

        // 初始化字符设备结构
        cdev_init(&cdev, &helloworld_fops);

        // 添加字符设备到系统
        ret = cdev_add(&cdev, MKDEV(MAJOR_NUM, MINOR_NUM), 1);
        if (ret < 0) {
                printk(KERN_ERR "Failed to add chrdev\n");
                unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
                return ret;
        }

        printk(KERN_INFO "Hello world character device driver loaded\n");
        return 0;

}


static void __exit helloworld_exit(void)
{
        // 从系统中移除字符设备
        cdev_del(&cdev);

        // 注销字符设备
        unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

        printk(KERN_INFO "Hello world character device driver unloaded\n");

}


module_init(hellowrld_init);
module_exit(helloworld_exit)


丰富版本代码-hello.c 


#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#define MAJOR_NUM 90
#define MINOR_NUM 1

#define DEVICE_NAME "mydev"

static struct cdev cdev;
static char message[256];

MODULE_LICENSE("GPL");
MODULE_AUTHOR("kk hu");
MODULE_DESCRIPTION("A simple hello world char dev module");
MODULE_VERSION("1.0.0");

static int helloworld_open(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Hello world   open !\n");
        return 0;
}

static int helloworld_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "Goodbye world!  release!\n");
        return 0;
}


static ssize_t helloworld_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
        int len = strlen(message);

        if (*pos >= len) {
                return 0;
        }

        if (copy_to_user(buf, message + *pos, len - *pos)) {
                return -EFAULT;
        }

        *pos += len;

        return len;
}

static ssize_t helloworld_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
        int len = count;

        if (*pos + len > sizeof(message)) {
                return -ENOSPC;
        }

        if (copy_from_user(message + *pos, buf, len)) {
                return -EFAULT;
        }

        *pos += len;

        return len;
}

static struct file_operations helloworld_fops = {
        .owner = THIS_MODULE,
        .open = helloworld_open,
        .release = helloworld_release,
        .read = helloworld_read,
        .write = helloworld_write,

};

static int __init hellowrld_init(void)
{
        printk(KERN_INFO "hello world \n");
            int ret;

        // 注册字符设备
        ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &helloworld_fops);
        if (ret < 0) {
                printk(KERN_ERR "Failed to register chrdev\n");
                return ret;
        }

        // 初始化字符设备结构
        cdev_init(&cdev, &helloworld_fops);

        // 添加字符设备到系统
        ret = cdev_add(&cdev, MKDEV(MAJOR_NUM, MINOR_NUM), 1);
        if (ret < 0) {
                printk(KERN_ERR "Failed to add chrdev\n");
                unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
                return ret;
        }

        printk(KERN_INFO "Hello world character device driver loaded\n");
        return 0;

}


static void __exit helloworld_exit(void)
{
        // 从系统中移除字符设备
        cdev_del(&cdev);

        // 注销字符设备
        unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

        printk(KERN_INFO "Hello world character device driver unloaded\n");

}


module_init(hellowrld_init);
module_exit(helloworld_exit)

(6)后记


当前linux 内核变化大,可能这边文章的代码,可能无法在你的环境里面执行,这时候,你就需要进行修改了。那么如何让你的代码具有兼容各个版本内核,就是一项工程化工作及问题。

(7)参考


有关字符设备驱动程序的更多信息,请参阅 Linux 内核文档:https://www.kernel.org/doc/html/v4.11/driver-api/index.html
有关 cdev 结构的更多信息,请参阅 Linux 内核 API 文档:https://www.kernel.org/doc/html/latest/search.html?q=cdev


 

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

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

相关文章

[Meachines][Easy]Crafty

Main $ sudo nmap -p- -sS -T4 10.10.11.249 发现25565端口是我的世界服务器端口 CVE-2021-44228: https://nodecraft.com/blog/service-updates/minecraft-java-edition-security-vulnerability在阿帕奇Log4j图书馆&#xff0c;广泛使用的记录框架&#xff0c;在Java应用程序…

一起Talk Android吧(第五百五十七回:如何获取文件读写权限)

文章目录 1. 概念介绍2. 使用方法3. 示例代码4. 内容总结各位看官们大家好,上一回中分享了一个Retrofit使用错误的案例,本章回中将介绍 如何获取文件读写权限。闲话休提,言归正转,让我们一起Talk Android吧! 1. 概念介绍 我们在本章回中说的文本读写权限是指读写手机中的…

0-1背包问题:贪心算法与动态规划的比较

0-1背包问题&#xff1a;贪心算法与动态规划的比较 1. 问题描述2. 贪心算法2.1 贪心策略2.2 伪代码 3. 动态规划3.1 动态规划策略3.2 伪代码 4. C语言实现5. 算法分析6. 结论7. 参考文献 1. 问题描述 0-1背包问题是组合优化中的一个经典问题。假设有一个小偷在抢劫时发现了n个…

CCF-CSP真题《202312-3 树上搜索》思路+c++满分题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 问题描述 试题编号&#xff1a;202312-3试题名称&#xff1a;树上搜索时间限制&#xff1a;1.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 题目背景 问题描述 输入格式 输出格式 样…

BioTech - 使用 Amber 工具 松弛(Relaxation) 蛋白质三维结构 (Python)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/137889532 Amber 工具在蛋白质 松弛(Relaxation) 过程中起着重要的作用。在分子动力学模拟中,蛋白质松弛是指模拟过程中蛋白质结构达到一个较为稳定的状态。这个过程通…

SQLite轻量级会话扩展(三十四)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite R*Tree 模块&#xff08;三十三&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 1. 引言 会话扩展提供了一种方便记录的机制 对 SQLite 数据库中某些表的部分或全部更改&#xff0c;以及 将这些…

视频质量评价 SSIM 算法详细介绍

SSIM SSIM(Structural Similarity Index Measure)是一种用于衡量两幅图像之间相似度的指标,是属于全参考视频质量评价算法范畴;它在图像质量评估领域得到了广泛的应用。SSIM是基于人类视觉系统的特性设计的,它考虑了图像的亮度、对比度和结构信息。SSIM的值范围在-1到1之…

xilinx 7系列FPGA时钟布线资源

7系列FPGA拥有多种时钟路由资源&#xff0c;以支持各种时钟方案和需求&#xff0c;包括高扇出、短传播延迟以及极低的偏斜。为了最佳地利用时钟路由资源&#xff0c;需要了解如何将用户时钟从PCB传递到FPGA&#xff0c;确定哪种时钟路由资源最优&#xff0c;然后通过利用适当的…

【数据结构|C语言版】单链表

前言1. 单链表的概念和结构1.1 单链表的概念1.2 单链表的结构 2. 单链表的分类3.单链表的实现3.1 新节点创建3.2 单链表头插3.3 单链表头删3.4 单链表尾插3.5 单链表尾删3.6 链表销毁 4. 代码总结4.1 SLT.h4.2 SLT.c4.3 test.c 后言 前言 各位小伙伴大家好&#xff01;时隔不久…

百科不全书之 docker记录

docker记录 1.参考文件2. Docker简介与虚拟机的区别 3. 安装Docker注意 Windows家庭版的要额外设置 4.使用5.docker与ROS 1.参考文件 参考视频&#xff1a;B站【GeekHour】Docker入门教程: 【GeekHour】30分钟Docker入门教程 2. Docker简介 Docker是一个用于构建运行 传送…

The C programming language (second edition,KR) exercise(CHAPTER 4)

E x c e r c i s e 4 − 1 Excercise\quad 4-1 Excercise4−1&#xff1a; #include <stdlib.h> #include <stdio.h> #include <string.h> int strindex(char s[],char t[]); int strrindex(char s[],char t[]);int main(void) {char s[100]"qwoulddf…

Java | Leetcode Java题解之第41题缺失的第一个正数

题目&#xff1a; 题解&#xff1a; class Solution {public int firstMissingPositive(int[] nums) {int n nums.length;for (int i 0; i < n; i) {while (nums[i] > 0 && nums[i] < n && nums[nums[i] - 1] ! nums[i]) {int temp nums[nums[i] …

yolov8实战第七天——pyqt5-yolov8实现车牌识别系统(参考论文(约7000字)+环境配置+完整部署代码+代码使用说明+训练好的模型)

基于 pyqt5-yolov8实现车牌识别系统,包括图片车牌识别,视频车牌识别,视频流车牌识别。 效果展示(图片检测,检测到的内容添加到历史记录): 效果展示(视频检测,视频车辆只会添加一条记录,下文更多实际应用中的优化策略): 基于YOLOv8和PyQt5的车牌识别系统设计与…

存储竞赛,角逐未来

随着人工智能&#xff08;AI&#xff09;和大数据驱动的海量数据需求&#xff0c;对存储技术的要求也在不断提高。在此背景下&#xff0c;各大存储芯片巨头之间的技术竞赛日益激烈。 在NAND闪存领域&#xff0c;企业关注的重点在于层数的突破。近日&#xff0c;《韩国经济日报》…

linux下编译c++程序报错“undefined reference to `std::allocator<char>::allocator()‘”

问题 linux下编译c程序报错“undefined reference to std::allocator::allocator()”。 原因 找不到c标准库文件。 解决办法 开始尝试给gcc指令添加-L和-l选项指定库路径和库文件名&#xff0c;但是一直不成功&#xff0c;后来把gcc改为g就可以了。

Stylus精讲:网页设计新境界【写作AI一键生成】

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

SWCTF

easy_php 源码 <?php// flag is in flag.php highlight_file(__FILE__); ini_set(display_errors, 0); error_reporting(0);if (isset($_GET[myon1]) && isset($_GET[myon2]) && isset($_GET[myon3])) {$myon1 $_GET[myon1];$myon2 $_GET[myon2];$myon…

# Win10 打不开【本地组策略编辑器】解决方案

Win10 打不开【本地组策略编辑器】解决方案 段子手168 问题描述&#xff1a; 当在 WIN R 打开【运行】输入&#xff1a;gpedit.msc 打开【本地组策略编辑器】时&#xff0c;出现错误时&#xff0c; 或者在【计算机管理】中 没有【本地用户和组】这一项。 可以试一下以下方…

Go 之 sync.Mutex 加锁失效现象

我先声明一下&#xff0c;并不是真的加锁失效&#xff0c;而是我之前的理解有误&#xff0c;导致看起来像是加锁失效一样。于是乎记录一下&#xff0c;加深一下印象。 我之前有个理解误区&#xff08;不知道大家有没有&#xff0c;有的话赶紧纠正一下——其实也是因为我这块的…

Spec-Gaussian:3D高斯溅射的各向异性视图相关外观

Spec-Gaussian: Anisotropic View-Dependent Appearance for 3D Gaussian Splatting Spec-Gaussian&#xff1a;3D高斯溅射的各向异性视图相关外观 Ziyi Yang1,3  Xinyu Gao1  Yangtian Sun2  Yihua Huang2  Xiaoyang Lyu2 杨子怡 1,3 高新宇 1 太阳扬天 2 黄宜华 2 吕晓阳…