当前位置: 首页 > news >正文

linux用户态与内核态通过字符设备交互

linux用户态与内核态通过字符设备交互

简述

Linux设备分为三类,字符设备、块设备、网络接口设备。字符设备只能一个字节一个字节读取,常见外设基本都是字符设备。块设备一般用于存储设备,一块一块的读取。网络设备,Linux将对网络通信抽象成一个设备,通过套接字对其进行操作。

在这里插入图片描述

对于字符设备的用户态与内核态交互,主要涉及到打开、读取、写入、关闭等操作。通过字符设备实现内核与用户程序的交互,设计实现一个内核态监控文件目录及文件复制拷贝的内核模块程序,其中字符设备交互时序图如下:

user_space kernel_space 发送监控目录信息(list) 回复监控目录信息已设置 user_space kernel_space

通信协议格式

[2bytes数据长度] + |2bytes目录路径数量| + |2bytes 长度| + |目录数据| + ... + |2bytes 长度| + |目录数据|

控制命令定义

#include <linux/ioctl.h>#define BASEMINOR 0
#define COUNT 5
#define NAME "ioctl_test"#define IOCTL_TYPE 'k'//定义无参的命令
#define IOCTL_NO_ARG _IO(IOCTL_TYPE, 1)//用户空间向内核空间写
#define IOCTL_WRITE_INT _IOW(IOCTL_TYPE, 2,int)//用户空间从内核空间读
#define IOCTL_READ_INT _IOR(IOCTL_TYPE, 3, int)//用户空间向内核空间写
#define IOCTL_WRITE_STRING _IOW(IOCTL_TYPE, 4,char*)//用户空间从内核空间读
#define IOCTL_READ_STRING _IOR(IOCTL_TYPE, 5, char*)#define IOCTL_MAXNR 5

上述命令实现了用户态向内核态写入、读取int型或string类型的数据,定义控制命令个数为5

用户态程序

#include <stdio.h>
#include <string.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "cmd.h"
enum arg_type{ARG_INT,ARG_STRING
};
union data{int integer;char string[255];};struct arg_node{int type; //字符串类型union data arg_data;struct arg_node*next;
};
void insert_node(struct arg_node**head, struct arg_node * item ){if(item==NULL){printf("待插入节点指针为空\n");return ;}if(*head == NULL){*head = item;printf("节点指针赋值,%p\n",*head);}else{struct arg_node *current = *head;while(current->next != NULL){current = current->next;}current->next = item;}}//参数格式:user_ipc -int 200 -string "12324154"
int main(int argc, char *argv[])
{if(argc<2 || argc%2==0){printf("参数个数不匹配\n");return -1;}int fd = 0;int arg = 0;fd = open("/dev/ioctl_test", O_RDWR);if(fd < 0){printf("open memdev0 failed!\n");return -1;}if(ioctl(fd, IOCTL_NO_ARG, &arg) < 0){printf("----打印命令传输失败----\n");return -1;}unsigned char *protocol_body = NULL;int init_length = 5;int realloc_length = 10;int len_tag_bytes = 2;protocol_body = calloc(init_length, sizeof(unsigned char )*init_length);int index = 4;int num_of_dirs = 0;struct arg_node *p_head = NULL;int i=0;for(i=1; i<argc; i=i+2){if(strcmp(argv[i],"-int") == 0){struct arg_node*p_item = malloc(sizeof(struct arg_node));p_item->next = NULL;p_item->type = ARG_INT;p_item->arg_data.integer = atoi(argv[i+1]);insert_node(&p_head, p_item);printf("插入int类型,值: %d \n",p_item->arg_data.integer);if(p_head==NULL)printf("链表头指针为空\n");}else if(strcmp(argv[i], "-string") == 0){struct arg_node *p_item = malloc(sizeof(struct arg_node));p_item->next = NULL;		p_item->type = ARG_STRING;memcpy(p_item->arg_data.string, argv[i+1],strlen(argv[i+1]));insert_node(&p_head, p_item);printf("插入string类型,值: %s \n",p_item->arg_data.string);//插入值组装协议数据包[2bytes数据长度] + [2bytes 字符串数量] +[2bytes长度] + [目录绝对路径] ... + [2bytes长度] + [目录绝对路径]int length = strlen(argv[i+1]);if((index+len_tag_bytes+length) > init_length) //空间不够,再分配{realloc_length = length + len_tag_bytes + 1; //计算再分配字节,多分配1个字节,作为结束nullprotocol_body = realloc(protocol_body, sizeof(unsigned char)*(init_length + realloc_length));if(!protocol_body){printf("再分配空间失败\n");exit(-1);}memset(protocol_body+index, 0, sizeof(unsigned char)*(init_length + realloc_length)); //初始化再分配空间为零init_length += realloc_length;printf("新分配空间成功,新分配空间字节大小 %d,总空间大小 %d\n",realloc_length, init_length);}protocol_body[index] = length / 256 ;protocol_body[index + 1] = length % 256;index = index + 2;memcpy(protocol_body + index, argv[i+1],length);index = index + length;num_of_dirs++;}}index = index -2;protocol_body[0] = index / 256;protocol_body[1] = index % 256;protocol_body[2] = num_of_dirs /256;protocol_body[3] = num_of_dirs %256;printf("组包数据:%d\n",index);for(i=0; i<index+2; i++){printf("%02x ",protocol_body[i]);}printf("\n");//内核交互 -- 字符设备if(ioctl(fd, IOCTL_WRITE_STRING, protocol_body)<0){printf("----用户态向内核写入字符串数据失败----\n");return -1;}char recv[256]={0};if(ioctl(fd,IOCTL_READ_STRING,recv)<0){printf("----用户态从内核态读取字符串数据失败----\n");return -1;}printf("从内核态读取数据:%s\n",recv);//释放申请内存free(protocol_body);protocol_body = NULL;close(fd);return 0;
}

上述代码实现把多个int或者char*类型的数据插入链表中,但是实际使用中,这个链表比没有用,和用户态交互,我只使用了string类型的数据,再把数据存入到protocol_body中,通过控制命令IOCTL_WRITE_STRING,实现把protocol_body写入到字符设备,供内核模块读取,同时内核模块返回一个随机数。

编译命令

gcc -o user_ipc user_ipc.c

内核模块

//msg_recv_send.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
#include <linux/random.h>
#include "cmd.h"
#include "ctl_data.h"dev_t dev_num;
struct cdev *cdevp = NULL;/*
struct dir_node{int length; //长度char *dir_s; //目录字符串struct list_head list; //链表
};
*/
LIST_HEAD(msg_list_head);//处理
int handle_recv_msg(char *msg, int len){int ret = 0;int dir_index=0;//清空链表struct dir_node *entry, *tmp;list_for_each_entry_safe(entry, tmp, &msg_list_head,list){list_del(&entry->list);kfree(entry->dir_s);kfree(entry);}//解析数据int dir_length = 0;int num_of_dirs = 0;int char_index = 2;num_of_dirs = msg[0]<<8 | msg[1];for(dir_index=0; dir_index<num_of_dirs; dir_index++){dir_length = msg[char_index]<<8 | msg[char_index+1];char_index = char_index + 2;struct dir_node * new_node = kmalloc(sizeof(struct dir_node),GFP_KERNEL);new_node->dir_s = kmalloc(sizeof(char)*(dir_length+1),GFP_KERNEL);memset(new_node->dir_s, 0, dir_length+1);new_node->length = dir_length;INIT_LIST_HEAD(&new_node->list);memcpy(new_node->dir_s, msg+char_index, dir_length);char_index = char_index + dir_length;list_add_tail(&new_node->list, &msg_list_head);}//遍历列表list_for_each_entry(entry, &msg_list_head, list){printk(KERN_INFO "接收数据:%s\n",entry->dir_s);}	return ret;
}
static long my_ioctl(struct file * filp, unsigned int cmd, unsigned long arg){long ret = 0;int err = 0;int ioarg = 0;char kernel_buffer[256];unsigned int random_value = 0;		if(_IOC_TYPE(cmd) != IOCTL_TYPE){return -EINVAL;}if(_IOC_NR(cmd) > IOCTL_MAXNR){return -EINVAL;}if(_IOC_DIR(cmd) & _IOC_READ){err = !access_ok((void*)arg, _IOC_SIZE(cmd));}else if(_IOC_DIR(cmd) & _IOC_WRITE){err = !access_ok((void*)arg, _IOC_SIZE(cmd));}if(err){return -EFAULT;}switch(cmd){case IOCTL_NO_ARG:printk(KERN_INFO "print not arg cmd\n");break;case IOCTL_WRITE_INT:ret = __get_user(ioarg, (int*)arg);printk(KERN_INFO "get data from user space is :%d\n", ioarg);break;case IOCTL_READ_INT:ioarg = 1101;ret = __put_user(ioarg, (int *)arg);break;case IOCTL_WRITE_STRING:memset(kernel_buffer, 0, sizeof(kernel_buffer));unsigned char len[3]={0};ret = copy_from_user(len, (char*)arg, 2);int recv_len = 0;recv_len = len[0]*256 + len[1];printk(KERN_INFO "用户态写入的数据长度 %d",len[0]*256+len[1]);char *recv_buffer = kmalloc(sizeof(char)*recv_len,GFP_KERNEL);ret = copy_from_user(recv_buffer, (unsigned char*)(arg+2), recv_len);if(ret!=0){printk(KERN_INFO "从用户态拷贝数据失败,失败字节数 %d\n",ret);}printk(KERN_INFO "get data from user space is :%*ph\n",recv_len, recv_buffer);//处理接收到的字符串handle_recv_msg(recv_buffer, recv_len);kfree(recv_buffer);break;case IOCTL_READ_STRING://memset(random_value, 0, sizeof(random_value));memset(kernel_buffer, 0, sizeof(kernel_buffer));random_value = get_random_int();snprintf(kernel_buffer, sizeof(kernel_buffer),"返回随机字符串数值:%u",random_value);printk(KERN_INFO "kern_buffer : %s\n",kernel_buffer);ret = copy_to_user((char *)arg,kernel_buffer,sizeof(kernel_buffer));if(ret == 0){printk(KERN_INFO "写文本字符到用户态成功,[%s]。\n",(char*)arg);}else{printk(KERN_INFO "写文本字符到用户态失败,未写入字节数 %d。\n",ret);}break;default:return -EINVAL;}return ret;
}static const struct file_operations fops = {.owner = THIS_MODULE,.unlocked_ioctl = my_ioctl
};int __init ioctl_init(void ){int ret ;ret = alloc_chrdev_region(&dev_num, BASEMINOR, COUNT, NAME);if(ret < 0){printk(KERN_ERR "alloc_chrdev_region failed...\n");goto err1;}printk(KERN_INFO, "major = %d\n",MAJOR(dev_num));cdevp = cdev_alloc();if(NULL == cdevp){printk(KERN_ERR "cdev_alloc failed...\n");ret = -ENOMEM;goto err2;}cdev_init(cdevp, &fops);ret = cdev_add(cdevp, dev_num, COUNT);if(ret < 0){printk(KERN_INFO "cdev_add failed...\n");goto err2;}printk(KERN_INFO "------init completely\n");return 0;
err2:unregister_chrdev_region(dev_num, COUNT);
err1:return ret;
}void __exit ioctl_exit(void){cdev_del(cdevp);unregister_chrdev_region(dev_num, COUNT);printk(KERN_INFO "exit success.");
}

上述代码中alloc_chrdev_region分配一个名为NAME的字符设备,

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
//dev 字符设备存储的指针,高12位是主设备号,低20位是从设备号
//baseminor是从设备号
//count 请求的设备号数量
//name  设备名

内核模块主文件

//file_shield.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/unistd.h>
#include <asm/ptrace.h>
#include <linux/kallsyms.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/cred.h>
#include "hook_func.h"
#include "ctl_data.h"
#include "msg_recv_send.h"MODULE_LICENSE("GPL");//增加字符设备处理逻辑代码static int __init file_shield_init(void){int ret = 0;printk(KERN_INFO "init completly");//创建字符设备ioctl_init();printk(KERN_INFO "模块已加载\n");return ret;
}
static void __exit file_shield_exit(void){//卸载字符设备ioctl_exit();printk(KERN_INFO "模块已卸载\n");
}
module_init(file_shield_init);
module_exit(file_shield_exit);

内核模块Makefile

KERNELDIR:=/lib/modules/$(shell uname -r)/build
EXTRA_CFLAGS +=-O1PWD = $(shell pwd)obj-m +=file_hook.o
file_hook-objs:=file_shield.o msg_recv_send.o all:make -C $(KERNELDIR) M=$(PWD) modules
clean:make -C $(KERNELDIR) M=$(PWD) clean

编译

sudo make

输出

 LD [M]  /home/admin01/file-shield/file_hook.oBuilding modules, stage 2.MODPOST 1 modulesCC [M]  /home/admin01/file-shield/file_hook.mod.oLD [M]  /home/admin01/file-shield/file_hook.ko
make[1]: 离开目录“/usr/src/linux-headers-5.4.18-53-generic”

设备节点文件

在Linux系统中,设备节点文件是一种用于与设备进行交互的接口。这些设备节点文件通常位于/dev目录下。在Linux系统中,设备节点文件是一种用于与设备进行交互的接口。这些设备节点文件通常位于/dev目录下。

设备节点文件是Linux中的一种特殊文件,用于与设备进行通信。它们允许用户空间程序通过标准的文件I/O操作(如打开、读取、写入、关闭)来与设备进行交互。在/dev目录下的每个设备节点文件都对应一个特定的设备或设备类。

在内核模块中注册字符设备时,通常使用cdev_add函数,它会告诉内核创建相应的设备节点文件。这些设备节点文件将在/dev目录下动态创建,以便用户空间程序能够访问注册的设备。

例如,如果你的设备被命名为my_device,在/dev目录下将创建一个名为my_device的设备节点文件。用户空间程序可以通过打开/dev/my_device来访问你的设备。

需要手动创建一个设备节点文件

sudo mknod /dev/ioctl_test c 240 0

加载内核模块

sudo insmod path/file_hook.ko

卸载内核模块

sudo rmmod file_hook

测试

用户态程序发送接收
在这里插入图片描述

内核模块发送与接收
在这里插入图片描述

相关文章:

linux用户态与内核态通过字符设备交互

linux用户态与内核态通过字符设备交互 简述 Linux设备分为三类&#xff0c;字符设备、块设备、网络接口设备。字符设备只能一个字节一个字节读取&#xff0c;常见外设基本都是字符设备。块设备一般用于存储设备&#xff0c;一块一块的读取。网络设备&#xff0c;Linux将对网络…...

如何高效查询文件:Linux 下的多种方法详解

如何高效查询文件&#xff1a;Linux 下的多种方法详解 在日常工作中&#xff0c;我们经常需要查找文件&#xff0c;无论是寻找特定的代码文件、配置文件还是其他文档。Linux 提供了多种强大的命令和工具&#xff0c;通过巧妙地使用管道符&#xff0c;我们可以将这些命令组合起来…...

记矩阵基础概念

转自up&#xff1a;Naruto_Qcsdn&#xff1a;三维空间几何变换矩阵 先贴个站里分享的基础概念。 learn form 肥猫同学VFX b站&#xff1a;会用transform就会用矩阵 移动 旋转 缩放 1.transofrm ——输出变化矩阵 可以移动transform查看变化去理解 位移 缩放 旋转 由此—…...

用html,js和layui写一个简单的点击打怪小游戏

介绍&#xff1a; 一个简单的打怪小游戏&#xff0c;点击开始游戏后&#xff0c;出现攻击按钮&#xff0c;击败怪物后可以选择继续下一关和结束游戏。 继续下一个怪兽的血量会增加5点&#xff0c;攻击按钮会随机变色。 效果图&#xff1a; html代码&#xff1a; <!DOCTYPE…...

[线代]不挂科猴博士

行列式的性质 行列式的计算及应用 矩阵的运算上(加减,相乘,取行列式) 矩阵的运算下(转置,逆,秩) 向量组与线性空间 解方程组...

扩散式过滤器 水泵角通除污器 0阻力过滤器直角过滤器工作原理

​ 1&#xff1a;扩散式除污器过滤器介绍 扩散除污器是一种在多个领域都有应用的设备&#xff0c;例如在泵站中用于拦截介质中的杂质&#xff0c;净化介质&#xff0c;保护管路&#xff0c;提高水泵效率&#xff0c;延长水泵寿命等。它还可以方便地进行变径处理&#xff0c;可以…...

MetalLB:本地Kubernetes集群的LoadBalancer负载均衡利器

背景 在本地集群进行测试时&#xff0c;我们常常面临一个棘手的问题&#xff1a;Service Type不支持LoadBalancer&#xff0c;而我们只能选择使用NodePort作为替代。这种情况下&#xff0c;我们通常会配置Service为NodePort&#xff0c;并使用externalIPs将流量导入Kubernetes…...

C++判定终端ip和目标ip是否在同一局域网内

程序如下&#xff1a;用于判断给定的终端 IP、子网掩码和目标 IP 是否在同一局域网内。请注意&#xff0c;这个程序假设 IP 地址是用整数表示的。 #include <iostream> #include <sstream> #include <vector> #include <bitset>// Function to check …...

深入解析 可空值类型

前言&#xff1a; 问&#xff1a;为什么会有可空值类型的诞生&#xff1f; 答&#xff1a;应对在某些特定场景中获取的信息可能是不完整的。 C# 1中的可空值类型 在C#1中没有对应的表示Null值的方法。当时普遍都是采用其他方式。第一种在数据缺失的情况下给其一个默认值。第…...

esp32idf使用thingscloud例程

对于不同的消息类型&#xff0c;API 如下&#xff1a; 消息类型 HTTP Method HTTP URL 设备上报属性 POST /device/v1//attributes 设备获取属性 GET /device/v1//attributes 设备上报事件 POST /device/v1//event/report 您只需要将以上的 HTTP URL 和接入点拼接即可获得最终…...

记一次应急响应练习(Linux)

记一次应急响应练习(Linux) Linux&#xff1a; 请提交攻击者的IP地址 答&#xff1a; 192.168.31.132 思路&#xff1a; 通过查看历史命令和开放的8080端口看到这台主机上运行的是Tomcat服务。并且在历史命令中看到了Tomcat的安装路径。那么就算是找到了日志的查看点了&#x…...

Pandas教程(四)—— 分层索引及数据联合

1.分层索引 分层索引就是在一个轴上拥有多个&#xff08;两个及以上&#xff09;索引级别&#xff0c;能以低维度形式处理高维度数据。 行索引有两层 1.1 分层索引的创建 1.1.1 方式一&#xff1a;直接设置 1&#xff09;在创建series、dataframe或读取文件时时&#xff0c;行…...

小梅哥Xilinx FPGA学习笔记16——FSM(状态机)的学习

目录 一、 状态机导读 1.1 理论学习 1.2 状态机的表示 1.3 状态机编码 1.4 状态机描述方式 二 、实战演练一&#xff08;来自野火&#xff09; 2.1 实验目标 2.2 模块框图 2.3 状态转移图绘制 2.4 设计文件 2.5 仿真测试文件 2.6 仿真结果 三、 实战演练二&…...

vol----学习随记!!!

目录 一、代码生成1.先新建一个功能的对应的代码配置各项解释&#xff1a; 2.后设置配置菜单3.再点保存&#xff0c;生成vue页面&#xff0c;生成model&#xff0c;生成业务类4.再通过菜单设置编写系统菜单 一、代码生成 1.先新建一个功能的对应的代码配置 各项解释&#xff…...

HarmonyOS4.0系统性深入开发10卡片事件能力说明

卡片事件能力说明 ArkTS卡片中提供了postCardAction()接口用于卡片内部和提供方应用间的交互&#xff0c;当前支持router、message和call三种类型的事件&#xff0c;仅在卡片中可以调用。 接口定义&#xff1a;postCardAction(component: Object, action: Object): void 接口…...

openGauss学习笔记-181 openGauss 数据库运维-升级-升级流程

文章目录 openGauss学习笔记-181 openGauss 数据库运维-升级-升级流程 openGauss学习笔记-181 openGauss 数据库运维-升级-升级流程 本章介绍升级到该版本的主要升级过程。 图 1 升级流程图 说明&#xff1a; 本文档中描述的时间仅供参考&#xff0c;实际操作时间以现场情况…...

提前应对威胁

通过新的《2023-2028 年荷兰国际网络安全战略》&#xff0c;荷兰政府在面对国家和犯罪分子持续构成的网络威胁时展现了责任和机构。它渴望将民主、人权和规范放在首位&#xff0c;并寻求维护全球开放、自由和安全的互联网。该战略明确了政府在国内实施打击的意愿和能力&#xf…...

C语言与人生:数组交换和二分查找

少年们&#xff0c;大家好。我是博主那一脸阳光&#xff0c;今天和分享数组交换和二分查找。 前言&#xff1a;探索C语言中的数组交换操作与二分查找算法 在计算机编程领域&#xff0c;特别是以C语言为代表的低级编程语言中&#xff0c;对数据结构的理解和熟练运用是至关重要的…...

Python实现【亚马逊商品】数据采集

前言 亚马逊公司&#xff0c;是美国最大的一家网络电子商务公司&#xff0c;位于华盛顿州的西雅图 是网络上最早开始经营电子商务的公司之一&#xff0c;亚马逊成立于1994年 今天教大家用Python批量采集亚马逊平台商品数据&#xff08;完整代码放在文末&#xff09; 地址&#…...

Git使用教程 gittutorial

该教程对该文章的翻译&#xff1a;https://git-scm.com/docs/gittutorial 本文介绍怎用使用 Git 导入新的工程、修改文件及如何其他人同步开发。 首先&#xff0c; 可以使用以下指令获取文档帮助 git help log笔者注&#xff1a;不建议看这个文档&#xff0c;标准的语法介绍…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...