Linux内核启动过程1

在这里插入图片描述

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。关注公粽号 《机器和智能》 回复关键词 “python项目实战” 即可获取美哆商城视频资源!


博主介绍:
CSDN博客专家,CSDN优质创作者,CSDN实力新星,CSDN内容合伙人;
阿里云社区专家博主;
华为云社区云享专家;
51CTO社区入驻博主,掘金社区入驻博主,支付宝社区入驻博主,博客园博主。


三、Linux内核的启动过程

      • Linux内核的生成过程
      • C语言程序是如何启动并运行的
      • BIOS、MBR、GRUB、setup
        • BIOS加电自检
        • MBR系统引导
        • GRUB
        • kernel
        • init进程
        • vmlinux、vmlinuz、zImage、bzImage
        • setup辅助程序
      • 内核解压
      • 页面映射
      • 链接脚本


专栏:《Linux内核设计思想与源码分析》


Linux内核的生成过程

内核的生成步骤可以概括如下:

① 先生成 vmlinux,这是一个elf可执行文件。
② 然后 objcopy 成 arch/i386/boot/compressed/vmlinux.bin,去掉了原 elf 文件中一些无用的section等信息。
③ gzip 后压缩为 arch/i386/boot/compressed/vmlinux.bin.gz。
④ 把压缩文件作为数据段链接成 arch/i386/boot/compressed/piggy.o。
⑤ 链接:arch/i386/boot/compressed/vmlinux = head.o+misc.o+piggy.o ,其中 head.o 和 misc.o 是用来解压缩的。
⑥ objcopy 成 arch/i386/boot/vmlinux.bin,去掉了原 elf 文件中一些无用的 section等信息。
⑦ 用 arch/i386/boot/tools/build.c 工具拼接 bzImage = bootsect+setup+vmlinux.bin。

想要探寻Linux内核的启动,可以先从一个普通的用户态C程序的启动来入手。

C语言程序是如何启动并运行的

首先写一个最简单的C程序 test.c

  1 #include <stdio.h>2 3 int main()4 {5     printf("hello world ...\n");6     return 0;7 }

我们都知道,C程序的入口时 main 函数,那么下面来深入探索一下为什么是 main 函数,首先使用 gcc 生成可执行文件

gcc test.c -o test

在这里插入图片描述

通过 file 命令我们可以查看到,生成的test是一个 ELF 格式的文件,具体来说是一个x86平台上的64位可执行文件。

使用 objdump 命令对该执行文件反汇编

在这里插入图片描述

查看反汇编代码,我们发现汇编代码中多出了很多函数,比如 _strart 函数等,这说明gcc在链接时链接了一些库。

在这里插入图片描述

我们重新使用gcc编译一下test.c文件,并显示出编译过程

gcc -v test.c -o test

在这里插入图片描述

我们可以看到,在编译链接过程中,链接了 /usr/lib/… 下的 crti.o crt1.o 等文件,而刚才提到的 _strart 函数就定义在 crt1.o 文件中。

使用 readelf 命令查看 elf 文件头,section table 和各section信息。

在这里插入图片描述

在 ELF Header 中可以看到 Entry point address 一行,也就是程序的入口地址。通过这个信息可以知道,test程序的真正入口地址是0x400440,我们在查看一下反汇编代码

在这里插入图片描述

可以看到0x400440地址处正是 _strart 函数。

由此可知,_start 函数才是test程序首先执行的函数,该函数执行完一系列初始化等工作后,经过层层调用,最终调用main()函数。因此,在我们的C程序中,main()函数时开始执行的入口。

在这里插入图片描述

_strart调用了__libc_start_main@plt,同样,后面是层层调用的过程到达main()函数。

现在,我们搞明白了为什么main()函数是C程序的入口了,那么test程序具体是如何执行的呢。我们可以通过 strace 跟踪test的执行过程。

strace ./test

在这里插入图片描述

可以看到很多函数调用。第一个调用是 execve() 函数,这是一个关键的系统调用,它负责载入test可执行文件并运行。其中最关键的一步就是把用户态的 eip 寄存器(实际上是它在内存中对应的值)设置为elf文件中的入口点址,也就是 _start() 函数。

由此可见,程序从哪里开始执行,取决于在刚开始执行的那一刻 eip 寄存器的值。而这个 eip 是由Linux内核设置的。具体过程如下:
① 用户在shell里运行 ./test ;
② shell(这里是bash)调用系统调用execve();
③ execve() 陷入内核执行 sys_execve(),把用户态的eip设置为_start();

④ 当系统调用执行完毕,test进程开始运行时,就从_start()开始执行;
⑤ test进程执行main();

BIOS、MBR、GRUB、setup

通过上面用户态C程序的启动,我们可以看出,程序真正的入口是 _strart ,而main()只是因为被调用了,所以才被看做是C程序的入口,main只是一个符号,如果 _strart 中调用的不是main,那么C程序的入口就不是main了。

同理,内核的真正入口也并不是 strart_kernel() ,真正决定程序执行入口的是载入程序。对于用户态的C程序test来说,Linux内核或者说bash负责设置test的入口点,并且启动执行test程序。

那么谁来启动Linux内核呢,基于KISS(keep it simple and stupid)原则,一个简单的思路就是用一个更简单的内核(或者说一段程序)来启动真正的内核,也就是BIOS(basic input/output system),BIOS通常存储在ROM上。

PC机器刚启动时,x86 CPU 会自动从BIOS开始启动,这是由硬件决定的。刚加电时,寄存器CS里面的值是0xffff,IP寄存器值为0,于是CPU会从0xffff0处开始取指令,通过下面命令可以看到,0xffff0地址位于 System ROM 中,也就是我们的BIOS。

在这里插入图片描述

BIOS通过 POST(Power On Self Test,加电自检) 来加载硬件信息,进行内存、CPU、主板等检测,如果硬件设备正常工作,BIOS会寻找硬盘第一个扇区(引导扇区MBR)中存储的数据(512字节),并使用MBR中的数据激活引导加载程序。这就是BIOS的作用,后面的工作将有其它程序负责。

在x86平台上,有两种保护模式,32位页式寻址的保护模式和32位段式寻址的保护模式。32位页式寻址的保护模式要求系统中已经构造好页表。从16位实地址模式直接到32位页式寻址的保护模式是很有难度的,因为要在16位实地址模式下构造页表。所以不妨这样来做,先从16位实地址模式跳到32位段式寻址的保护模式,再从32位段式寻址的保护模式跳到32位页式寻址的保护模式。也就是说,我们需要这样一个程序,负责从16位实地址模式跳到32位段式寻址的保护模式,然后设置eip,启动内核。这个程序就是 arch/i386/boot/setup.S。最后它汇编成setup程序。

在这里插入图片描述

事实上,平时所见的压缩内核映象 bzImage 是由三部分组成的。可以从 arch/i386/boot/tools/build.c 中看到。build.c是用户态工具build的源代码,后者用来把 bootsect(MBR),setup辅助程序和vmlinux.bin(压缩过的内核) 拼接成 bzImage。
现在有了负责启动内核的setup程序,但是谁来启动setup呢。因为MBR只有512个字节,而且有64个字节来存放主分区表,它的功能是非常有限的。所以,还需要在setup和MBR之间再架一座桥梁。这就是引导程序,引导程序用来引导setup程序。现有的引导程序如grub,lilo不仅功能强大,而且还提供了人机交互的功能。

这样,我们就可以归纳出来一系列流程如下:

① CPU加电,从0xffff0处,执行BIOS(可以理解为“硬件”引导BIOS)。
② BIOS执行扫描检测设备的操作,然后将MBR读到物理地址为0x7c00处,然后从MBR头部开始执行(可以理解为BIOS引导MBR)。
③ MBR上的代码跳转到引导程序,开始执行引导程序的代码,例如grub(引以理解为BIOS引导boot loader)。
④ 引导程序把内核映象(包括bootsect,setup,vmlinux)读到内存中,其中setup位于0x90200处,如果是zImage,则vmlinux.bin位于0x10000(64K)处。如果是bzImage,则vmlinux.bin位于0x100000(1M)处。然后执行setup(可以理解为boot loader引导setup)。
⑤ setup负责引导linux内核vmlinux.bin。

BIOS ——> MBR ——> boot loader ——> setup ——> kernel ——> init

下面介绍下上面的这些名词都是什么含义。

BIOS加电自检
  • BIOS全称 Basic Input/Output System,即基本输入输出系统,它是一个被永久刻录在ROM中的软件,加电自检是指 Power On Self Test,POST,属于BIOS的主要组成部分。
  • 计算机在接通电源后,BIOS通过POST来加载硬件信息,进行内存、CPU、主板等检测,如果硬件设备正常工作,BIOS会寻找硬盘第一个扇区中存储的数据,并使用MBR中的数据激活引导加载程序。
MBR系统引导

第一个扇区(512字节)称为主引导记录。主引导记录分为3部分,前446byte是引导信息,后64byte是磁盘分区信息,最后2byte是标志位。MBR的作用是找到 boot loader 。

  • MBR全程 Master Boot Recode,是一种磁盘分区格式,也是以此种格式的磁盘中0盘片0扇区中存储的一段记录——主引导记录。磁盘中扇区的大小为512byte,主引导记录MBR占据第一个扇区的前446字节,剩余的空间依次存储一个64字节的磁盘分区表,和一个用于标识MBR是否有效的2字节的模数。
  • 主引导记录MBR中包含一个实现引导加载功能的程序——Boot Loader。由于BIOS只能访问很少量的数据,所以MBR中的引导加载程序其实只是一段初始程序的加载程序 Initial Program Loader,IPL,这段程序唯一的功能就是定位并加载 Boot Loader 的主体程序。
  • 加载引导分为两个阶段
    • 第一阶段,BIOS引导IPL获取 Boot Loader 主题程序在磁盘中的位置,此时系统启动的控制权由BIOS转移到MBR;
    • 第二阶段,Boot Loader 主题程序与操作系统对应的内核,定位到内核文件所在的位置,并将其加载到计算机内存中,此时系统启动的控制权由MBR转移到内核。
GRUB

是一种 boot loader ,用于加载kernel核心信息。

kernel

内核。

init进程

内核的第一个程序,分为7个启动级别。

查看启动级别配置文件

cat /etc/inittab  #查看启动级别相关的配置文件

inti命令可以切换系统的启动级别

inti 0/1/2/3/4/5/6
  • 0表示关机(不能设置为开机默认启动级别)
  • 1表示单用户
  • 2表示多用户(无网络的3级别)
  • 3多用户(命令行模式,字符终端)
  • 4用于开发
  • 5图形界面,默认启动方式
  • 6reboot(不能设置为开机默认启动级别)
runlevel   #查看系统的启动级别
vmlinux、vmlinuz、zImage、bzImage

vmlinuz是可引导的、压缩的内核,“vm"代表"Virtual Memory”。Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名"vm"。vmlinuz是可执行的Linux内核,它位于 /boot/vmlinuz,它一般是一个软链接。vmlinuz的建立有两种方式。一是编译内核时通过"make zImage"创建,然后通过"cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage /boot/vmlinuz"产生。zImage
适用于小内核的情况,它的存在是为了向后的兼容性。二是内核编译时通过命令make bzImage创建,然后通过"cp /usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz"产生。bzImage是压缩的内核映像,bz表示"big zImage"。
zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip 或 gzip -dc解包vmlinuz。内核文件中包含一个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage 或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。vmlinux是未压缩的内核,vmlinuz是vmlinux的压缩文件。

vmlinux是一个包含 linux kernel 的静态链接的可执行文件,文件类型是linux接受的可执行文件格式之一(ELF、COFF或a.out)。

vmlinuz是可引导的,压缩的linux内核,“vm”代表的“virtual memory”。vmlinuz是vmlinux经过gzip和 objcopy(*) 制作出来的压缩文件。vmlinuz不仅是一个压缩文件,而且在文件的开头部分内嵌有gzip解压缩代码。
在这里插入图片描述

通过file命令可以看到自己的vmlinuz是bzImage。通过前面我们知道,zImage是vmlinuz经过gzip压缩后的文件,适用于小内核(512KB以内),加载到内存的开始640KB处。bzimage(not bzizp but big)是vmlinuz经过gzip压缩后的文件,适用于大内核。在zImage和bzImage都可以通过解压缩提取出vmlinux,只不过提取方法不同。

setup辅助程序

setup辅助程序主要进行了这些操纵,以zImage为例,首先把它从0x10000拷贝到0x1000,调用BIOS功能,查询硬件信息,然后放在内存中供将来的内核使用,然后建立临时的idt和gdt,负责把16位实地址模式转化为32位段式寻址的保护模式。

我们可以查看下 arch/i386/boot/setup.S 中的汇编代码

在这里插入图片描述

# Note that the short jump isn't strictly needed, although there are
# reasons why it might be a good idea. It won't hurt in any case.movw	$1, %ax				# protected mode (PE) bitlmsw	%ax				# This is it!jmp	flush_instrflush_instr:xorw	%bx, %bx			# Flag to indicate a bootxorl	%esi, %esi			# Pointer to real-mode codemovw	%cs, %sisubw	$DELTA_INITSEG, %sishll	$4, %esi			# Convert to 32-bit pointer
# NOTE: For high loaded big kernels we need a
#	jmpi    0x100000,__KERNEL_CS
#
#	but we yet haven't reloaded the CS register, so the default size
#	of the target offset still is 16 bit.
#       However, using an operant prefix (0x66), the CPU will properly
#	take our 48 bit far pointer. (INTeL 80386 Programmer's Reference
#	Manual, Mixing 16-bit and 32-bit code, page 16-6).byte 0x66, 0xea			# prefix + jmpi-opcode
code32:	.long	0x1000			# will be set to 0x100000# for big kernels.word	__KERNEL_CS

x86处理器提供了特殊的手段来访问大于1M的内存,那就是在指令前加前缀0x66(具体可见上面程序块)。由于跳转地址与内核大小相关(zImage和bzImage不一样)所以用一个小技巧,即把该指令
当作数据处理。在计算机看来,指令和数据是没什么区别的,只要ip寄存器指向内存中某地址,计算机就把地址中的数据当作指令来看待。

关于我们上面提到的所有包括test,vmlinux,arch/i386/boot/compressed/vmlinux,setup,bootsect有什么区别与联系呢。
这几个可执行文件都是由gcc编译生成的,只是格式不一样。其中,test,vmlinux,arch/i386/boot/compressed/vmlinux都是elf32_i386格式的可执行文件;setup,bootsect是binary格式的可执行文件,它们的区别如下

  • text是普通的elf32_i386可执行文件,它的入口是 _start,运行在用户态空间,变量的地址都是32位页式寻址的保护模式的地址,存放在用户态空间,由shell负责装载。
  • vmlinux是未压缩的内核,它的入口是startup_32(0x100000,线性地址),运行在内核态空间,变量的地址是32位页式寻址的保护模式的地址,存放在内核态空间,由内核自解压后启动运行。
  • arch/i386/boot/compressed/vmlinux是压缩后的内核,它的入口地址是startup_32(0x100000,线性地址),运行在32位段式寻址的保护模式下,变量的地址是32位段式寻址的保护模式的地址,由setup启动运行。
  • setup是装载内核的binary格式的辅助程序,它的入口地址是begtext(偏移地址为0,运行时需要把cs段寄存器设置为0x9020),运行在16位实地址模式下。变量的地址等于相对于代码段起始地址的偏移地址。由boot loader启动运行。
  • bootsect是MBR上的引导程序,也为binary格式。它的入口地址是_start(),由于装载到0x7c00处,运行时需要把cs段寄存器设置为0x7c0。运行在16位实地址模式下。变量地址等于相对于代码段起始地址的偏移地址。由BIOS启动运行。

内核解压

在文件arch/i386/boot/compressed/head.S和arch/i386/kernel/head.S中都存在一个startup_32,那么这两个startup_32有什么区别呢,我们从内核的链接过程来看。

从内核的生成过程来看内核的链接主要有三步:
第一步是把内核的源代码编译成 .o 文件,然后链接arch/i386/kernel/head.S,生成vmlinux。注意这里的所有变量地址都是32位页寻址方式保护模式下的虚拟地址,通常大小在3G以上。
第二步将 vmlinux objcopy 成 arch/i386/boot/compressed/vmlinux.bin,然后压缩,最后作为数据编译成piggy.o。这时候,在编译器看来,piggy.o中还不存在startup_32。
第三步,把 head.o,misc.o 和 piggy.o 链接生成 arch/i386/boot/compressed/vmlinux,这一步,链接的是
arch/i386/boot/compressed/head.S。这时 arch/i386/kernel/head.S 中的 startup_32 被压缩,作为一段普通的数据,而被编译器忽视。这里的地址都是32位段寻址方式保护模式下的线性地址。
setup执行完毕,跳转到vmlinux.bin中的startup_32()是arch/i386/boot/compressed/head.S中的startup_32()这是一段自解压程序,过程和内核生成的过程正好相反。这时,CPU处在32位段寻址方式的保护模式下,寻址范围从1M扩大到4G。只是没有页表。内核解压完毕。位于0x100000即1M处。最后,执行一条跳转指令,执行0x100000处的代码,即 arch/i386/kernel/head.S 中的startup_32()代码。

页面映射

通过setup辅助程序,现在从16位实地址模式过渡到了32位段式寻址保护模式。并且在 arch/i386/boot/compressed/head.S 帮助下实现了内核自解压,从arch/i386/kernel/head.S中的startup_32开始执行。现在,线性地址0x100000(1M) 处开始便是解压后的内核,而startup_32的地址也是0xa00000。但是现在还没有开启页面映射,所以必须引用变量的线性地址,也就是变量的虚拟地址-PAGE_OFFSET。要想解决这个问题,就要建立页表并开启页面映射。

在Linux中,每个进程都拥有一个页表,也就是说每个页表都对应着一个进程。通常情况下,Linux通过fork()系统调用复制原进程来产生一个新的进程,那么问题来了,第一个进程是怎么来的呢。实际上,第一个进程并不是复制出来的,它是以全局变量的方式制造出来的,即 init_thread_union,也就是我们所说的0号进程swapper。swapper进程运行后调用start_kernel(),整个程序就跑起来了。

有了第一个进程,还要为该进程建立页表。为保证可移植性,Linux采用了三级页表(但是x86处理器只使用两级页表),swapper的页表叫做swapper_pg_dir,在arch/i386/kernel/head.S中我们可以看到这样的代码

/** This is initialized to create an identity-mapping at 0-8M (for bootup* purposes) and another mapping of the 0-8M area at virtual address* PAGE_OFFSET.*/
.org 0x1000
ENTRY(swapper_pg_dir).long 0x00102007.long 0x00103007.fill BOOT_USER_PGD_PTRS-2,4,0/* default: 766 entries */.long 0x00102007.long 0x00103007/* default: 254 entries */.fill BOOT_KERNEL_PGD_PTRS-2,4,0

这样,便建好了页目录,下面开始映射页表。我们知道,一个页目录最多可以映射1024个页表,每个页表可以映射4M虚拟地址,也就是说总共可以映射4G虚拟地址空间。

由于不同进程的用户空间相互独立,所以用户态进程的地址映射并不是连续的。但是,所有进程共享内核态代码和数据。内核态虚拟地址从3G开始,而内核代码和数据事实上是从物理地址0x100000开始,本着KISS原则,加上3G就作为对应的虚拟地址即可。由此可见,对内核态代码和数据来说:虚拟地址=物理地址+PAGE_OFFSET(3G)。

建表过程可见下面代码

/** Initialize page tables*/movl $pg0-__PAGE_OFFSET,%edi /* initialize page tables */movl $007,%eax		/* "007" doesn't mean with right to kill, butPRESENT+RW+USER */
2:	stosladd $0x1000,%eaxcmp $empty_zero_page-__PAGE_OFFSET,%edijne 2b

上面有一条注释 /* “007” doesn’t mean with right to kill, but PRESENT+RW+USER */,由于每个页表项有32位,但其实只需保存物理地址的高20位就够了,所以剩下的低12位可以用来表示页的属性。0x007正好表示PRESENT+RW+USER(在内存中,可读写,用户页面,这样在用户态和内核态都可读写,从而实现平滑过渡)。

下面就要开启分页功能。开启页面映射后,可以直接引用内核中的所有变量了。不过离start_kernel还有点距离,要启动swapper进程,得首先设置内核堆栈。看下面程序

/** Enable paging 开启分页功能*/
3:movl $swapper_pg_dir-__PAGE_OFFSET,%eaxmovl %eax,%cr3		/* set the page table pointer.. */movl %cr0,%eaxorl $0x80000000,%eaxmovl %eax,%cr0		/* ..and set paging (PG) bit */jmp 1f			/* flush the prefetch-queue */
1:movl $1f,%eaxjmp *%eax		/* make sure eip is relocated */
1:/* Set up the stack pointer */lss stack_start,%esp#ifdef CONFIG_SMPorw  %bx,%bxjz  1f				/* Initial CPU cleans BSS */pushl $0popfljmp checkCPUtype
1:
#endif CONFIG_SMP
/** start system 32-bit setup. We need to re-do some of the things done* in 16-bit mode for the "real" operations.*/call setup_idt
call SYMBOL_NAME(start_kernel)

链接脚本

在用户态,内核会解析elf可执行文件的各个section,并把它们映射到虚拟地址空间,这些都不需要用户关心。但是,在内核启动的时候,映射section等这些工作都必须要内核自己来完成。除此之外,内核还要负责对BSS段的变量进行初始化(一般会初始化为0),这些都需要内核知道section的具体位置。这就要求,存在那么一个文件来指定各个section的虚拟地址。在内核源码树中,arch/i386/kernel/vmlinux.lds.S 文件就是 linker scripts 连接器脚本。

在链接器脚本中,. 表示当前 location counter 地址计数器的值,默认为0。

#ifdef CONFIG_X86_32. = LOAD_OFFSET + LOAD_PHYSICAL_ADDR;phys_startup_32 = ABSOLUTE(startup_32 - LOAD_OFFSET);
#else. = __START_KERNEL;phys_startup_64 = ABSOLUTE(startup_64 - LOAD_OFFSET);
#endif

. = __START_KERNEL; 表示地址计数器从__KERNEL_START(0xc00100000) 开始。

下面代码描述了 .text section 包含了哪些section

/* Text and read-only data */.text :  AT(ADDR(.text) - LOAD_OFFSET) {_text = .;_stext = .;/* bootstrapping code */HEAD_TEXTTEXT_TEXTSCHED_TEXTCPUIDLE_TEXTLOCK_TEXTKPROBES_TEXTSOFTIRQENTRY_TEXT
#ifdef CONFIG_RETPOLINE__indirect_thunk_start = .;*(.text.__x86.*)__indirect_thunk_end = .;
#endifSTATIC_CALL_TEXTALIGN_ENTRY_TEXT_BEGINENTRY_TEXTALIGN_ENTRY_TEXT_END*(.gnu.warning)} :text =0xcccc

. = ALIGN(PAGE_SIZE); 表示对齐方式。

链接器脚本指定了各个section的起始位置和结束位置。它还允许程序员在脚本中对
变量进行赋值。这使内核可以通过 __initcall_start 和 __initcall_end之类的变量获得段的起始地址和结束地址,从而对某些段进行操作。

下面是两个比较重要的section:

  • bss section,存放在代码里未初始化的全局变量,最后初始化为0。
  • init sections,所有只在初始化时调用的函数和变量,包括所有在内核启动时调用的函数,以及内核模块初始化时调用的函数。其中最特别的是.initcall .init section。通过__initcall_start和__initcall_end,内核可以调用里面所有的函数。这些section在使用一次后就可以释放,从而节省内存。

在这里插入图片描述

在这里插入图片描述


❗❗❗重要❗❗❗☞关注下方公粽号 《机器和智能》 回复关键词 “python项目实战” 即可获取美哆商城视频资源!

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

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

相关文章

阿里云+frp内网穿透工作站远程开机

frp 是一个可用于内网穿透的高性能的反向代理应用&#xff0c;支持 tcp, udp, http, https 等协议。利用 frp 和一个带有公网 IP 的云服务器作为中间跳板&#xff0c;可以实现内网穿透&#xff0c;不在家的时候也可以访问到家里面的工作站。有了这个缺口之后&#xff0c;在外面…

基于STM32单片机的恒温焊台的设计

本设计以STM32F401CBU6为主控控制芯片&#xff0c;包含电压采集模块&#xff0c;温度控制模块&#xff0c;输入模块和OLED显示模块等。使用EC11编码器设置温度&#xff0c;选择开关机时间&#xff0c;当焊台开机后&#xff0c;就可以设置所需要的温度&#xff0c;这时的手柄开始…

BricsCAD 24:智能绘图与自动化,加速设计流程

BricsCAD是一款功能丰富、易于使用且具有良好兼容性的CAD软件。bricscad 24 mac一款集2D绘图和3D建模于一体的CAD软件&#xff0c;它由比利时Bricsys NV公司研发&#xff0c;界面与AutoCAD相近&#xff0c;易于上手。 BricsCAD 24 for mac v24.1.05 注册密钥下载 BricsCAD 21 …

MES系统中的正向追溯与反向追溯管理

随着制造业的日益发展&#xff0c;生产过程中的质量控制和管理变得尤为关键。MES系统作为一种实现车间生产管理和控制的重要工具&#xff0c;其追溯功能在生产过程中起着至关重要的作用。 一、MES系统概述 MES系统是一套面向制造企业车间执行层的生产信息化管理系统。它通过对…

1-laravel 搭建与路由基础

文章目录 laravel 环境搭建安装工程的命令 基于laravel 开发访问默认欢迎页面第一路由 laravel 环境搭建 借助 phpstudy 搭建环境 安装工程的命令 C:\phpstudy_pro\WWW>composer create-project --prefer-dist laravel/laravel la-3 安装位置 安装…

使用 Go 和 Gin 框架构建简单的用户和物品管理 Web 服务

使用 Go 和 Gin 框架构建简单的用户和物品管理 Web 服务 在本项目中&#xff0c;我们使用 Go 语言和 Gin 框架构建了一个简单的 Web 服务&#xff0c;能够管理用户和物品的信息。该服务实现了两个主要接口&#xff1a;根据用户 ID 获取用户名称&#xff0c;以及根据物品 ID 获…

大模型驱动机器狗——从UMI on Legs到Helpful DoggyBot:分别把机械臂装到机器狗背上、夹爪装到机器狗嘴里

前言 今年十一7天假期期间&#xff0c;一半的时间都在改本博客内的上一篇文章《从Fast-UMI到Diff-Control&#xff1a;分别改进UMI的硬件及其所用的Diffusion policy(含ControlNet详解)》&#xff0c;改完之后&#xff0c;接下来计划要写的博客包括且不限于 第1-2篇&#xff…

CDA数据分析师证书含金量到底如何?

为什么学习数据分析&#xff1f; 2024年&#xff0c;是一个被数据影响的时代。数据&#xff0c;如同无形的燃料&#xff0c;驱动着现代社会的运转。从全球互联网的用户每天产生的2.5亿TB数据&#xff0c;到制造业的传感器、金融交易、医疗病历等领域的海量信息&#xff0c;数据…

小红书爆款首图生成prompt v0.1

由于平时需要在小红书&#xff0c;抖音&#xff0c;公众号等自媒体平台发布一些内容&#xff0c;其中一个需求就是需要一个亮眼的首图&#xff0c;特别是小红书&#xff0c;首图效果好坏会直接决定推流的效果。 受到李继刚老师一系列 Prompt 的启发&#xff0c;创作了下面这个小…

牛客:xay loves count与1LL的用法

xay loves count 题目描述 登录—专业IT笔试面试备考平台_牛客网 运行代码 #include <bits/stdc.h> using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int n;cin >> n;int a[1000005] {0};int cnt[1000005] …

Linux内核启动过程2

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

en造数据结构与算法C# 之 动态规划

动态规划 动态规划和分治法很像&#xff0c;都是拆解问题解决 分治法常用递归算法来写&#xff0c;但是动态规划和分治法的最大不同就是存入值 &#xff0c;AI真方便 钢条切割问题 其实该问题最平常&#xff0c;也是最直接的思想就是先把前项最赚米的方案总结出来&…

JDBC: 连接池

文章目录 没有连接池的现状连接池解决现状问题的原理连接池好处常用连接池的介绍Druid连接池DRUID简介Druid常用的配置参数Druid连接池基本使用API介绍 案例代码 没有连接池的现状 通过下图来分析一下我们目前jdbc程序的结构。 以前使用的jdbc的缺点&#xff1a; 1、操作数据库…

SeaTunnel如何创建Socket数据同步作业?

本文为Apache SeaTunnel Socket Connector的使用文档&#xff0c;旨在帮助用户快速理解和有效利用Socket Connector&#xff0c;助力用户的应用程序实现高效、稳定的网络通信。 Socket是应用层与TCP/IP协议族之间进行通信的中间软件抽象层&#xff0c;它是网络编程的基础&…

Vue工程化结构环境安装及搭建教程 : 之nvm

vue需要的环境&#xff1a; node.js : Node.js和Vue.js通常会一起使用。Node.js作为后端服务器&#xff0c;处理服务器端的逻辑和数据访问&#xff0c;而Vue.js则负责前端用户界面的构建和交互。通过Ajax通信&#xff0c;Vue.js应用程序向Node.js服务器发送请求&#xff0c;并…

扩展、包含、泛化-系统架构师(七十七)

1&#xff08;&#xff09;是系统分析阶段结束后得到的工作产品&#xff0c;&#xff08;&#xff09;是系统测试阶段完成后的工作产品。 问题1 A系统设计规格说明 B系统方案建议书 C系统规格说明 D单元测试数据 问题2 A验收测试计划 B测试标准 C系统测试计划 D操作手…

基于STM32单片机的配电室环境监测系统

本设计了一个基于STM32单片机的配电室环境监测系统。该系统可以实现配电室环境温湿度检测、烟雾浓度检测和火焰信息检测&#xff0c;这主要是为了防止火灾发生&#xff1b;本系统还加入了红外人体检测模块&#xff0c;可以检测配电室周围是否有行人经过&#xff0c;最终将传感器…

极狐GitLab 发布安全补丁版本 17.4.1、17.3.4、17.2.8

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

Autodesk Flame 2025:视觉特效制作解决方案

Autodesk Flame 2025是一款功能强大的视觉特效制作解决方案&#xff0c;由Autodesk公司开发。它提供了出色的性能&#xff0c;为视觉特效艺术家成功完成制作项目提供了所需的交互性和灵活性。 以下是Autodesk Flame 2025的一些主要特点和功能&#xff1a; 高效的三维合成环境&…

基于BERT的深度强化学习求解图上的组合优化问题(未完)

文章目录 Abstract1 Introduction2 文献综述2.1 相关的深度学习方法2.2 基于强化学习的方法3 Methodology3.1 问题定义和预备知识3.2 策略网络架构Abstract 组合优化,如图上的车辆路径和旅行商问题,是NP-hard问题,几十年来一直被研究。已经提出了许多方法来解决这些问题,包…