《ARM Linux内核源码剖析》读书笔记——0号进程(init_task)的创建时机

最近在读《ARM Linux内核源码剖析》,一直没有看到0号进程(init_task进程)在哪里创建的。直到看到下面这篇文章才发现书中漏掉了set_task_stack_end_magic(&init_task)这行代码。

下面这篇文章提到:start_kernel()上来就会运行 set_task_stack_end_magic(&init_task)创建初始进程。init_task是静态定义的一个进程,也就是说当内核被放入内存时,它就已经存在,它没有自己的用户空间,一直处于内核空间中运行,并且也只处于内核空间运行。

书中代码:
在这里插入图片描述
内核源码:
在这里插入图片描述

一. 前言

前文分析到Linux内核正式启动,完成了实模式到保护模式的切换,并做好了各种准备工作。下来就要看开始内核初始化工作了,源码位置位于init/main.c中的start_kernel(),源码如附录所示。这包括了一系列重要的初始化工作,本文会介绍其中一部分较为重要的,但是详细的介绍依然会留在后文各个模块的源码学习中单独进行。本文的目的在于承接上文给出一个从内核启动到各个模块开始运转的过程介绍,而不是详细的各部分内容介绍。

创建0号进程:INIT_TASK(init_task)
异常处理类中断服务程序挂接:trap_init()
内存初始化:mm_init()
调度器初始化sched_init()
剩余初始化:rest_init()

二. 0号进程的创建

start_kernel()上来就会运行 set_task_stack_end_magic(&init_task)创建初始进程。init_task的定义是 struct task_struct init_task = INIT_TASK(init_task)。它是系统创建的第一个进程,我们称为 0 号进程。这是唯一一个没有通过 fork 或者 kernel_thread产生的进程,是进程列表的第一个。

如下所示为init_task的定义,这里只节选了部分,采用了gcc的结构体初始化方式为其进行了直接赋值生成。

/*

  • Set up the first task table, touch at your own risk!. Base=0,
  • limit=0x1fffff (=2MB)
    */
    struct task_struct init_task
    #ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
    __init_task_data
    #endif
    = {

    .state = 0,
    .stack = init_stack,
    .usage = REFCOUNT_INIT(2),
    .flags = PF_KTHREAD,
    .prio = MAX_PRIO - 20,
    .static_prio = MAX_PRIO - 20,
    .normal_prio = MAX_PRIO - 20,
    .policy = SCHED_NORMAL,
    .cpus_ptr = &init_task.cpus_mask,
    .cpus_mask = CPU_MASK_ALL,
    .nr_cpus_allowed = NR_CPUS,
    .mm = NULL,
    .active_mm = &init_mm,

    .thread_pid = &init_struct_pid,
    .thread_group = LIST_HEAD_INIT(init_task.thread_group),
    .thread_node = LIST_HEAD_INIT(init_signals.thread_head),

    };
    EXPORT_SYMBOL(init_task);
    init_task是静态定义的一个进程,也就是说当内核被放入内存时,它就已经存在,它没有自己的用户空间,一直处于内核空间中运行,并且也只处于内核空间运行。0号进程用于包括内存、页表、必要数据结构、信号、调度器、硬件设备等的初始化。当它执行到最后(剩余初始化)时,将start_kernel中所有的初始化执行完成后,会在内核中启动一个kernel_init内核线程和一个kthreadd内核线程,kernel_init内核线程执行到最后会通过execve系统调用执行转变为我们所熟悉的init进程,而kthreadd内核线程是内核用于管理调度其他的内核线程的守护线程。在最后init_task将变成一个idle进程,用于在CPU没有进程运行时运行它,它在此时仅仅用于空转。

三. 中断初始化

由代码可见,trap_init()设置了很多的中断门(Interrupt Gate),用于处理各种中断,如系统调用的中断门set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32)。

void trap_init(void)
{
int i;
//设置系统的硬件中断 中断位于kernel/asm.s 或 system_call.s
set_trap_gate(0,÷_error);//0中断,位于/kernel/asm.s 19行
set_trap_gate(1,&debug);
set_trap_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5 can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_trap_gate(14,&page_fault);
set_trap_gate(15,&reserved);
set_trap_gate(16,&coprocessor_error);
for (i=17;i<48;i++)
set_trap_gate(i,&reserved);
set_trap_gate(45,&irq13);
outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xdf,0xA1);
set_trap_gate(39,¶llel_interrupt);
}

四. 内存初始化

内存相关的初始化内容放在mm_init()中进行,代码如下所示

// init/main.c
/*

  • Set up kernel memory allocators
    /
    static void __init mm_init(void)
    {
    /
    • page_ext requires contiguous pages,
    • bigger than MAX_ORDER unless SPARSEMEM.
      /
      page_ext_init_flatmem();
      mem_init();
      kmem_cache_init();
      pgtable_init();
      vmalloc_init();
      ioremap_huge_init();
      /
      Should be run before the first non-init thread is created /
      init_espfix_bsp();
      /
      Should be run after espfix64 is set up. */
      pti_init();
      }
      调用的函数功能基本如名字所示,主要进行了以下初始化设置:

page_ext_init_flatmem()和cgroup的初始化相关,该部分是docker技术的核心部分
mem_init()初始化内存管理的伙伴系统
kmem_cache_init()完成内核slub内存分配体系的初始化,相关的还有buffer_init
pgtable_init()完成页表初始化,包括页表锁ptlock_init()和
vmalloc_init()完成vmalloc的初始化
ioremap_huge_init() ioremap实现I/O内存资源由物理地址映射到虚拟地址空间,此处为其功能的初始化
init_espfix_bsp()和pti_init()完成PTI(page table isolation)的初始化
此处不展开说明这些函数,留待后面内存管理部分详细分析各个部分。

五. 调度器初始化

调度器初始化通过sched_init()完成,其主要工作包括:

对相关数据结构分配内存:如初始化waitqueues数组,根据调度方式FAIR/RT设置alloc_size,调用kzalloc分配空间
初始化root_task_group:根据FAIR/RT的不同,将kzalloc分配的空间用于其初始化,主要结构task_group包含以下几个重要组成部分:se, rt_se, cfs_rq 以及 rt_rq。其中cfs_rq和rt_rq表示run queue,即一种特殊的per-cpu结构体用于内核调度器存储激活的线程。
调用for_each_possible_cpu()初始化每个possibleCPU(存储于cpu_possible_mask为图中)的runqueue队列(包括其中的cfs队列和实时进程队列),rq结构体是调度进程的基本数据结构,调度器用rq决定下一个将要被调度的进程。详细介绍会在调度一节进行。
调用set_load_weight(&init_task),将init_task进程转变为idle进程
需要说明的是init_task在这里会被转变为idle进程,但是它还会继续执行初始化工作,相当于这里只是给init_task挂个idle进程的名号,它其实还是init_task进程,只有到最后init_task进程开启了kernel_init和kthreadd进程之后,才转变为真正意义上的idle进程。

六. 剩余初始化

rest_init是非常重要的一步,主要包括了区分内核态和用户态、初始化1号进程和初始化2号进程。

6.1 内核态和用户态
在运行用户进程之前,尚需要完成一件事:区分内核态和用户态。x86 提供了分层的权限机制,把区域分成了四个 Ring,越往里权限越高,越往外权限越低。操作系统很好地利用了这个机制,将能够访问关键资源的代码放在 Ring0,我们称为内核态(Kernel Mode);将普通的程序代码放在 Ring3,我们称为用户态(User Mode)。
在这里插入图片描述

6.2 初始化1号进程
rest_init() 的一大工作是,用 kernel_thread(kernel_init, NULL, CLONE_FS)创建第二个进程,这个是 1 号进程。1 号进程对于操作系统来讲,有“划时代”的意义,因为它将运行一个用户进程,并从此开始形成用户态进程树。这里主要需要分析的是如何完成从内核态到用户态切换的过程。kernel_thread()代码如下所示,可见其中最主要的是第一个参数指针函数fn决定了栈中的内容,根据fn的不同将生成1号进程和后面的2号进程。

/*

  • Create a kernel thread.
    */
    pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    {
    struct kernel_clone_args args = {
    .flags = ((flags | CLONE_VM | CLONE_UNTRACED) & ~CSIGNAL),
    .exit_signal = (flags & CSIGNAL),
    .stack = (unsigned long)fn,
    .stack_size = (unsigned long)arg,
    };

    return _do_fork(&args);
    }
    这就说明,1 号进程运行的是一个文件。如果我们打开 run_init_process() 函数,会发现它调用的是 do_execve()。

static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(getname_kernel(init_filename),
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
接着会进行一系列的调用:do_execve->do_execveat_common->exec_binprm->search_binary_handler,这里search_binary_handler()主要是加载ELF文件(Executable and Linkable Format,可执行与可链接格式),代码如下

int search_binary_handler(struct linux_binprm *bprm)
{

struct linux_binfmt *fmt;

retval = fmt->load_binary(bprm);

}
这个结构就是在系统调用的时候,内核中保存用户态运行上下文的,里面将用户态的代码段 CS 设置为 __USER_CS,将用户态的数据段 DS 设置为 __USER_DS,以及指令指针寄存器 IP、栈指针寄存器 SP。这里相当于补上了原来系统调用里,保存寄存器的一个步骤。最后的 iret 是干什么的呢?它是用于从系统调用中返回。这个时候会恢复寄存器。从哪里恢复呢?按说是从进入系统调用的时候,保存的寄存器里面拿出。好在上面的函数补上了寄存器。CS 和指令指针寄存器 IP 恢复了,指向用户态下一个要执行的语句。DS 和函数栈指针 SP 也被恢复了,指向用户态函数栈的栈顶。所以,下一条指令,就从用户态开始运行了。

经过上述过程,我们完成了从内核态切换到用户态。而此时代码其实还在运行 kernel_init函数,会调用

if (!ramdisk_execute_command)
ramdisk_execute_command = “/init”;
结合上面的init程序,这里出现了第二个init。这是有其存在的必要性的:上文提到的 init 程序是在文件系统上的,文件系统一定是在一个存储设备上的,例如硬盘。Linux 访问存储设备,要有驱动才能访问。如果存储系统数目很有限,那驱动可以直接放到内核里面,反正前面我们加载过内核到内存里了,现在可以直接对存储系统进行访问。但是存储系统越来越多了,如果所有市面上的存储系统的驱动都默认放进内核,内核就太大了。这该怎么办呢?

我们只好先弄一个基于内存的文件系统。内存访问是不需要驱动的,这个就是 ramdisk。这个时候,ramdisk 是根文件系统。然后,我们开始运行 ramdisk 上的 /init。等它运行完了就已经在用户态了。/init 这个程序会先根据存储系统的类型加载驱动,有了驱动就可以设置真正的根文件系统了。有了真正的根文件系统,ramdisk 上的 /init 会启动文件系统上的 init。接下来就是各种系统的初始化。启动系统的服务,启动控制台,用户就可以登录进来了。

6.3 初始化2号进程
rest_init 另一大事情就是创建第三个进程,就是 2 号进程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)又一次使用 kernel_thread 函数创建进程。这里需要指出一点,函数名 thread 可以翻译成“线程”,这也是操作系统很重要的一个概念。从内核态来看,无论是进程,还是线程,我们都可以统称为任务(Task),都使用相同的数据结构,平放在同一个链表中。这里的函数kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。

kthreadd,即2号进程,用于内核态线程的管理,是一个守护线程。其源码如下所示,运行流程包括

初始化了task结构,并将该线程设置为允许任意CPU运行。
进入循环,将线程状态设置为TASK_INTERRUPTIBLE,如果当前kthread_create_list为空,没有要创建的线程,则执行schedule()让出CPU资源。
如果需要创建,则设置为TASK_RUNNING状态,加上锁spin_lock,从链表中取得kthread_create_info 结构的地址,在上文中已经完成插入操作(将kthread_create_info结构中的 list 成员加到链表中,此时根据成员 list 的偏移获得 create)
调用create_kthread(create)完成线程的创建
int kthreadd(void *unused)
{
struct task_struct *tsk = current;

/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_states[N_MEMORY]);current->flags |= PF_NOFREEZE;
cgroup_init_kthreadd();for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (list_empty(&kthread_create_list))schedule();__set_current_state(TASK_RUNNING);spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {struct kthread_create_info *create;create = list_entry(kthread_create_list.next,struct kthread_create_info, list);list_del_init(&create->list);spin_unlock(&kthread_create_lock);create_kthread(create);spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);
}return 0;

}
而create_kthread(create)函数做了一件让人意外的事情:调用了kernel_thread(),所以又回到了创建1号进程和2号进程的函数上,这次的回调函数为kthread,该函数才会真正意义上分配内存、初始化一个新的内核线程。

static void create_kthread(struct kthread_create_info *create)
{
int pid;

#ifdef CONFIG_NUMA
current->pref_node_fork = create->node;
#endif
/* We want our own signal handler (we take no signals by default). /
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {
/
If user was SIGKILLed, I release the structure. */
struct completion *done = xchg(&create->done, NULL);

    if (!done) {kfree(create);return;}create->result = ERR_PTR(pid);complete(done);
}

}
下面是kthread的源码,这里有个很重要的地方:新创建的线程由于执行了 schedule() 调度,此时并没有执行,直到我们使用wake_up_process§唤醒新创建的线程。线程被唤醒后, 会接着执行最后一段threadfn(data)

static int kthread(void _create)
{
/
Copy data: it’s on kthread’s stack */
struct kthread_create_info *create = _create;
int (*threadfn)(void *data) = create->threadfn;
void *data = create->data;
struct completion *done;
struct kthread *self;
int ret;

self = kzalloc(sizeof(*self), GFP_KERNEL);
set_kthread_struct(self);/* If user was SIGKILLed, I release the structure. */
done = xchg(&create->done, NULL);
if (!done) {kfree(create);do_exit(-EINTR);
}if (!self) {create->result = ERR_PTR(-ENOMEM);complete(done);do_exit(-ENOMEM);
}self->data = data;
init_completion(&self->exited);
init_completion(&self->parked);
current->vfork_done = &self->exited;/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;
/** Thread is going to call schedule(), do not preempt it,* or the creator may spend more time in wait_task_inactive().*/
preempt_disable();
complete(done);
schedule_preempt_disabled();
preempt_enable();ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {cgroup_kthread_ready();__kthread_parkme(self);ret = threadfn(data);
}
do_exit(ret);

}
由此,我们可以总结一下第2号进程的工作流程:

第2号进程kthreadd进程由第0号进程通过kernel_thread()创建,并始终运行在内核空间, 负责所有内核线程的调度和管理
第2号进程会循环检测kthread_create_list全局链表, 当我们调用kernel_thread创建内核线程时,新线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程
检测到新线程创建,则调用kernel_thread()创建线程,其回调为kthread
kthread在创建完后调用schedule()让出CPU资源,而不是直接运行。等待收到wake_up_process§的唤醒后再继续执行threadfn(data)。
因此
任何一个内核线程入口都是 kthread()
通过kthread_create()创建的内核线程不会立刻运行,需要手工 wake up.
通过kthread_create() 创建的内核线程有可能不会执行相应线程函数threadfn而直接退出
回到rest_init(),当完成了1号2号进程的创建后,我们将0号进程真正归位idle进程,结束rest_init(),也正事结束了start_kernel()函数,由此,内核初始化全部完成。

七. 总结

本文介绍了内核初始化的几个重要部分,其实还有很多初始化没有介绍,如cgroup初始化、虚拟文件系统初始化、radix树初始化、rcu初始化、计时器和时间初始化、架构初始化等等,这些会在后面有针对性的单独介绍。

https://zhuanlan.zhihu.com/p/440829030

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

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

相关文章

迅腾文化用网络集成化生态系统助力品牌之路的坚实后盾

商业竞争激烈&#xff0c;品牌不仅是企业的标志和形象&#xff0c;更是其核心价值和竞争力的体现。然而&#xff0c;企业在品牌推广过程中面临着诸多如缺乏有效的渠道管理、品牌形象模糊以及竞争激烈的市场环境等。这些阻碍着企业的品牌发展和市场占有率的提升。本文将通过企业…

在centos系统安装mqtt

在CentOS系统上安装MQTT&#xff0c;通常意味着要安装一个MQTT代理&#xff08;broker&#xff09;&#xff0c;比如Mosquitto。下面是在CentOS上安装Mosquitto的步骤&#xff1a; 添加EPEL仓库&#xff1a; 由于Mosquitto可能不在CentOS默认的Yum仓库中&#xff0c;你可能需要…

国标GB28181视频监控EasyCVR平台:视频集中录制存储/云端录像功能及操作介绍

安防视频监控系统EasyCVR视频综合管理平台&#xff0c;采用了开放式的网络结构&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;同时还具备权限管理、设…

Python新年文字烟花简单代码

简单的Python新年烟花代码示例&#xff1a; import random import timedef create_firework():colors [红色, 橙色, 黄色, 绿色, 蓝色, 紫色]flashes [爆裂, 闪光, 旋转, 流星, 喷射]color random.choice(colors)flash random.choice(flashes)print(f"发射一枚{color…

html5基础入门

html5基础语法与标签 前言前端开发零基础入门介绍前端开发行业介绍&#xff1a;大前端时代&#xff1a;前端开发主要技术介绍学习方法IDE简介vscode快捷键&#xff1a; 总结 HTML语法与基础标签互联网基本原理HTTP协议&#xff08;请求、响应&#xff09;什么是前端、后端&…

GitHub Copilot的使用方法和快捷按键

GitHub Copilot是GitHub与OpenAI合作开发的一款人工智能编码助手。它基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;模型&#xff0c;可以为你提供代码补全、建议和生成的功能 使用方法&#xff1a; 安装插件&#xff1a; 首先&#xff0c;确保你的开发环…

免费简单好用的 webshell 在线检测:支持 php、jsp、asp等多格式文件

话不多说&#xff0c;直接上图上链接&#xff1a;https://rivers.chaitin.cn/?share3d4f2e8aaec211eea5550242c0a8170c 还是比较好用的&#xff0c;支持 PHP、JSP 文件 webshell 检测&#xff0c;看官方解释文档&#xff0c;引擎使用静态文本特征、骨架哈希、静态语义分析、动…

Windows 项目从0到1的部署

目录 一. 安装jdk 1.1 安装jdk 1.2 配置jdk的环境配置jdk 1.3 配置成功 二. 配置tomcat 2.1 启动tomcat 2.2 防火墙设置 三. 安装MySQL 3.1 安装步骤 3.2 内部连接 3.3 外部连接 四. 部署项目 4.1 项目部署 4.2 修改mysql的用户密码 一. 安装jdk 这里给大家准备好了jdk和…

远程开发之vacode插件Remote - SSH

远程开发之vacode插件Remote - SSH vscode插件(Remote - SSH)ssh config自定义配置跳板机ssh-agent配置(使ForwardAgent配置生效, 免密拉代码)拷贝公钥到服务器(实现免密登录服务器) 通过vscode的Remote - SSH插件, 实现远程服务器进行像本地操作一样使用远程服务器, 亦可进行像…

八爪鱼拉拉手

欢迎来到程序小院 八爪鱼拉拉手 玩法&#xff1a;点击鼠标左键拖动移动八爪鱼&#xff0c;当他的手很忙的时候他会很高兴&#xff0c; 不同关卡不同的八爪鱼的位置摆放&#xff0c;快去闯关吧^^。开始游戏https://www.ormcc.com/play/gameStart/248 html <div id"gam…

计算机毕业设计 基于Java的美食信息推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

K8S的存储卷---数据卷

容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的。delete&#xff0c;K8S用控制器创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态也会恢复到初始状态。一旦回到初始状态&#xff0c;所有的后天编辑的文件都会消失 容器和节点之间创建一个…

golang实现rpc方法二:使用jsonrpc库【跨平台】

首先在golang实现rpc方法一net/rpc库中实现了RPC方法&#xff0c;但是那个方法不是跨平台的&#xff0c;没法在其他语言中调用这个实现的RPC方法&#xff0c;接下来我们可以通过jsonroc库实现跨语言的RPC方法。俩种实现方式的代码其实也是差不多的&#xff0c;大差不差&#xf…

手机直连卫星及NTN简介

一、手机直连卫星的发展现状 近日&#xff0c;华为推出了支持北斗卫星短报文的Mate 50旗舰机、P60系列&#xff0c;苹果也跟Globalstar&#xff08;全球星&#xff09;合作推出了支持卫星求救的iPhone14&#xff0c;最亮眼的还是华为的。这几款产品揭开了卫星通信探索消费领域…

Retrieval-Augmented Generation for Large Language Models: A Survey

PS: 梳理该 Survey 的整体框架&#xff0c;后续补充相关参考文献的解析整理。本文的会从两个角度来分析总结&#xff0c;因此对于同一种技术可能在不同章节下都会有提及。第一个角度是从整体框架的迭代来看&#xff08;对应RAG框架章节&#xff09;&#xff0c;第二个是从RAG中…

前端背景收集之烟花背景

文章目录 &#x1f412;个人主页&#x1f3c5;Vue项目常用组件模板仓库&#x1f4d6;前言&#xff1a;&#x1f380;源码如下&#xff1a; &#x1f412;个人主页 &#x1f3c5;Vue项目常用组件模板仓库 &#x1f4d6;前言&#xff1a; 本篇博客主要提供前端背景收集之烟花背景…

强化学习应用(八):基于Q-learning的无人机物流路径规划研究(提供Python代码)

一、Q-learning简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于马尔可夫决策过程&#xff08;MDP&#xff09;的问题。它通过学习一个价值函数来指导智能体在环境中做出决策&#xff0c;以最大化累积奖励。 Q-learning算法的核心思想是通过不断更新一个称为Q值的…

完美解决报错Please verify that the package.json has a valid “main“ entry处理方法

出现下图中的错误 &#xff0c;说明缺少main入口 解决方法&#xff1a; 1.删除 node_modules 和 package-lock.json这两个文件 2.命令行输入npm i 会重新下载包 3.重新执行检查没有报错

【LLM 论文阅读】NEFTU N E: LLM微调的免费午餐

指令微调的局限性 指令微调对于训练llm的能力至关重要&#xff0c;而模型的有用性在很大程度上取决于我们从小指令数据集中获得最大信息的能力。在本文中&#xff0c;我们提出在微调正向传递的过程中&#xff0c;在训练数据的嵌入向量中添加随机噪声&#xff0c;论文实验显示这…

静态路由、代理ARP

目录 静态路由静态路由指明下一跳和指明端口的区别代理ARP 我们知道&#xff0c;跨网络通信需要路由 路由有三种类型&#xff1a; 1.直连路由。 自动产生的路由&#xff0c;当网络设备连接到同一网络时&#xff0c;他们可以自动学习到对方的存在。自动学习相邻网络设备的直连信…