Linux x86_64 平台下系统调用的实现

文章目录

  • 前言
  • 一、简介
  • 二、Defining a syscall with SYSCALL_DEFINEn()
    • 2.1 SYSCALL_METADATA
    • 2.2 __SYSCALL_DEFINEx
  • 三、Syscall table entries
  • 四、x86_64 syscall invocation
  • 参考资料

前言

本文来自 https://lwn.net/Articles/604287/

一、简介

系统调用(system calls)是用户空间程序与Linux内核进行交互的主要机制。由于其重要性,内核包含了各种机制,以确保系统调用可以在不同体系结构上进行通用实现,并以高效且一致的方式提供给用户空间。

这篇文章探讨了内核对系统调用(或syscalls)的实现细节。在本文中,我们将重点讨论主流情况下的机制:普通系统调用(read())的机制,以及允许x86_64用户程序调用它的机制。

对于x86(32bit)请参考:https://lwn.net/Articles/604515/

系统调用与常规函数调用不同,因为被调用的代码位于内核中。需要特殊指令来使处理器执行从用户态切换到特权态(ring 0)。此外,调用的内核代码通过系统调用号来标识,而不是函数地址。

当用户空间程序需要执行一个系统调用时,它会使用特定的指令(例如x86架构中的syscall指令)触发从用户态到内核态的切换。在进行切换时,处理器会将当前的上下文保存起来,包括寄存器状态和程序计数器等。然后,处理器会跳转到预定义的系统调用入口点,该入口点由系统调用号标识。

在内核中,系统调用表(system call table)维护了系统调用号与相应内核函数的映射关系。当处理器进入内核态并跳转到系统调用入口点时,内核会根据系统调用号找到对应的内核函数来执行相应的操作。内核函数完成后,处理器将恢复之前保存的上下文,并返回到用户空间程序继续执行。

通过使用系统调用号而不是函数地址,内核能够提供一种标准化的、跨平台的系统调用接口。不同的系统调用由唯一的系统调用号进行标识,这样用户空间程序可以使用相同的系统调用号在不同的操作系统上进行系统调用,而无需关心具体的内核实现。

因此,系统调用的机制涉及从用户态到内核态的切换、系统调用号的标识和匹配,以及内核中相应的处理逻辑,以实现用户空间程序与内核的交互。

二、Defining a syscall with SYSCALL_DEFINEn()

read()系统调用是一个很好的初始示例,可以用来探索内核的系统调用机制。它在fs/read_write.c中作为一个简短的函数实现,大部分工作由vfs_read()函数处理。从调用的角度来看,这段代码最有趣的地方是函数是如何使用SYSCALL_DEFINE3()宏来定义的。实际上,从代码中,甚至并不立即清楚该函数被称为什么。

// linux-3.10/fs/read_write.c

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
	struct fd f = fdget(fd);
	ssize_t ret = -EBADF;

	if (f.file) {
		loff_t pos = file_pos_read(f.file);
		ret = vfs_read(f.file, buf, count, &pos);
		file_pos_write(f.file, pos);
		fdput(f);
	}
	return ret;
}

这些SYSCALL_DEFINEn()宏是内核代码定义系统调用的标准方式,其中n后缀表示参数计数。这些宏的定义(在include/linux/syscalls.h中)为每个系统调用提供了两个不同的输出。

// include/linux/syscalls.h

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
// include/linux/syscalls.h

#define SYSCALL_DEFINEx(x, sname, ...)				\
	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
    SYSCALL_METADATA(_read, 3, unsigned int, fd, char __user *, buf, size_t, count)
    __SYSCALL_DEFINEx(3, _read, unsigned int, fd, char __user *, buf, size_t, count)
    {
    	struct fd f = fdget_pos(fd);
    	ssize_t ret = -EBADF;
    	/* ... */

2.1 SYSCALL_METADATA

其中之一是SYSCALL_METADATA()宏,用于构建关于系统调用的元数据,以便进行跟踪。只有在内核构建时定义了CONFIG_FTRACE_SYSCALLS时才会展开该宏,展开后它会生成描述系统调用及其参数的数据的样板定义。(单独的页面详细描述了这些定义。)

SYSCALL_METADATA()宏主要用于在内核中进行系统调用的跟踪和分析。当启用了CONFIG_FTRACE_SYSCALLS配置选项进行内核构建时,宏会展开,并生成一系列用于描述系统调用及其参数的元数据定义。这些元数据包括系统调用号、参数个数、参数类型等信息,用于记录和分析系统调用的执行情况。

通过使用SYSCALL_METADATA()宏,内核能够在编译时生成系统调用的元数据,以支持跟踪工具对系统调用的监控和分析。这些元数据的定义是一种样板代码,提供了系统调用的相关信息,帮助开发人员和调试工具在系统调用层面进行问题排查和性能优化。

2.2 __SYSCALL_DEFINEx

__SYSCALL_DEFINEx()部分更加有趣,因为它包含了系统调用的实现。一旦各种宏和GCC类型扩展层层展开,生成的代码包含一些有趣的特性:

#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...)					\
	asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));	\
	static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__));	\
	asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__))	\
	{								\
		long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));	\
		__MAP(x,__SC_TEST,__VA_ARGS__);				\
		__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));	\
		return ret;						\
	}								\
	SYSCALL_ALIAS(sys##name, SyS##name);				\
	static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))

    asmlinkage long sys_read(unsigned int fd, char __user * buf, size_t count)
    	__attribute__((alias(__stringify(SyS_read))));

    static inline long SYSC_read(unsigned int fd, char __user * buf, size_t count);
    asmlinkage long SyS_read(long int fd, long int buf, long int count);

    asmlinkage long SyS_read(long int fd, long int buf, long int count)
    {
    	long ret = SYSC_read((unsigned int) fd, (char __user *) buf, (size_t) count);
    	asmlinkage_protect(3, ret, fd, buf, count);
    	return ret;
    }

    static inline long SYSC_read(unsigned int fd, char __user * buf, size_t count)
    {
    	struct fd f = fdget_pos(fd);
    	ssize_t ret = -EBADF;
    	/* ... */
[root@localhost ~]# uname -r
3.10.0-693.el7.x86_64
[root@localhost ~]# cat /proc/kallsyms | grep '\<sys_read\>'
ffffffff812019e0 T sys_read
[root@localhost ~]# cat /proc/kallsyms | grep '\<SYSC_read\>'
[root@localhost ~]# cat /proc/kallsyms | grep '\<SyS_read\>'
ffffffff812019e0 T SyS_read

SYSC_read函数是 inline inline ,没有对应的符号。

首先,我们注意到系统调用的实现实际上被命名为SYSC_read(),但它是静态的,因此在该模块之外是无法访问的。相反,有一个封装函数名为SyS_read(),并在外部以sys_read()的别名可见。仔细观察这些别名,我们注意到它们的参数类型存在差异 - sys_read()期望显式声明的类型(例如第二个参数的char __user *),而SyS_read()只期望一组(long)整数。深入研究其历史,我们发现长整型版本确保对于某些64位内核平台,32位值得到了正确的符号扩展,从而避免了一个历史性的漏洞。

在SyS_read()封装函数中,我们还注意到了asmlinkage指令和asmlinkage_protect()函数调用。《Kernel Newbies FAQ》提供了有用的解释,asmlinkage指令表示函数应该从堆栈中获取参数,而不是从寄存器中获取。而asmlinkage_protect()函数的通用定义解释了它的作用,即防止编译器假设可以安全地重用堆栈的这些区域。

除了sys_read()(带有准确类型的变体)的定义外,还在include/linux/syscalls.h中进行了声明。这使得其他内核代码可以直接调用系统调用的实现(在几个地方会这样调用)。通常情况下,不鼓励从内核的其他位置直接调用系统调用,并且这种情况并不常见。

// include/linux/syscalls.h

asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);

三、Syscall table entries

寻找调用sys_read()的函数还有助于了解用户空间如何调用该函数。对于没有提供自己覆盖的"通用"架构,include/uapi/asm-generic/unistd.h文件中包含了一个引用sys_read的条目:

// include/uapi/asm-generic/unistd.h

#define __NR_read 63
__SYSCALL(__NR_read, sys_read)

这个定义为read()定义了通用的系统调用号__NR_read(63),并使用__SYSCALL()宏以特定于体系结构的方式将该号码与sys_read()关联起来。例如,arm64使用asm-generic/unistd.h头文件填充一个表格,将系统调用号映射到实现函数指针。

然而,我们将集中讨论x86_64架构,它不使用这个通用表格。相反,x86_64架构在arch/x86/syscalls/syscall_64.tbl中定义了自己的映射,其中包含sys_read()的条目:

// arch/x86/syscalls/syscall_64.tbl

#
# 64-bit system call numbers and entry vectors
#
# The format is:
# <number> <abi> <name> <entry point>
#
# The abi is "common", "64" or "x32" for this file.
#
0	common	read			sys_read
1	common	write			sys_write
2	common	open			sys_open
3	common	close			sys_close
4	common	stat			sys_newstat
......

这表明在x86_64架构上,read()的系统调用号为0(不是63),并且对于x86_64的两种ABI(应用二进制接口),即sys_read(),有一个共同的实现。(关于不同的ABI将在本系列的第二部分中讨论。)syscalltbl.sh脚本从syscall_64.tbl表生成arch/x86/include/generated/asm/syscalls_64.h文件,具体为sys_read()生成对__SYSCALL_COMMON()宏的调用。然后,该头文件用于填充syscall表sys_call_table,这是一个关键的数据结构,将系统调用号映射到sys_name()函数。

// arch/x86/syscalls/syscalltbl.sh

#!/bin/sh

in="$1"
out="$2"

grep '^[0-9]' "$in" | sort -n | (
    while read nr abi name entry compat; do
	abi=`echo "$abi" | tr '[a-z]' '[A-Z]'`
	if [ -n "$compat" ]; then
	    echo "__SYSCALL_${abi}($nr, $entry, $compat)"
	elif [ -n "$entry" ]; then
	    echo "__SYSCALL_${abi}($nr, $entry, $entry)"
	fi
    done
) > "$out"

在x86_64架构中,syscalltbl.sh脚本使用syscall_64.tbl表格生成了arch/x86/include/generated/asm/syscalls_64.h文件。其中,对于sys_read()的定义会包含类似以下的代码:

__SYSCALL_COMMON(0, sys_read)

这个宏的调用将系统调用号0和sys_read()函数关联起来。然后,arch/x86/include/generated/asm/syscalls_64.h文件会被其他代码引用,用于填充sys_call_table数据结构。

sys_call_table是一个数组,其中每个元素对应一个系统调用号,它将系统调用号映射到相应的sys_name()函数。在这种情况下,sys_read()函数将与系统调用号0关联起来,以便当用户空间发起sys_read()的系统调用请求时,内核可以根据系统调用号从sys_call_table中找到sys_read()函数并执行。这样,内核就能正确处理用户空间对read()的系统调用请求。

四、x86_64 syscall invocation

现在我们将看一下用户空间程序如何调用系统调用。这是与体系结构相关的,因此在本文的剩余部分,我们将集中讨论x86_64架构(其他x86架构将在本系列的第二篇文章中进行讨论)。调用过程涉及几个步骤,如下图所示:
在这里插入图片描述
在前面的部分中,我们发现了一个系统调用函数指针表;对于x86_64,这个表格大致如下(使用GCC的一个数组初始化扩展,确保任何缺少的条目指向sys_ni_syscall()):

typedef void (*sys_call_ptr_t)(void);

extern void sys_ni_syscall(void);

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
	/*
	 * Smells like a compiler bug -- it doesn't work
	 * when the & below is removed.
	 */
	[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
};
    asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    	[0 ... __NR_syscall_max] = &sys_ni_syscall,
    	[0] = sys_read,
    	[1] = sys_write,
    	/*... */
    };

对于64位代码,这个表格可以从arch/x86/kernel/entry_64.S中的system_call汇编入口点进行访问;它使用RAX寄存器选择数组中的相关条目,并调用它。

// arch/x86/kernel/entry_64.S

/*
 * Register setup:
 * rax  system call number
 * rdi  arg0
 * rcx  return address for syscall/sysret, C arg3
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 	(--> moved to rcx for C)
 * r8   arg4
 * r9   arg5
 * r11  eflags for syscall/sysret, temporary for C
 * r12-r15,rbp,rbx saved by C code, not touched.
 *
 * Interrupts are off on entry.
 * Only called from user space.
 *
 * XXX	if we had a free scratch register we could save the RSP into the stack frame
 *      and report it properly in ps. Unfortunately we haven't.
 *
 * When user can change the frames always force IRET. That is because
 * it deals with uncanonical addresses better. SYSRET has trouble
 * with them due to bugs in both AMD and Intel CPUs.
 */

ENTRY(system_call)
	......
GLOBAL(system_call_after_swapgs)

	movq	%rsp,PER_CPU_VAR(old_rsp)
	movq	PER_CPU_VAR(kernel_stack),%rsp
	/*
	 * No need to follow this irqs off/on section - it's straight
	 * and short:
	 */
	ENABLE_INTERRUPTS(CLBR_NONE)
	SAVE_ARGS 8,0
	......
	movq %r10,%rcx
	call *sys_call_table(,%rax,8)  # XXX:	 rip relative
	movq %rax,RAX-ARGOFFSET(%rsp)

在函数的早期,SAVE_ARGS宏将各种寄存器推入栈中,以匹配之前我们看到的asmlinkage指令。

// arch/x86/include/asm/calling.h

	.macro SAVE_ARGS addskip=0, save_rcx=1, save_r891011=1
	subq  $9*8+\addskip, %rsp
	CFI_ADJUST_CFA_OFFSET	9*8+\addskip
	movq_cfi rdi, 8*8
	movq_cfi rsi, 7*8
	movq_cfi rdx, 6*8

	.if \save_rcx
	movq_cfi rcx, 5*8
	.endif

	movq_cfi rax, 4*8

	.if \save_r891011
	movq_cfi r8,  3*8
	movq_cfi r9,  2*8
	movq_cfi r10, 1*8
	movq_cfi r11, 0*8
	.endif

	.endm

这个宏的目的是将函数调用中的参数保存到内核栈上,以便在函数执行过程中可以访问这些参数。通过在函数调用前使用这个宏,可以将参数保存到指定的寄存器和栈空间中,以便在函数执行过程中使用。

在向外扩展的过程中,system_call入口点本身在syscall_init()函数中被引用,该函数在内核启动序列的早期被调用:

void syscall_init(void)
{
	/*
	 * LSTAR and STAR live in a bit strange symbiosis.
	 * They both write to the same internal register. STAR allows to
	 * set CS/DS but only a 32bit target. LSTAR sets the 64bit rip.
	 */
	wrmsrl(MSR_STAR,  ((u64)__USER32_CS)<<48  | ((u64)__KERNEL_CS)<<32);
	wrmsrl(MSR_LSTAR, system_call);
	wrmsrl(MSR_CSTAR, ignore_sysret);
	......

wrmsrl指令用于向特定于模型的寄存器(Model-Specific Register,MSR)写入一个值。在这种情况下,它将通用的system_call系统调用处理函数的地址写入了寄存器MSR_LSTAR(0xc0000082),该寄存器是用于处理x86_64架构中的SYSCALL指令的特定于模型的寄存器。

在x86_64架构中,SYSCALL指令用于从用户空间转移到内核空间,执行系统调用。当发生SYSCALL指令时,处理器会读取MSR_LSTAR寄存器中的地址,并跳转到该地址执行系统调用处理函数。

通过将通用system_call系统调用处理函数的地址写入MSR_LSTAR寄存器,内核告诉处理器将SYSCALL指令的控制权转移到该函数的地址。这样,当用户空间程序发起系统调用时,处理器会跳转到相应的system_call系统调用处理函数,从而执行相应的系统调用操作。

SYSCALL指令:
在这里插入图片描述
SYSCALL指令在特权级0下调用操作系统的系统调用处理程序。它通过从IA32_LSTAR MSR(特定于模型的寄存器)加载RIP(指令指针)来实现这一点,同时将SYSCALL指令后面的指令地址保存到RCX寄存器中。(WRMSR指令确保IA32_LSTAR MSR始终包含规范地址)。

MSR_LSTAR寄存器:
在这里插入图片描述
这些信息足以将用户空间与内核代码联系起来。在x86_64架构中,用户程序调用系统调用的标准ABI是将系统调用号(例如,读取操作的系统调用号为0)放入RAX寄存器中,将其他参数放入特定寄存器中(第1个参数放入RDI,第2个参数放入RSI,第3个参数放入RDX),然后发出SYSCALL指令。这个指令会导致处理器转换到特权级0,并调用由MSR_LSTAR特定于模型的寄存器引用的代码(即system_call)。system_call代码将寄存器推入内核栈中,并调用sys_call_table表中entry RAX处的函数指针,也就是sys_read()。sys_read()是对真实实现SYSC_read()的asmlinkage包装器。

(1)
在Linux启动之时,会进行一系列的初始化过程。其中,系统调用的初始化在文件:

// arch/x86/kernel/cpu/common.c

void syscall_init(void)
{
	......
	wrmsrl(MSR_LSTAR, system_call);
	......
}

(2)

read() --> userspace
	-->SYSCALL指令
		-->RIP = IA32_LSTAR MSR = system_call
			-->sys_call_table[rax]
				-->sys_read()

参考资料

https://lwn.net/Articles/604287/
https://juejin.cn/post/7203681024236355639

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

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

相关文章

Unity 角色控制(初版)

角色控制器组件&#xff0c;当然是将组件放在角色上了。 using System.Collections; using System.Collections.Generic; using UnityEngine;public class c1 : MonoBehaviour {// 获取角色控制器private CharacterController player;void Start(){// 加载角色控制器player …

【物联网】stm32芯片结构组成,固件库、启动过程、时钟系统、GPIO、NVIC、DMA、UART以及看门狗电路的全面详解

一、stm32的介绍 1、概述 stm32: ST&#xff1a;指意法半导体 M&#xff1a;指定微处理器 32&#xff1a;表示计算机处理器位数 与ARM关系:采用ARM推出cortex-A&#xff0c;R,M三系中的M系列&#xff0c;其架构主要基于ARMv7-M实现 ARM分成三个系列&#xff1a; Cortex-A&…

机器人工具箱学习(二)

一、机械臂及运动学 1.1 机械臂构成 机械臂多采用关节式机械结构&#xff0c;一般具有6个自由度&#xff0c;其中3个用来确定末端执行器的位置&#xff0c;另外3个则用来确定末端执行装置的方向(姿态)。   如图所示&#xff0c;一个机械臂是由一组可做相对运动的关节连接的连…

Spring中Bean的作用域、实例化方式、生命周期、循环依赖问题

Spring中Bean的作用域、实例化方式、生命周期、循环依赖问题 一、Bean的作用域1.singleton2.prototype3.其他scope值 二、Bean的实例化方式1.通过构造方法实例化2.通过简单工厂模式实例化3.通过factory-bean实例化4.通过FactoryBean接口实例化5.BeanFactory和FactoryBean的区别…

http【详解】状态码,方法,接口设计 —— RestfuI API,头部 —— headers,缓存

http 状态码 1xx 服务器收到请求 2xx 请求成功 200 成功 3xx 重定向&#xff08;目标服务器返回另一个服务器的地址&#xff0c;浏览器会自动去访问另一个服务器&#xff09; 常见应用场景&#xff1a;搜索引擎&#xff0c;短网址 301 永久重定向 &#xff08;常用于已停服的…

05-prometheus的联邦模式-分布式监控

一、联邦模式概述 1&#xff0c;架构介绍 由于&#xff0c;在大型企业中&#xff0c;被监控项目比较多&#xff0c;多到一台prometheus服务无法承载其大量的监控数据的传输&#xff0c;所以&#xff0c;联邦模式应运而生&#xff0c;它同等于zabbix监控的分布式&#xff0c;就…

Web前端---表格和表单

1.表格概述 表格标记&#xff1a;<table></table> 表格标题标记&#xff1a;<caption></caption> 表头&#xff1a;<th></th>------heading 行标记&#xff1a;<tr></tr>-----r是row 列标记&#xff1a;<td></t…

2024年计算语言学与自然语言处理国际会议(CLNLP 2024) | Ei、Scopus双检索

会议简介 Brief Introduction 2024年计算语言学与自然语言处理国际会议(CLNLP 2024) 会议时间&#xff1a;2024年7月19日-21日 召开地点&#xff1a;中国银川 大会官网&#xff1a;www.clnlp.org CLNLP 2024将围绕“计算语言学与自然语言处理”的最新研究领域而展开&#xff0c…

用FPGA CORDIC IP核实现信号的相位检测,计算相位角

用FPGA CORDIC IP核实现信号的相位检测 1.matlab仿真 波形仿真代码&#xff1a; 代码功能&#xff1a;生成一个点频信号s&#xff0c;求出s的实部和虚部&#xff1b;并且结算相位角atan2。画出图形&#xff0c;并且将Q和I数据写入文件中。 %代码功能&#xff1a;生成一个点…

Linux 之一:Linux 简介、客户端、安装

Linux简介 Linux 内核最初只是由芬兰人林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在赫尔辛基大学上学时出于个人爱好而编写的。 Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹于…

Java 反射详解:动态创建实例、调用方法和访问字段

“一般情况下&#xff0c;我们在使用某个类之前已经确定它到底是个什么类了&#xff0c;拿到手就直接可以使用 new 关键字来调用构造方法进行初始化&#xff0c;之后使用这个类的对象来进行操作。” Writer writer new Writer(); writer.setName("少年");像上面这个…

HTTP常用状态码详解

目录 1xx - 信息性状态码 2xx - 成功状态码 3xx - 重定向状态码 4xx - 客户端错误状态码 5xx - 服务器错误状态码 总结 HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种用于传输超文本的应用层协议。在HTTP通信中&#xff0c;服务器和客户端之间会通过状态…

使用华为云云函数functiongraph

之前使用腾讯云serverless&#xff0c;但是突然开始收费了。所以改用functiongraph 首先登陆华为云。 目录 1.登录华为云 2.在控制台找到functiongraph并开通 3.添加依赖包&#xff1a; 3.1 制作依赖包 3.2引入依赖包 4.发送请求 4.1直接发送 4.1.1uri 4.1.2 请求头…

使用word写论文或项目书时,插入图片显示不完整或随文字移动解决办法

一、背景 虽然我们写科技论文时可以用Latex进行排版&#xff0c;但在写项目书或硕士论文时&#xff0c;一般给的模板都是word类型&#xff0c;所以我们还是不能放弃word的使用。但在word中插入图片时&#xff0c;会出现图片显示不完整情况&#xff0c;如下图所示&#xff0c;具…

单调队列(347. 前 K 个高频元素239. 滑动窗口最大值)

单调队列和单调栈其实差不多,就是维护一个区间单调的队列或者是栈,单调队列就是我们所说的大顶堆小顶堆, //升序队列 小顶堆 great 小到大 priority_queue <int,vector<int>,greater<int> > pri_que; //降序队列 大顶堆 less 大到小 默认 priority_qu…

xss.haozi.me:0x01

<textarea></textarea> 标签是不可以写入javascript代码的 所以我们要把textarea标签给闭合掉 这样就成功了 </textarea><script>alert(1)</script>

【Java设计模式】四、原型设计模式

文章目录 1、原型设计模式2、深克隆和浅克隆 1、原型设计模式 说明&#xff1a; 用一个原型对象&#xff0c;创建和原型对象相同的对象&#xff0c;以能够保证创建对象的性能是创建大量相同对象的最佳方式 使用场景&#xff1a; 对象的创建非常复杂&#xff0c;可以使用原型…

神经网络结构——CNN、RNN、LSTM、Transformer !!

文章目录 前言 一、什么是CNN 网络结构 解决问题 工作原理 实际应用 二、什么是RNN 网络结构 解决问题 工作原理 应用场景 三、什么是LSTM 网络结构 解决问题 工作原理 应用场景 四、什么是Transformer 网络结构 解决问题 工作原理 BERT GPT 前言 本文将从什么是CNN&#xff1…

使用 Footprint Analytics 提升 Web3 项目的空投活动

作者&#xff1a;stellafootprint.network 空投已成为 Web3 项目提升知名度、激励早期贡献者并吸引新用户的核心策略。尽管空投的概念一目了然——即向各类钱包免费发放代币&#xff0c;但要成功实施却需要周密的执行和战略规划。单纯的代币分发并非万全之策。在许多的空投后&a…

爬虫案例一

首先我举一个案例比如豆瓣电影排行榜 (douban.com)这个电影&#xff0c;首先我们进去检查源代码 说明源代码有&#xff0c;说明是服务器渲染&#xff0c;可以直接那html 但是返回的结果是空&#xff0c;所以我们需要在头里面加上User-Agent 然后可以看到有返回的结果&#xff0…
最新文章