【驱动】块设备驱动(四)-块设备驱动层

前言
块设备驱动程序是Liux块子系统中的最底层组件。它们从IO调度程序中获得请求,然后按要求处理这些请求。一个块设备驱动程序可能处理几个块设备。例如,IDE设备驱动程序可以处理几个IDE磁盘,其中的每个都是一个单独的块设备。而且,每个磁盘通常是被分区的,每个分区又可以被看作是一个逻辑块设备。

核心数据结构
block_device
block_device结构代表了内核中的一个块设备。它可以表示整个磁盘或一个特定的分区。当这个结构代表一个分区时,它的bd_contains成员指向包含这个分区的设备,bd_part成员指向设备的分区结构。当这个结构代表一个块设备时,bd_disk成员指向设备的gendisk结构。

1
struct block_device {
2
dev_t bd_dev; /* not a kdev_t - it’s a search key /
3
int bd_openers;
4
struct inode * bd_inode; /
will die /
5
struct super_block * bd_super;
6
struct mutex bd_mutex; /
open/close mutex /
7
struct list_head bd_inodes;
8
void * bd_claiming;
9
void * bd_holder;
10
int bd_holders;
11
bool bd_write_holder;
12
#ifdef CONFIG_SYSFS
13
struct list_head bd_holder_disks;
14
#endif
15
struct block_device * bd_contains;
16
unsigned bd_block_size;
17
struct hd_struct * bd_part;
18
/
number of times partitions within this device have been opened. /
19
unsigned bd_part_count;
20
int bd_invalidated;
21
struct gendisk * bd_disk;
22
struct request_queue * bd_queue;
23
struct list_head bd_list;
24
/

25
* Private data. You must have bd_claim’ed the block_device
26
* to use this. NOTE: bd_claim allows an owner to claim
27
* the same device multiple times, the owner must take special
28
* care to not mess up bd_private for that case.
29
/
30
unsigned long bd_private;
31

32
/
The counter of freeze processes /
33
int bd_fsfreeze_count;
34
/
Mutex for freeze */
35
struct mutex bd_fsfreeze_mutex;
36
};
bd_dev:设备号,用作搜索键而不是kdev_t类型。

bd_openers:设备打开者的计数器。

bd_inode:指向相关的inode结构体的指针(已弃用)。

bd_super:指向相关的super_block结构体的指针。

bd_mutex:用于保护设备的开启和关闭操作的互斥锁。

bd_inodes:一个链表头,用于保存使用此设备的inode结构体。

bd_claiming:指向声明该设备的指针。

bd_holder:指向持有该设备的指针。

bd_holders:持有该设备的计数器。

bd_write_holder:表示设备是否由写操作的持有者。

bd_holder_disks:一个链表头,用于保存持有该设备的磁盘。

bd_contains:指向包含该设备的block_device结构体的指针。

bd_block_size:设备的块大小。

bd_part:指向相关的hd_struct结构体的指针。

bd_part_count:该设备的分区被打开的次数。

bd_invalidated:表示设备是否已失效。

bd_disk:指向相关的gendisk结构体的指针。

bd_queue:指向相关的request_queue结构体的指针。

bd_list:一个链表头,用于将该设备添加到全局设备列表中。

bd_private:私有数据。使用该数据之前,必须使用bd_claim声明对设备的所有权。需要注意的是,bd_claim允许一个所有者多次声明相同的设备,所有者必须特别注意不要破坏针对该情况的bd_private。

bd_fsfreeze_count:冻结进程的计数器。

bd_fsfreeze_mutex:用于冻结操作的互斥锁。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

register_blkdev
register_blkdev函数用于注册块设备,并将主设备号与设备名进行映射。它使用一个链表数组major_names来存储不同主设备号的映射关系,并使用互斥锁来确保并发访问的安全性。通过动态分配内存和链表操作,函数可以有效地管理和分配主设备号,并提供适当的错误处理。

1
int register_blkdev(unsigned int major, const char *name)
2
{
3
struct blk_major_name **n, p;
4
int index, ret = 0;
5

6
mutex_lock(&block_class_lock);
7

8
/
temporary */
9
if (major == 0) {
10
for (index = ARRAY_SIZE(major_names)-1; index > 0; index–) {
11
if (major_names[index] == NULL)
12
break;
13
}
14

15
if (index == 0) {
16
printk(“register_blkdev: failed to get major for %s\n”,
17
name);
18
ret = -EBUSY;
19
goto out;
20
}
21
major = index;
22
ret = major;
23
}
24

25
p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);
26
if (p == NULL) {
27
ret = -ENOMEM;
28
goto out;
29
}
30

31
p->major = major;
32
strlcpy(p->name, name, sizeof(p->name));
33
p->next = NULL;
34
index = major_to_index(major);
35

36
for (n = &major_names[index]; *n; n = &(*n)->next) {
37
if ((*n)->major == major)
38
break;
39
}
40
if (!*n)
41
*n = p;
42
else
43
ret = -EBUSY;
44

45
if (ret < 0) {
46
printk(“register_blkdev: cannot get major %d for %s\n”,
47
major, name);
48
kfree§;
49
}
50
out:
51
mutex_unlock(&block_class_lock);
52
return ret;
53
}
使用互斥锁block_class_lock来锁定对块设备类的访问。这是为了确保在注册块设备时不会发生并发访问的问题。

如果传入的主设备号major为0,表示请求动态分配主设备号。

在上述条件块中,通过遍历major_names数组找到一个可用的主设备号。遍历是从数组末尾开始,找到第一个为NULL的元素位置,表示该位置的主设备号可用。将其赋值给major并将其作为返回值,表示成功分配的主设备号。

使用kmalloc函数动态分配一个struct blk_major_name结构体的内存,该结构体用于存储主设备号和设备名的映射关系。

将设备名name复制到struct blk_major_name结构体的name成员中。这里使用了strlcpy函数,确保不会发生缓冲区溢出。

将主设备号major转换为索引值index,用于在major_names数组中定位对应的链表。

在对应的链表中遍历,查找是否已经存在相同的主设备号major。

如果找到了相同的主设备号,表示已经被占用,将返回值ret设置为-EBUSY表示注册失败。否则,将新分配的struct blk_major_name结构体添加到链表的末尾。

如果ret小于0(即注册失败),则打印错误消息并释放分配的内存。

释放对块设备类的互斥锁,允许其他线程访问块设备类。

返回ret作为函数的结果,表示注册块设备的状态。如果成功,返回的是分配的主设备号;如果失败,返回的是相应的错误码。

blkdev_open
对于块设备文件的操作,通过block_dev伪文件系统来完成,open操作定义的函数为blkdev_open()

blkdev_open的主要任务有两个:1.获取设备的block_device信息。2.从gendisk中读取相关信息保存到block_device,同时建立数据结构之间的联系。

1
static int blkdev_open(struct inode * inode, struct file * filp)
2
{
3
struct block_device bdev;
4

5
/

6
* Preserve backwards compatibility and allow large file access
7
* even if userspace doesn’t ask for it explicitly. Some mkfs
8
* binary needs it. We might want to drop this workaround
9
* during an unstable branch.
10
*/
11
filp->f_flags |= O_LARGEFILE;
12

13
if (filp->f_flags & O_NDELAY)
14
filp->f_mode |= FMODE_NDELAY;
15
if (filp->f_flags & O_EXCL)
16
filp->f_mode |= FMODE_EXCL;
17
if ((filp->f_flags & O_ACCMODE) == 3)
18
filp->f_mode |= FMODE_WRITE_IOCTL;
19

20
bdev = bd_acquire(inode);
21
if (bdev == NULL)
22
return -ENOMEM;
23

24
filp->f_mapping = bdev->bd_inode->i_mapping;
25

26
return blkdev_get(bdev, filp->f_mode, filp);
27
}
设置文件标志O_LARGEFILE,以支持对大文件的访问。这是为了保持向后兼容性,在不显式要求的情况下允许对大文件的访问。某些mkfs二进制文件可能需要这个设置。

检查文件标志O_NDELAY是否被设置。如果是,则设置文件模式FMODE_NDELAY,表示以非阻塞模式打开文件。

检查文件标志O_EXCL是否被设置。如果是,则设置文件模式FMODE_EXCL,表示以独占模式打开文件。

检查文件访问模式是否为O_RDWR,即读写模式。如果是,则设置文件模式FMODE_WRITE_IOCTL,表示允许通过IOCTL进行写操作。

根据给定的inode获取对应的块设备block_device。bd_acquire函数负责获取块设备的引用计数,确保块设备在文件打开期间不会被卸载。

检查获取块设备是否失败。如果获取失败,则返回错误码-ENOMEM,表示内存不足。

将文件的映射关系设置为块设备的i_mapping。这是为了确保文件系统能够正确地将读写操作转发到块设备。

调用blkdev_get函数以确保块设备的引用计数递增,并执行必要的打开操作。函数将返回打开操作的结果。

blkdev_read_iter
blkdev_read_iter函数用于在块设备上执行读取操作。它首先获取块设备的大小和当前位置信息,然后检查是否已经达到或超出了块设备的大小。根据剩余可读取的字节数,调整目标迭代器的长度。最后,调用通用的文件读取函数generic_file_read_iter进行实际的读取操作,并返回读取操作的结果。

我们具体分析下generic_file_read_iter。generic_file_read_iter函数中的这部分代码用于执行通用文件的读取操作。它根据iocb中的标志判断是否进行直接IO读取,然后根据情况调用相应的函数进行读取操作,并更新位置信息和迭代器。在特定条件下,它会跳过剩余的读取操作,并更新文件的访问时间。最后,它返回读取操作的结果。

1
ssize_t blkdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
2
{
3
struct file *file = iocb->ki_filp;
4
struct inode *bd_inode = file->f_mapping->host;
5
loff_t size = i_size_read(bd_inode);
6
loff_t pos = iocb->ki_pos;
7

8
if (pos >= size)
9
return 0;
10

11
size -= pos;
12
iov_iter_truncate(to, size);
13
return generic_file_read_iter(iocb, to);
14
}
15

16
ssize_t
17
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
18
{
19
struct file *file = iocb->ki_filp;
20
ssize_t retval = 0;
21
loff_t *ppos = &iocb->ki_pos;
22
loff_t pos = *ppos;
23

24
if (iocb->ki_flags & IOCB_DIRECT) {
25
struct address_space *mapping = file->f_mapping;
26
struct inode inode = mapping->host;
27
size_t count = iov_iter_count(iter);
28
loff_t size;
29

30
if (!count)
31
goto out; /
skip atime */
32
size = i_size_read(inode);
33
retval = filemap_write_and_wait_range(mapping, pos,
34
pos + count - 1);
35
if (!retval) {
36
struct iov_iter data = *iter;
37
retval = mapping->a_ops->direct_IO(iocb, &data, pos);
38
}
39

40
if (retval > 0) {
41
ppos = pos + retval;
42
iov_iter_advance(iter, retval);
43
}
44

45
/

46
* Btrfs can have a short DIO read if we encounter
47
* compressed extents, so if there was an error, or if
48
* we’ve already read everything we wanted to, or if
49
* there was a short read because we hit EOF, go ahead
50
* and return. Otherwise fallthrough to buffered io for
51
* the rest of the read. Buffered reads will not work for
52
* DAX files, so don’t bother trying.
53
*/
54
if (retval < 0 || !iov_iter_count(iter) || *ppos >= size ||
55
IS_DAX(inode)) {
56
file_accessed(file);
57
goto out;
58
}
59
}
60

61
retval = do_generic_file_read(file, ppos, iter, retval);
62
out:
63
return retval;
64
}
获取与iocb(kiocb结构体)相关联的文件对象file。

loff_t *ppos = &iocb->ki_pos;:获取指向iocb中位置信息的指针ppos。

loff_t pos = *ppos;:将当前位置信息保存到变量pos中。

if (iocb->ki_flags & IOCB_DIRECT) { … }:检查iocb中的标志IOCB_DIRECT是否被设置。如果设置了该标志,表示执行直接IO(Direct I/O)操作。

在直接IO操作的条件块中,首先获取文件的地址空间mapping和对应的索引节点inode。然后获取读取操作的字节数count。如果字节数为0,则跳过访问时间更新(atime)的步骤。

调用filemap_write_and_wait_range函数,确保在进行直接IO读取之前,将文件中的数据写回存储设备并等待完成。该函数将返回写入操作的结果。

如果写入操作成功(retval为0),则复制iter到新的data迭代器,并调用文件地址空间的direct_IO操作进行直接IO读取。该函数将返回读取操作的结果。

如果读取操作返回的字节数retval大于0,表示读取成功,更新当前位置ppos和iter的偏移,并继续进行后续的读取操作。

接下来的条件块用于处理特定情况下的直接IO读取。例如,如果读取出现错误(retval小于0),或者已经读取完所有数据(iov_iter_count(iter)为0),或者已经读取到文件末尾(*ppos >= size),或者文件是DAX文件(IS_DAX(inode)),则跳过剩余的读取操作,并更新文件的访问时间。

如果上述条件均不满足,则调用do_generic_file_read函数执行通用文件读取操作。该函数将处理剩余的读取操作,并更新位置信息和迭代器。

最后,跳转到标签out处,并返回读取操作的结果retval。

blkdev_write_iter
blkdev_write_iter函数用于在块设备上执行写入操作。它首先检查块设备是否为只读模式,以及输入迭代器中是否有数据可写入。然后,根据当前位置和块设备的大小调整输入迭代器的长度。接着,通过调用通用的文件写入函数进行实际的写入操作,并返回写入操作的结果。如果写入成功,还会进行同步写入操作,确保数据真正写入块设备。最后,返回写入操作的结果。

1
ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
2
{
3
struct file *file = iocb->ki_filp;
4
struct inode *bd_inode = file->f_mapping->host;
5
loff_t size = i_size_read(bd_inode);
6
struct blk_plug plug;
7
ssize_t ret;
8

9
if (bdev_read_only(I_BDEV(bd_inode)))
10
return -EPERM;
11

12
if (!iov_iter_count(from))
13
return 0;
14

15
if (iocb->ki_pos >= size)
16
return -ENOSPC;
17

18
iov_iter_truncate(from, size - iocb->ki_pos);
19

20
blk_start_plug(&plug);
21
ret = __generic_file_write_iter(iocb, from);
22
if (ret > 0) {
23
ssize_t err;
24
err = generic_write_sync(file, iocb->ki_pos - ret, ret);
25
if (err < 0)
26
ret = err;
27
}
28
blk_finish_plug(&plug);
29
return ret;
30
}
struct file *file = iocb->ki_filp;:获取与iocb(kiocb结构体)相关联的文件对象file。

struct inode *bd_inode = file->f_mapping->host;:获取文件对象对应的块设备的索引节点bd_inode。

loff_t size = i_size_read(bd_inode);:获取块设备的大小(文件大小)。

if (bdev_read_only(I_BDEV(bd_inode))) return -EPERM;:检查块设备是否为只读模式。如果是,直接返回错误码-EPERM,表示无法进行写入操作。

if (!iov_iter_count(from)) return 0;:检查输入迭代器from中的字节数是否为0。如果为0,表示没有数据可写入,直接返回0,表示写入操作已完成。

if (iocb->ki_pos >= size) return -ENOSPC;:检查当前位置是否已经达到或超出了块设备的大小。如果是,返回错误码-ENOSPC,表示空间不足,无法进行写入操作。

iov_iter_truncate(from, size - iocb->ki_pos);:根据当前位置和块设备的大小,调整输入迭代器from的长度,确保只写入剩余可写入的字节数。

blk_start_plug(&plug);:启动块设备的批量操作,将后续的写入操作收集到一个批处理中。

ret = __generic_file_write_iter(iocb, from);:调用通用的文件写入函数__generic_file_write_iter,执行写入操作。将iocb和输入迭代器from传递给该函数进行写入操作,并返回写入操作的结果。

如果写入操作返回的字节数ret大于0,表示写入成功,执行以下代码块:

a. ssize_t err;:定义变量err用于保存同步写入操作的结果。

b. err = generic_write_sync(file, iocb->ki_pos - ret, ret);:调用通用的写入同步函数generic_write_sync,将写入的起始位置和字节数传递给该函数进行同步写入操作,并将结果保存到err中。

c. if (err < 0) ret = err;:如果同步写入操作返回的结果小于0,表示出现错误,将错误码保存到ret中。

blk_finish_plug(&plug);:结束块设备的批量操作。

返回写入操作的结果ret。

__generic_file_write_iter
__generic_file_write_iter函数用于实际执行文件写入操作。它会进行一系列的检查和操作,包括移除特权标志、更新修改时间戳和调用适当的子函数来处理直接IO或标准缓冲区写入。需要注意的是,对于O_SYNC写入,该函数不会处理数据同步的问题,需要调用者自行处理。这主要是因为希望避免在持有i_mutex时进行数据同步操作。

1
ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
2
{
3
struct file *file = iocb->ki_filp;
4
struct address_space * mapping = file->f_mapping;
5
struct inode inode = mapping->host;
6
ssize_t written = 0;
7
ssize_t err;
8
ssize_t status;
9

10
/
We can write back this queue in page reclaim /
11
current->backing_dev_info = inode_to_bdi(inode);
12
err = file_remove_privs(file);
13
if (err)
14
goto out;
15

16
err = file_update_time(file);
17
if (err)
18
goto out;
19

20
if (iocb->ki_flags & IOCB_DIRECT) {
21
loff_t pos, endbyte;
22

23
written = generic_file_direct_write(iocb, from, iocb->ki_pos);
24
/

25
* If the write stopped short of completing, fall back to
26
* buffered writes. Some filesystems do this for writes to
27
* holes, for example. For DAX files, a buffered write will
28
* not succeed (even if it did, DAX does not handle dirty
29
* page-cache pages correctly).
30
/
31
if (written < 0 || !iov_iter_count(from) || IS_DAX(inode))
32
goto out;
33

34
status = generic_perform_write(file, from, pos = iocb->ki_pos);
35
/

36
* If generic_perform_write() returned a synchronous error
37
* then we want to return the number of bytes which were
38
* direct-written, or the error code if that was zero. Note
39
* that this differs from normal direct-io semantics, which
40
* will return -EFOO even if some bytes were written.
41
/
42
if (unlikely(status < 0)) {
43
err = status;
44
goto out;
45
}
46
/

47
* We need to ensure that the page cache pages are written to
48
* disk and invalidated to preserve the expected O_DIRECT
49
* semantics.
50
/
51
endbyte = pos + status - 1;
52
err = filemap_write_and_wait_range(mapping, pos, endbyte);
53
if (err == 0) {
54
iocb->ki_pos = endbyte + 1;
55
written += status;
56
invalidate_mapping_pages(mapping,
57
pos >> PAGE_CACHE_SHIFT,
58
endbyte >> PAGE_CACHE_SHIFT);
59
} else {
60
/

61
* We don’t know how much we wrote, so just return
62
* the number of bytes which were direct-written
63
*/
64
}
65
} else {
66
written = generic_perform_write(file, from, iocb->ki_pos);
67
if (likely(written > 0))
68
iocb->ki_pos += written;
69
}
70
out:
71
current->backing_dev_info = NULL;
72
return written ? written : err;
73
}
struct file *file = iocb->ki_filp;:获取与iocb(kiocb结构体)相关联的文件对象file。

struct address_space *mapping = file->f_mapping;:获取文件对象对应的地址空间mapping。

struct inode *inode = mapping->host;:获取地址空间对应的索引节点inode。

current->backing_dev_info = inode_to_bdi(inode);:将当前进程的backing_dev_info字段设置为inode对应的块设备信息。

err = file_remove_privs(file);:移除文件对象的特权标志。这是为了确保写入操作不会以特权身份执行。

err = file_update_time(file);:更新文件的修改时间戳。

如果iocb->ki_flags中包含IOCB_DIRECT标志,表示执行直接IO(direct I/O),则执行以下代码块:

a. 定义变量pos和endbyte,用于记录写入的起始位置和结束位置。

b. written = generic_file_direct_write(iocb, from, iocb->ki_pos);:调用通用的直接写入函数generic_file_direct_write,执行直接IO操作,并返回已写入的字节数。

c. 如果写入操作未完成(written < 0)或输入迭代器中没有数据可写入(!iov_iter_count(from)),或者inode是DAX文件(IS_DAX(inode)为真),则跳转到out标签。

d. status = generic_perform_write(file, from, pos = iocb->ki_pos);:调用通用的执行写入操作的函数generic_perform_write,执行标准缓冲区写入操作,并将写入的起始位置保存到pos中,返回写入的状态码。

e. 如果status小于0,表示写入操作返回了同步错误,将错误码保存到err中,并跳转到out标签。

f. 计算写入操作的结束位置endbyte = pos + status - 1。

g. err = filemap_write_and_wait_range(mapping, pos, endbyte);:将页高速缓存中的数据写入磁盘,并等待写入操作完成。

h. 如果err为0,表示写入操作成功,更新iocb的位置iocb->ki_pos为endbyte + 1,累加已写入的字节数到written中,并使映射页无效。

i. 如果err不为0,表示写入操作出现错误,由于无法确定实际写入了多少字节,因此不做处理。

如果不是直接IO操作,则执行以下代码块:

a. written = generic_perform_write(file, from, iocb->ki_pos);:调用通用的执行写入操作的函数generic_perform_write,执行标准缓冲区写入操作,并返回已写入的字节数。

b. 如果已写入的字节数大于0,则更新iocb的位置iocb->ki_pos为当前位置加上已写入的字节数。

out:标签处的代码用于清理操作,将current->backing_dev_info字段重置为NULL。

返回已写入的字节数written,如果没有写入任何数据,则返回错误码err。

generic_write_sync
generic_write_sync函数用于根据文件的打开标志和文件的映射索引节点的同步属性,判断是否需要执行同步写入操作。如果满足同步条件,则调用vfs_fsync_range函数执行同步写入操作。

1
static inline int generic_write_sync(struct file *file, loff_t pos, loff_t count)
2
{
3
if (!(file->f_flags & O_DSYNC) && !IS_SYNC(file->f_mapping->host))
4
return 0;
5
return vfs_fsync_range(file, pos, pos + count - 1,
6
(file->f_flags & __O_SYNC) ? 0 : 1);
7
}
vfs_fsync_range
vfs_fsync_range函数用于将指定文件的指定范围内的数据和元数据同步到磁盘。它会检查文件是否定义了fsync函数,并根据参数决定是否写入元数据。在写入元数据之前,它会清除索引节点状态中的相应标志位,并将索引节点标记为已修改。最后,它调用文件的fsync函数来执行实际的同步操作。

1
int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync)
2
{
3
struct inode *inode = file->f_mapping->host;
4

5
if (!file->f_op->fsync)
6
return -EINVAL;
7
if (!datasync && (inode->i_state & I_DIRTY_TIME)) {
8
spin_lock(&inode->i_lock);
9
inode->i_state &= ~I_DIRTY_TIME;
10
spin_unlock(&inode->i_lock);
11
mark_inode_dirty_sync(inode);
12
}
13
return file->f_op->fsync(file, start, end, datasync);
14
}
struct inode *inode = file->f_mapping->host;:获取文件file对应的索引节点inode。

if (!file->f_op->fsync):检查文件的文件操作函数指针f_op中是否定义了fsync函数。如果未定义,则返回错误码-EINVAL。

if (!datasync && (inode->i_state & I_DIRTY_TIME)):如果不是仅执行数据同步,并且索引节点的状态中标志位I_DIRTY_TIME被设置。

spin_lock(&inode->i_lock);:获取索引节点的自旋锁,用于保护对索引节点状态的修改。

inode->i_state &= ~I_DIRTY_TIME;:清除索引节点状态中的I_DIRTY_TIME标志位,表示元数据已被写回。

spin_unlock(&inode->i_lock);:释放索引节点的自旋锁。

mark_inode_dirty_sync(inode);:将索引节点标记为已修改,需要同步到磁盘。

return file->f_op->fsync(file, start, end, datasync);:调用文件的fsync函数,将数据和元数据同步到磁盘。该函数由文件系统提供,并提供了特定文件系统的实现。

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

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

相关文章

vulnhub靶场之Thales

一.环境搭建 1.靶场描述 Description : Open your eyes and change your perspective includes 2 flags:user.txt and root.txt. Telegram: machineboy141 (for any hint) This works better with VIrtualBox rathe than VMware 2.靶场地址 https://www.vulnhub.com/entry/t…

ensp实验合集(二)

实验6 VLAN划分....................................................................... - 30 - 实验7 路由器调试及常用命令使用........................................ - 42 - 实验8 配置静态路由器............................................................…

GCC编译器的使用以及使用Makefile语法进行操控

Makefile 这里使用的Makefile操控编译器 gcc 常见的组成部分 c&#xff1a; gcc 的一个版本&#xff0c;默认语言设置为 C&#xff0c;而且在链接的时候自动包含标准 C 库。这和 g 一样configure&#xff1a; GCC 源代码树根目录中的一个脚本。用于设置配置值和创建 GCC 编…

pytest中fixture的使用方法

一、pytest中的fixture是什么 为可靠的和可重复执行的测试提供固定的基线&#xff08;可以理解为测试的固定配置&#xff0c;使不同范围的测试都能够获得统一的配置&#xff09;&#xff0c;fixture提供了区别于传统单元测试&#xff08;setup/teardown&#xff09;风格的令人…

2024最新版鸿蒙HarmonyOS开发工具安装使用指南

2024最新版鸿蒙HarmonyOS开发工具安装使用指南 By JacksonML 0. 什么是鸿蒙Harmony OS&#xff1f; 华为鸿蒙系统&#xff08;HUAWEI Harmony OS&#xff09;&#xff0c;是华为公司在2019年8月9日于东莞举行的华为开发者大会&#xff08;HDC.2019&#xff09;上正式发布的分…

蓝桥杯每日一题-----数位dp练习

题目 链接 参考代码 写了两个&#xff0c;一个是很久以前写的&#xff0c;一个是最近刚写的&#xff0c;很久以前写的时候还不会数位dp所以写了比较详细的注释&#xff0c;这两个代码主要是设置了不同的记忆数组&#xff0c;通过这两个代码可以理解记忆数组设置的灵活性。 im…

redis源码之:集群创建与节点通信(1)

一、创建集群与添加节点&#xff08;meet&#xff09; 通过redis源码之&#xff1a;redis-cli 集群命令发现&#xff0c;不管是新建cluster集群还是往集群里添加新节点&#xff0c;都是通过meet指令完成&#xff0c;假设有ABCD四个节点&#xff0c;新建集群&#xff1a;redis-…

Vue学习笔记之组件基础

1、组件的定义 一般将 Vue 组件定义在一个单独的 .vue 文件中&#xff0c;称做单文件组件&#xff1b;当然也可以将组件直接定义在js文件中&#xff0c;如下js代码&#xff0c;定义一个组件BlogPost&#xff0c;通过props定义对外暴露属性title&#xff0c;父组件传递title&am…

分享62个节日PPT,总有一款适合您

分享62个节日PPT&#xff0c;总有一款适合您 62个节日PPT下载链接&#xff1a;https://pan.baidu.com/s/1mheNtIvXknGHse44FW7nOw?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…

2023年全球软件架构师峰会(ArchSummit上海站):核心内容与学习收获(附大会核心PPT下载)

微服务架构是当今软件架构的主流趋势之一。随着云计算和分布式系统的普及&#xff0c;越来越多的企业开始采用微服务架构来构建他们的应用。微服务架构可以将一个大型的应用拆分成多个小型的服务&#xff0c;每个服务都独立部署、独立运行&#xff0c;并通过轻量级的通信协议进…

非常好看的CSS加载中特效,引用css文件既可用

非常好看的CSS加载中特效 demo效果源码&#xff1a; <!DOCTYPE html5> <head><link rel"stylesheet" type"text/css" href"demo.css"/><link rel"stylesheet" type"text/css" href"loaders.css&…

【Qt解决】QIcon图标不显示以及LNK2019: 无法解析的外部符号问题

一句话解决 qmake重新构建&#xff0c;然后build&#xff0c;然后run 原因剖析 QIcon图标不显示 首先确保 qrc 文件已经添加对应图标文件&#xff0c;但是仍然不显示是因为没有编译新文件 LNK2019: 无法解析的外部符号 明明已经定义的槽函数&#xff0c;还是报这个错&…

开发板有线连主机,主机无线上网,开发板上网

网络配置&#xff1a; 以太网4连接开发板 wlan设置共享 vmwave需要禁用&#xff08;否则占用共享地址192.168.137.1&#xff09; 开发板 /etc/netplan 目录下&#xff1a;xx.yaml 00-installer-config.yaml /etc/resolv.conf route -n ifconfig 可以ping主机 可ping自己…

SpringBoot中的WebMvcConfigurer

SpringBoot中的WebMvcConfigurer 一、WebMvcConfigurer二、页面跳转控制器三、数据格式化1.Formatter\<T>2.内容转换器 四、拦截器 一、WebMvcConfigurer WebMvcConfigurer 作为配置类&#xff0c;采用 JavaBean 的形式来代替传统的 XML 配置文件形式&#xff0c;进而针…

SpringSecurity(18)——OAuth2授权码管理

AuthorizationCodeServices public interface AuthorizationCodeServices {//为指定的身份验证创建授权代码。String createAuthorizationCode(OAuth2Authentication authentication);//使用授权码。OAuth2Authentication consumeAuthorizationCode(String code)throws Invali…

C/C++实现无序入参的命令解析工具

C/C实现无序入参的命令解析工具 1 实现思路2 主要功能3 效果展示3.1 直接运行代码图3.2help命令执行效果图3.3命令行执行命令解析效果图 4 代码实现5 代码下载 1 实现思路 基本介绍&#xff1a; 思路来源于atlas,atc(模型转换工具)&#xff0c;该工具对命令支持众多&#xff0…

机器学习的整个流程

机器学习的整个流程定义了数据科学团队执行以创建和交付机器学习模型的工作流。此外&#xff0c;机器学习流程还定义了团队如何协作合作&#xff0c;以创建最有用的预测模型。 机器学习high level的流程 机器学习流程的关键步骤包括问题探索&#xff08;Problem Exploration&a…

幻兽帕鲁服务器怎么搭建?Palworld多人联机教程

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

http伪造本地用户字段系列总结

本篇记录了http伪造本地用户的多条字段&#xff0c;便于快速解决题目 用法举例&#xff1a; 直接把伪造本地用户的多个字段复制到请求头中&#xff0c;光速解决部分字段被过滤的问题。 Client-IP: 127.0.0.1 Forwarded-For-Ip: 127.0.0.1 Forwarded-For: 127.0.0.1 Forwarded…

(4)【Python数据分析进阶】Machine-Learning模型与算法应用-回归、分类模型汇总

线性回归、逻辑回归算法应用请参考: https://codeknight.blog.csdn.net/article/details/135693621https://codeknight.blog.csdn.net/article/details/135693621本篇主要介绍决策树、随机森林、KNN、SVM、Bayes等有监督算法以及无监督的聚类算法和应用PCA对数据进行降维的算法…