【嵌入式环境下linux内核及驱动学习笔记-(3-字符设备驱动详解)】

news2024/7/4 5:01:32

目录

  • 1、文件系统与设备驱动
  • 2、设备文件
    • 2.1 linux的文件种类:
    • 2.2 设备分类
  • 3、 设备号
    • 3.1 dev_t类型
    • 3.2 与设备号相关的操作介绍
      • 3.2.1 宏 MKDEV
      • 3.2.2 宏 MAJOR
      • 3.2.3 宏 MINOR
      • 3.2.4 命令mknod
      • 3.2.5 register_chrdev_region()
      • 3.2.6 alloc_chrdev_region()
      • 3.2.7 unregister_chrdev_region()
  • 4、cdev结构体与file_operations结构体
    • 4.1 struct cdev与struct file_operations
    • 4.2 cdev操作函数
      • 4.2.1 cdev_init()函数
      • 4.2.2 cdev_alloc()
      • 4.2.3 cdev_put()
      • 4.3.4 cdev_add()
      • 4.2.5 cdev_del()
  • 5、字符设备驱动模板
    • 5.1 字符设备驱动简单模板
    • 5.2 编译上述模块所要用到的Makefile文件
  • 6、struct inode 及 struct file
    • 6.1 struct inode
    • 6.2 struct file
  • 7、字符设备驱动框架的总结
    • 7.1 5个重点数据类型
    • 7.2 框架的工作机制

设备驱动的学习,重在对框架的理解。因此以下会在涉及框架的重点部分用黄色块标注。

1、文件系统与设备驱动

在这里插入图片描述

Linux一切皆文件,通过VFS虚拟文件系统,把所有外设的细节都隐藏起来,最后都以文件的形态呈现于应用层。这种方式完美的统一了对用户的接口,极大方便了应用层的调用方式。

应用程序与VFS之间的接口是系统调用。VFS向下与具体的文件系统或设备文件之间的接口是file_operations结构体成员函数。file_operations是一个内核的结构体,其成员函数为真正实现对文件的打开、关闭、读写、控制的一系列成员函数。


对字符设备而言,file_operations成员函数就直接是设备驱动,在函数内完成对字符设备的读写等操作。这部分由驱动工程师直接编写。

对块设备有两种访问方法:

  • 不通过文件系统直接访问块设备,这是通过内核已实现的file_operations类型的变量def_blk_fops。典型的应用就是dd 这个命令。
  • 另一方法是通过文件系统来访问块设备,file_operations的实现位于文件系统内。

因此,以下是针对字符驱动的框架说明重点一:
从上图可以看出,当应用层面要使用某个字符设备时(比如LED灯),只需要通过统一的系统函数open()、read()、write()、close()等函数来操作与该设备所对应的字符设备文件(比如/dev/led)即可。
而从内核层面,实际是一一对应的执行了xxx_open()、xxx_read()、xxx_write)、xxx_close()等函数来具体的操作硬件设备。因此驱动程序的开发本质就是完成xxx_open()等内核驱动程序。

2、设备文件

2.1 linux的文件种类:

  1. -:普通文件
  2. d:目录文件
  3. p:管道文件
  4. s:本地socket文件
  5. l:链接文件
  6. c:字符设备
  7. b:块设备

2.2 设备分类

Linux内核按驱动程序实现模型框架的不同,将设备分为三类:

  1. 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
  2. 块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率
  3. 网络设备:针对网络数据收发的设备

3、 设备号

3.1 dev_t类型

内核用设备号来区分不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:

  1. 主设备号:占高12位,用来表示驱动程序相同的一类设备
  2. 次设备号:占低20位,用来表示被操作的哪个具体设备

应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。

Linux把设备文件统一放在/dev目录内。用 ls -l命令可以看到如下的显示:
在这里插入图片描述
在日期的前面,图上的红框部分,就是主设备号与次设备号。前面的主设备号是与驱动对应的概念,同一类设备一般使用相同的主设备号,同一驱动可以支持多个同类设备,因此用次设备号来描述使用该驱动的设备的序号,序号一般从0开始。

设备号,是设备在操作层面的一个重要身份识别号,内核层面的操作都是以设备号来识别,定位,管理设备

3.2 与设备号相关的操作介绍

3.2.1 宏 MKDEV

项目说明
语法MKDEV(int major , int minor)
功能将主设备号和次设备号转换成dev_t类型
头文件<linux/kdev_t.h>
参数major为主设备号,minor为次设备号
宏定义#define MKDEV(major,minor)(((major)<<MINORBITS)
返回值成功执行返回dev_t类型的设备编号

3.2.2 宏 MAJOR

项目说明
语法MAJOR(dev_t dev)
功能从内核设备号中取得主设备号
头文件<linux/kdev_t.h>
参数设备号
宏定义#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
返回值成功执行返回unsigned int 类型的主设备编号

3.2.3 宏 MINOR

项目说明
语法MINOR(dev_t dev)
功能从内核设备号中取得次设备号
头文件<linux/kdev_t.h>
参数设备号
宏定义#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
返回值成功执行返回unsigned int 类型的次设备编号

3.2.4 命令mknod

项目说明
命令mknod name c或b 主设备号 次设备号
功能创建字符设备文件和块设备文件
参数【name:要创建的设备名】【c: 表示创建的是字符设备】【 b:表示创建的是块设备】【 主设备号:创建设备的主设备号】【次设备号:创建设备次设备号】
举例sudo mknod /dev/mydevice c 20 0

3.2.5 register_chrdev_region()

#include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name)
该函数用于向系统申请已知的设备号,函数的原型如下所示:

参数:

     from:所需设备编号范围内的第一个,必须包括主设备号

     count:所需要的连续设备编号数量

     name:设备或驱动程序的名称

返回值:

     成功:返回0

     失败:返回负的错误号

3.2.6 alloc_chrdev_region()

#include <linux/fs.h>
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
该函数用于向系统动态申请未被占用的设备号,能自动避开设备号重复的冲突,函数的原型如下:

参数:

     dev:传出参数,传出第一个分配的设备号

     baseminor:所需要的第一个次设备号

     count:需要的次设备号的数量

     name:设备或驱动的名称

返回值:

     成功:返回0

     失败:返回负的错误号

3.2.7 unregister_chrdev_region()

#include <linux/fs.h>
void unregister_chrdev_region(dev_t from, unsigned count)
该函数用于释放掉原先申请的设备号,函数的原型如下所示:

参数:

     from:需要释放的设备号范围的第一个

     count:需要释放的设备号的数量

返回值:

4、cdev结构体与file_operations结构体

设备号是设备的身份标识,那么cdev结构体就是字符设备本体了。开发者需要主动构造cdev结构体去描述一个设备。file_operations结构体则相当于行为能力的集合,用于具体操作设备之用。

4.1 struct cdev与struct file_operations

#include  <linux/cdev.h>

struct cdev
{
 	struct kobject kobj;                //相当于父类,表示该类型实体是一种内核对象
	 struct module *owner;             //填THIS_MODULE,表示该字符设备从属于哪个内核模块
 	const struct file_operations *ops;    //指向空间存放着针对该设备的各种操作函数地址
 	struct list_head list;                  //链表指针域,各个cdev通过该链表指针串起来
 	dev_t dev;                            //设备号
	 unsigned int count;           //设备数量
};

在内核里,每一个设备都有一个对应的cdev结构体,所有的结构体在内核中以链表的形式串接起来,结构体中的 struct list_head list成员就是链表的指针域,在初始化时,由系统完成链表的挂接。

cdev结构体代表了设备本体,需要开发者在驱动模块初始化阶段手动去构造,并通过操作函数去初始化和挂接到链表。

#include <linux/fs.h>
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,  loff_t len);
	int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

常用成员指针函数简单介绍:

llseek():用来修改一个文件的当前读写位置,并将新的位置进行返回,如果出错,则函数返回一个负值;

read():用来从设备中读取数据,成功时返回读取到的字节数,出错时,则函数返回一个负值;

write():用于向设备发送数据,成功时返回写入的字节数,若该函数未实行时,用户进行函数调用,将得到-EINVAL返回值;

unlocked_ioctl():提供设备相关的控制命令的实现(不是读和写操作),调用成功时,返回给调用程序一个非负值,与应用程序调用fcntl和ioctl函数相对应;

mmap():函数将设备内存映射到进程的虚拟地址空间中,当设备驱动未实现此函数时,用户进行调用将会得到-ENODEV返回值;

open():用于打开驱动设备,若驱动程序中不实现此函数,则设备的打开操作永远成功;

release():与open相反,用于关闭设备;

poll():一般用于询问设备是否可被非阻塞地立即读写;

aio_read():对文件描述符对应的设备进行异步读操作;

aio_write():对文件描述符对应的设备进行异步写操作。

file_operations结构体如上所示,其成员几乎全部是函数指针,这些函数指针所指向的操作函数需要由开发者编写,完成直接操作设备的能力。而这些能力又是与系统调用接口open()、read()、write()、close()等一一对应的。

成员 struct module *owner; 填THIS_MODULE,表示该结构体对象从属于哪个内核模块

file_openations结构体需要开发者在驱动模块的初始化环节完成手工构建

4.2 cdev操作函数

#include <linux/cdev.h>
在这里插入图片描述

4.2.1 cdev_init()函数

void cdev_init(struct cdev *cdev , const struct file_operations *fops);
cdev_init()的作用用来初始化一个cdev结构体,函数的代码如下所示:
参数:
     cdev:要初始化化的cdev结构体
     fops:设备的file_operations结构体
返回值:无

4.2.2 cdev_alloc()

struct cdev *cdev_alloc(void);
cdev_alloc()的作用是用来动态分配一个cdev结构体,函数的代码如下所示:
参数:  无
返回值:
     成功:返回cdev结构体的指针
     失败:返回NULL

4.2.3 cdev_put()

cdev_put()函数的作用用来释放cdev,函数的代码如下所示:
参数:
     p:cdev结构体指针
返回值:
     无

4.3.4 cdev_add()

int cdev_add(struct cdev *p , dev_t dev , unsigned int  count);
cdev_add()函数用于向系统添加一个cdev,完成字符设备的注册,函数的代码如下所示:
参数:
     p:字符设备的cdev结构体指针
     dev:此设备负责的第一个设备号
     count:与此对应的次设备号的数量
返回值:
     成功:返回0
     失败:返回error号

4.2.5 cdev_del()

void cdev_del(struct cdev *);
cdev_del()向系统删除一个cdev,用于完成字符设备的注销,函数的代码如下所示:
参数:
     p:要在系统中移除的cdev结构体指针
返回值:         无

5、字符设备驱动模板

5.1 字符设备驱动简单模板

到了这一步,了解了上面相关的内容后,可以直接写出字符设备驱动程序了。这里直接给出一个模板,供编程时直接拷贝使用。

/*************************************************************************
	> File Name: arch-char.c
	> 作用:字符设备驱动简单模板
 ************************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
/*1、定义重要的变量及结构体*/
static dev_t devno;  //设备号变量
int major, minor;    //主设备号,次设备号变量
struct cdev  my_dev;  //cdev设备描述结构体变量


int my_open(struct inode *pnode , struct file *pf);  //函数声明
int my_close(struct inode *pnode , struct file *pf);  //函数声明

//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
    .open = my_open,
    .release = my_close,
};


static int __init my_init(void){
    int unsucc =0;
    /*2、创建 devno */
    unsucc = alloc_chrdev_region(&devno , 0 , 1 , "arch-char");
    if (unsucc){
        printk(" creating devno  faild\n");
        return -1;
    }
    major = MAJOR(devno);
    minor = MINOR(devno);
    /*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
    /*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
    cdev_init(&my_dev , &fops);
    my_dev.owner = THIS_MODULE;
    /*4、注册cdev结构体到内核链表中*/
    unsucc = cdev_add(&my_dev,devno,1);
    if (unsucc){
        printk("cdev add aild \n");
        return 1;
    }
    printk("the driver arch-char initalization completed\n");
    return 0;
}


static void  __exit my_exit(void)
{
    cdev_del(&my_dev);
    unregister_chrdev_region(devno , 1);
    printk("***************the driver arch-char exit************\n");
}
/*5、具体操作硬件的函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int my_open(struct inode *pnode , struct file *pf){
    printk("arch-char is opened\n");
    return 0;
    
}
/*file_operations结构全成员函数.release的具体实现*/
int my_close(struct inode *pnode , struct file *pf){
    printk("arch-char is closed \n");
    return 0;
}
    


module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

上方为字符设备驱动程序的简单模板。里面除了内核模块原有的基础程式化框架以外,针对字符设备驱动又增加了5个步骤。而之所以叫模板,因为内部的结构已模式化了,开发者在这模板基础上只需要把注意力放在file_operations的成员函数的具体实现上即可。也就是那5个步骤里,真正需要开发者关心的是第5步,其它基本可以照抄,除非你要改变变量名称。

模块中,的my_open()函数是与应用层内核调用open()函数一一对应的,也即内核调用open()函数后,到了内核底层实际是调用了my_open()函数完成对设备的操作。而如何操作设备,完成这个open的行为,这就因不同设备而异。这在后面的例子中会继续详细说明。

5.2 编译上述模块所要用到的Makefile文件

  • Makefile文件容

ROOTFS_DIR = /opt/4412/rootfs
ifeq ($(KERNELRELEASE), )
KERNEL_DIR := /home/mao/linux/linux-3.14
CUR_DIR := $(shell pwd)
all :
	make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules

clean :
	make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean
	
install:
	cp -raf *.ko   $(ROOTFS_DIR)/drv
else
obj-m += arch-char.o
endif

这个Makefile文件适用于驱动源码与linux内核源码不在同一个目录结构下的情况。比如,这里的linux内核源码在/home/mao/linux/inux-3.14目录内。而驱动代码和在/home/mao/driver/5-arch/arch-char.c。Makefile文件也与arch-char.c在相同的目录中,编译完成后,会形成archchar.ko文件,这个就是内核模块了。

在Makefile 中 obj-m用来指定模块名,注意模块名加.o而不是.ko

可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名(每个同名的.c文件对应的.o目标文件)

一个目录下的Makefile可以编译多个模块,则每个模块都用一条如下的指令就行。

添加:obj-m += 下一个模块名.o

  • Makefile工作顺序解读

KERNELRELEASE是在Linux内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE当然还没有被定义,所以顺序执行下面的各个目标语句。 如果make的目标是clean,直接执行clean操作,然后结束。

当make的目标为all时,-C ( K E R N E L D I R ) 指明跳转到 " l i n u x 内核源码目录 " 下读取那里的 M a k e f i l e ; M = (KERNEL_DIR)指明跳转到"linux内核源码目录"下读取那里的Makefile;M= (KERNELDIR)指明跳转到"linux内核源码目录"下读取那里的MakefileM=(PWD) 表明然后返回到当前目录(存放驱动代码的目录)继续读入、执行当前的Makefile。

当从内核源码目录返回时,KERNELRELEASE已被定义(因为刚才系统-C $(ERNELDIR)就是执行了内核源码的makefile),make将继续读取else之后的内容,obj-m += arch-char.o表示编译连接后将生成arch-char.ko模块。

6、struct inode 及 struct file

这里需要继续解释的是结构体struct inode 以及 struct file。

6.1 struct inode

在<linux/fs.h>头文件里定义的struct inode 结构体如下,其与磁盘上的i-node相对应。当应用层用open()访问一个文件时,会在内核中为i-node创建一个副本,主要记录如下内容。这是管理一个文件的基本要素。其中与字符驱动密切相关的是i_rdev成员,存放了设备文件对应的设备号。i_cdev则是存放着字符设备对应的cdev结构体指针,该结构体由驱动程序模块建立的。
在这里插入图片描述

6.2 struct file

在<linux/fs.h>头文件中定义的struct file 结构体如下,和inode一样,其在文件被open()时,由系统创建。并获得由字符驱动模块构建的实际操作文件的函数入口file_operations结构体的指针,将指针存于f_op成员内。
在这里插入图片描述

在应用层,每个进程都会有一个文件描述符表,这个表内会存放进程打开的每一个文件的所谓文件描述符fd。实质,文件描述符表是一个数组,而fd则是这个数组元素的下标,也就是,如果fd = 0,则‘0’是指的该数组的第0个元素。fd=10,指的是该数组的第10个元素,该元素所存的内容是每个对应文件的struct file结构体指针,该结构体又存有文件的inode 与file_operations 结构体指针。

这就达到一个目的,当应用的任何一个操作设备文件的指令,如read(fd) , write(fd)等,都可以通过文件描述符表数组的fd下标对应的元素找到内核 的file_operations结构体指针,这样就可以调用该结构体内对应.read和.write的成员函数指针,从而完成实质的对字符设备的读,写操作。

7、字符设备驱动框架的总结

7.1 5个重点数据类型

与框架相关的5个重点数据类型如下:

变量用途说明
dev_t devno设备号,标识设备的身份标号,可以解析出主设备号与次设备号创建于驱动加载之时,也存于cdev结构体内
struct cdev表示设备的结构体,将设备号与操作函数结构体file_operations关联起来创建于驱动加载之时,加载到内核的cdev链表中
struct file_operations驱动实际操作的函数入口,是具体的设备驱动函数集合的入口创建于驱动加载之时,会被其它结构体引用
struct inode集中了设备文件的相关属性,内部存有devno及cdev结构体指针创建于设备文件被open之时,应用层打开文件时,通过inode去寻找cdev
struct file保存了文件操作的状态等属性,关键是存储了file_operations结构体指针文件状态等控制信息,用于向底层操作函数传递这些状态信息

5个数据类型,devno , struct cdev , struct file_operations是在驱动程序加载时创建的。与设备驱动是一一对应的。一个设备驱动对应一套这些数据对象。

struct inode 与 struct file 是在应用层open()设备文件时,创建的。数据的关联关系如下:
驱动加载时:生成并注册devno,创建struct cdev , 创建file_operations 。并在cdev中把devno与file_operations关联起来。

应用层open(设备文件名):创建struct inode 关联了devno与struct cdev , 创建了struct file ,关联了file_operations,生成了fd关联了格struct file

应用层 read(fd):通过fd 找到struct file ,通过struct file关联file_operations找到了硬件操作函数指针.read()函数。完成硬件操作。

7.2 框架的工作机制

  • 图:字符设备驱动框架图

在这里插入图片描述

  • 图的解释:

左侧,为驱动加载时完成的工作。加载后,建立了struct cdev 以及struct file_operations 。cdev会挂在内核 的cdev链表里,等着被使用。

右侧,为用户用了 open()函数后的动作过程,会建立空struct inode和空struct file。然后,读出cdev后,把cdev中的file_operations写入struct file中。

这里,每个驱动有一个cdev,每个设备有一个inode ,每次open会产生一个fd文件描述符以及对应的struct file。

最后,每个应用层里调用read() close() ioctl()等就可以对应有如下动作 (以read举例): fd -> struct file -> file_operations.xxx_read()

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

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

相关文章

【剑指offer-C++】JZ82:二叉树中和为某一值的路径(一)

【剑指offer-C】JZ82&#xff1a;二叉树中和为某一值的路径[一]题目描述解题思路题目描述 描述&#xff1a;给定一个二叉树root和一个值 sum &#xff0c;判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。 1.该题路径定义为从树的根结点开始往下一直到叶子结点所经…

一篇文章 学会 Vue3 极速入门 (附带增删改查 案例 + Springboot)

vue3前置 00-导学 这将是你能看到的最快速Vue3 入门文章&#xff0c; 我们将快速的 去学习Vue3相关的知识&#xff0c;并结合后端做一个增删改查的项目&#xff0c;能够帮助你快速的上手Vue3&#xff0c; 包含了Vue 所含的所有特性&#xff0c; 你会知道 Vue3和Vue2 的区别&am…

Day944.度量指标 -系统重构实战

度量指标 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于度量指标的内容。 很多时候在研发过程中&#xff0c;都习惯性地用“拍脑袋”的方式来看待一个事情。例如这个代码写得不好、这个自动化测试覆盖不充分、版本的发布频率太差了等等。往往只知道哪里有问题&…

后台服务异常?测试右移告警监控早知道。。。。

目录 引言 “测试右移”思想下实践步骤 什么是“测试右移” 一、收到问题反馈 二、沟通定位问题 1.服务架构 三、讨论并选定解决方案 1.讨论分析解决方案 2.选定解决方案 四、解决方案实现 1.总体方案设计 2.编写监控脚本 3.配置服务器定时任务 五、测试环境验证…

从零开始学Java之Integer底层原理探究

前言 在之前的两篇文章中&#xff0c;壹哥给大家介绍了Java中的包装类及其特点、用法&#xff0c;但是这些内容主要是停留在”怎么用“的层面&#xff0c;没有太多涉及”为什么“&#xff0c;所以接下来壹哥会给大家讲一讲Integer这个包装类的底层原理。在现在的就业环境下&am…

Apache Tomcat CVE-2020-1938 漏洞

Apache Tomcat CVE-2020-1938 漏洞简单复现 文章目录 Apache Tomcat CVE-2020-1938 漏洞简单复现实验准备实验步骤搭建环境nmap扫描漏洞端口POC代码验证漏洞修复建议 参考链接 实验准备 所选漏洞&#xff1a;Apache Tomcat远程代码执行漏洞 漏洞编号&#xff1a;CVE-2020-193…

AppArmor零知识学习三、源码介绍与下载

本文内容参考&#xff1a; AppArmor配置&#xff08;二&#xff09;_domybest_nsg的博客-CSDN博客&#xff0c; Apparmor简单学习_trap0D的博客-CSDN博客&#xff0c; 学习LSM(Linux security module)之三:Apparmor的前世今生和基本使用_wx5b7658e51ef04的技术博客_51CTO博客…

JavaEE初阶学习:文件操作

1.文件 1.认识文件 平时说的文件一般都是指存储再硬盘上的普通文件&#xff0c;形如txt&#xff0c;jpg&#xff0c;MP4&#xff0c;rar等这些文件都可以认为是普通文件&#xff0c;它们都是再硬盘上存储的。 在计算机中&#xff0c;文件可能是一个广义的概念&#xff0c;就…

【云原生|Docker】12-Docker Harbor企业级镜像管理

【云原生Docker】12-Docker Harbor企业级镜像管理 文章目录【云原生Docker】12-Docker Harbor企业级镜像管理前言Harbor简介简介架构介绍组件间说明工作原理docker logindocker pushHarbor安装版本介绍安装Http模式部署https模式部署前言 ​ 上一章节我们介绍了Docker的官方的镜…

【redis】集成到SpringBoot

集成到SpringBoot 类似于java连接mysql需要JDBC 而Java连接redis则需要 对应的 工具类 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录集成到SpringBoot前言一、Jedis加依赖写业务二、lettuce加依赖写业务Jedis和…

转换字符串的最少操作次数是否所有1都至少相隔k个元素重复至少k次且长度为M的模式移除指定数字得到的最大结果 得到k个黑块的最少涂色次数

转换字符串的最少操作次数&#xff08;回顾等级&#xff1a;不值得&#xff1b;已达最优解&#xff09; 来源&#xff1a;自己LeetCode刷题 usa int minimumMoves(char * s) {int szstrlen(s);int res0;for (int i0;i<sz;i){if (s[i]X){i2;res;}}return res; }是否所有1都…

Makefile实验

一、准备C程序 1、input.h #ifndef __INPUT_H #define __INPUT_Hvoid input_int(int *a, int *b);#endif 2、input.c #include <stdio.h> #include "input.h"void input_int(int *a, int *b) {printf("input two nums: ");scanf("%d %d&q…

8年经验的面试官分享Android工程师的面试秘诀

本人目前在一家知名企业担任Android高级工程师&#xff0c;工作八年来面试不过不少Android工程师求职者&#xff0c;前后累积有两三百人。在本文内容中&#xff0c;小编将结合本人的面试经验&#xff0c;给各位正在找Android开发工作的小伙伴提出一些面试前的准备建议。 一&am…

vue3通用后台管理项目

一、创建项目 1、使用vite创建名为my-vue的项目&#xff1a;npm init vite-app my-vue 2、进入到my-vue文件夹下&#xff1a;cd my-vue 3、安装依赖包&#xff1a;npm install 4、运行该项目&#xff1a;npm run dev 二、引入element-plus 1、element-plus地址&#xff1a;http…

Camunda整体架构

REST API REST API 允许您从远程应用程序或 JavaScript 应用程序使用流程引擎。&#xff08;注意&#xff1a;REST API 的文档被分解为自己的文档。&#xff09;REST API Reference | docs.camunda.org Camunda Tasklist用于人工工作流管理和用户任务的 Web 应用程序&#xff0…

UniverSeg:通用医学图像分割模型来了!

自从今年以来ChatGPT爆火和GPT-4的发布&#xff0c;一时间在大模型的潮流下&#xff0c;通用人工智能&#xff08;AGI&#xff09;也呼之欲出。随着本月初SAM和SegGPT等通用的CV大模型的提出&#xff0c;大模型和通用模型这把火也逐渐烧到的CV领域&#xff0c;特别是图像分割领…

Linux应用编程(文件IO进阶)

一、Linux 系统如何管理文件 1.1、静态文件与 inode 文件存放在磁盘文件系统中&#xff0c;并且以一种固定的形式进行存放&#xff0c;我们把他们称为静态文件。 每一个文件都必须对应一个 inode&#xff0c;inode 实质上是一个结构体&#xff0c;这个结构体中有很多的元素&a…

【C语言】初阶指针(指针运算、二级指针及指针数组)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 指针运算 4.1 指针-整数 1.2 指针 - 指针 1.3 指针的关系运算 2. 指针和数组 3. 二级指针 4. 指针数组 1. 指针运算 指针-整数指针-指针指针的关系运算 4.1 指针-整数 上面这个程序的作用是将数组中每个元…

【攻城狮计划】Renesas RA2E1 开发板

&#x1f6a9;WRITE IN FRONT&#x1f6a9; &#x1f50e;介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四"&#x1f50e;&#x1f3c5;荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2022博客之星TOP10…

Scalable Recognition with a Vocabulary Tree(词汇树)

视觉单词 参考 视觉词袋&#xff08;BoVW&#xff0c;Bag of Visual Words&#xff09;模型&#xff0c;是“词袋”&#xff08;BoW&#xff0c;Bag of Words&#xff09;模型从自然语言处理与分析领域向图像处理与分析领域的一次自然推广。对于任意一幅图像&#xff0c;BoVW模…