【linux】【操作系统】内核之sched.c源码阅读

news2024/10/1 7:30:20

在这里插入图片描述

sched.c提供的代码片段包含了与操作系统内核中的进程调度和管理相关的多个函数。schedule函数首先对所有任务(进程)进行检测,唤醒任何一个已经得到信号的任务。具体方法是针对任务数组中的每个任务,检查其报警定时值alam。如果任务的alam时间已经过期(alarm≤jiffies),则在它的信号位图中设置SIGALRM信号,然后清alam值。jiffies是系统从开机开始算起的滴答数(IOms滴答)。sched..h中定义。如果进程的信号位图中除去被阻塞的信号外还有其它信号,并且任务处于可中断睡眠状态(TASK INTERRUPTIBLE),则置任务为就绪状态(TASK RUNNING)

schedule()
void schedule(void)
{
    int i, next, c;
    struct task_struct ** p;

    /* 检查报警,唤醒任何已接收信号的可中断任务 */
    for (p = &LAST_TASK; p > &FIRST_TASK; --p)
        if (*p) {
            if ((*p)->alarm && (*p)->alarm < jiffies) {
                (*p)->signal |= (1 << (SIGALRM - 1));
                (*p)->alarm = 0;
            }
            if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
                (*p)->state == TASK_INTERRUPTIBLE)
                (*p)->state = TASK_RUNNING;
        }

    /* 这是调度器的核心部分: */

    while (1) {
        c = -1;
        next = 0;
        i = NR_TASKS;
        p = &task[NR_TASKS];
        while (--i) {
            if (!*--p)
                continue;
            if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
                c = (*p)->counter, next = i;
        }
        if (c) break;
        for (p = &LAST_TASK; p > &FIRST_TASK; --p)
            if (*p)
                (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
    }
    switch_to(next);
}
  • 目的:此函数实现了核心调度逻辑,根据当前进程的状态和优先级选择下一个要运行的进程。

  • 过程

    1. 信号处理:检查是否有需要因信号或定时器而唤醒的进程。
    2. 调度逻辑
    • 通过遍历所有进程来查找最高优先级的可运行进程。
    • 如果找不到可运行的进程,则减少所有进程的计数器,使它们再次变为可运行状态。
  • 详细解释 schedule 函数
    检查报警并唤醒可中断的任务

  1. 检查报警

    • 循环反向遍历所有任务 (struct task_struct 指针)。
    • 如果一个任务设置了报警,并且当前时间 (jiffies) 大于报警时间,则向该任务发送报警信号 (SIGALRM)。
    • 报警时间随后重置为 0。
  2. 唤醒可中断的任务

    • 如果一个任务在可中断睡眠状态 (TASK_INTERRUPTIBLE) 下接收到信号,则通过将其状态设置为 TASK_RUNNING 来唤醒它。
调度逻辑
  1. 查找下一个要运行的任务

    • 循环搜索具有最高优先级的可运行任务。优先级由任务结构中的 counter 字段决定。
    • 循环遍历所有任务,检查它们是否处于 TASK_RUNNING 状态并且 counter 值高于当前最高优先级任务。
    • 最高优先级任务的索引存储在 next 中。
  2. 退出循环

    • 如果找到具有非零 counter 的任务,循环结束,下一步是切换到该任务。
    • 如果没有找到这样的任务,则继续执行下一步。
  3. 调整任务优先级

    • 如果没有找到具有非零 counter 的任务,循环将调整所有任务的优先级。
    • 每个任务的 counter 被减半,然后加上任务的 priority
    • 这样可以确保较低优先级的任务最终有机会运行。
  4. 切换到下一个任务

    • 一旦找到合适任务或调整优先级后,函数调用 switch_to(next) 切换到选定的任务。
总结

此函数执行两项主要任务:

  1. 它检查报警并唤醒任何已接收信号的可中断任务。
  2. 它根据优先级选择下一个要运行的任务,并使用 switch_to 函数切换到该任务。
sys_pause()
  • 目的:暂停当前进程直到接收到信号。
  • 过程:将当前进程的状态设置为 TASK_INTERRUPTIBLE 并调用 schedule() 来释放 CPU。
sleep_on()

sleep_on(函数的主要功能是当一个进程(或任务)所请求的资源正忙或不在内存中时暂时切换出去,放在等待队列中等待一段时间。当切换回来后再继续运行。放入等待队列的方式是利用了函数中的t即指针作为各个正在等待任务的联系。

  • 目的:将当前进程置于不可中断的睡眠状态。
  • 过程
    • 将当前进程的状态保存到指定指针中。
    • 将当前进程的状态设置为 TASK_UNINTERRUPTIBLE
    • 调用 schedule() 来释放 CPU。
    • 恢复原始进程的状态。
      在这里插入图片描述
      在这里插入图片描述

此函数用于改变当前进程的状态至不可中断睡眠状态,并进行进程调度。具体步骤如下:

  • 检查参数:首先检查传入的指针 p 是否为空。若为空,则直接返回,不做任何操作。

  • 检查当前进程:判断当前进程是否为系统初始化进程 init_task。如果是,则会触发一个 panic 异常,因为初始化进程不应该进入睡眠状态。

  • 进程状态交换:使用临时变量 tmp 保存 *p 的值,然后将 *p 设置为当前进程的结构体指针 current。这意味着原本 *p 指向的进程信息被保存在 tmp 中,而 *p 现在指向了当前进程。

  • 设置当前进程状态:将当前进程的状态设置为 TASK_UNINTERRUPTIBLE,表示当前进程进入不可中断的睡眠状态。在此状态下,进程不会响应信号,直到等待的条件满足。

  • 调度器调用:通过调用 schedule() 函数,使其他就绪进程有机会运行。这会导致当前进程挂起,控制权交给其他进程。

  • 恢复原进程状态:最后,如果 tmp 不为空(即原本 *p 指向的进程存在),则将其状态设为0。这通常意味着恢复原进程的状态为初始状态或某种未指定状态,具体取决于上下文。

总结来说,这个函数主要用于让当前进程进入不可中断的睡眠状态,并允许其他进程运行。

interruptible_sleep_on()
  • 目的:将当前进程置于可中断的睡眠状态。
  • 过程
    • 类似于 sleep_on(),但进程可以被信号中断。
    • 不断检查是否应唤醒进程,并重复这一过程直到满足条件为止。
wake_up()
  • 目的:唤醒正在睡眠的进程。
  • 过程
    • 将由 p 指向的进程的状态设置为 0,表示该进程已准备好运行。
    • 清除指针以指示进程已被唤醒。
ticks_to_floppy_on()
  • 目的:计算打开软盘驱动器电机之前的延迟时间。
  • 过程
    • 根据当前软盘驱动器的状态和所需的驱动器编号确定延迟时间。
    • 使用 sleep_on() 函数等待指定的延迟时间。
floppy_on()
  • 目的:打开指定的软盘驱动器。
  • 过程:调用 ticks_to_floppy_on() 计算延迟时间并使用 sleep_on() 等待。
floppy_off()
  • 目的:关闭指定的软盘驱动器。
  • 过程:设置一个定时器,在一定延迟后关闭驱动器。
do_floppy_timer()
  • 目的:处理软盘驱动器的定时操作。
  • 过程
    • 遍历四个软盘驱动器并根据定时器更新其状态。
add_timer()
  • 目的:将定时事件添加到定时器列表中。
  • 过程
    • 如果定时器持续时间为非正数,则立即执行回调函数。
    • 否则,将定时器添加到列表中,并按定时器到期时间对列表进行排序。
do_timer()
  • 目的:处理系统定时器中断。
  • 过程
    • 更新当前进程的用户时间和系统时间。
    • 处理定时器列表,执行任何已过期的定时器。
    • 处理软盘驱动器定时。
    • 如果当前进程的计数器达到零,则调度另一个进程。
sys_alarm()
  • 目的:为当前进程设置或获取报警定时器。
  • 过程
    • 如果提供了新的值,则设置报警定时器。
    • 返回先前的报警值(如果有的话)。
sys_getpid(), sys_getppid(), sys_getuid(), sys_geteuid(), sys_getgid(), sys_getegid()
  • 目的:检索当前进程的各种属性。
  • 过程
    • 每个函数返回当前进程的一个特定属性(PID、PPID、UID、EUID、GID、EGID)。
sys_nice()
  • 目的:调整当前进程的优先级。
  • 过程
    • 如果可能,根据指定的增量降低当前进程的优先级。
sched_init()

sched_init 函数是 Linux 内核启动过程中的一部分,用于初始化调度器相关的数据结构和设置。下面是该函数的详细解释:

初始化描述符表
  1. 检查 sigaction 结构大小:
    • 确保 sigaction 结构的大小为 16 字节。如果不满足这一条件,将引发 panic 异常。
  2. 设置 TSS 和 LDT 描述符:
    • 使用 set_tss_desc 函数设置全局描述符表 (GDT) 中的第一个 TSS (Task State Segment) 描述符,指向初始化任务 (init_task) 的 TSS
    • 使用 set_ldt_desc 函数设置 GDT 中的第一个 LDT (Local Descriptor Table) 描述符,指向初始化任务的 LDT
  3. 初始化任务描述符表:
    • GDT 中第一个TSS描述符之后的位置开始,循环初始化每个任务的描述符。
    • 对于每个任务,将对应的描述符字段 ab 清零。
设置中断和系统调用门
  1. 清除 NT 标志:

    • 使用汇编指令清除标志寄存器中的 NT 标志位。这有助于避免后续可能出现的问题。
  2. 加载 TSSLDT:

    • 使用 ltr 指令加载空的 TSS 段选择符。
    • 使用 lldt 指令加载空的 LDT 段选择符。
  3. 配置 8259A PIC:

    • 设置 8259A PIC 的操作模式为二进制计数,模式 3,LSB/MSB,通道 0。
    • 设置 8259A PIC 的中断屏蔽寄存器以允许 IRQ0 中断。
  4. 设置中断门和系统调用门:

    • 使用 set_intr_gate 函数设置 IRQ0 的中断门,指向 timer_interrupt 函数。
    • 使用 set_system_gate 函数设置系统调用门,指向 system_call 函数。
  5. 禁用 NMI:

    • 设置 8259A PIC 的中断屏蔽寄存器以禁止 NMI (不可屏蔽中断)。
GDT 全局描述符

全局描述符表 (Global Descriptor Table, GDT) 是 x86 架构中保护模式下的一个重要的数据结构,用于管理处理器中的段选择器和描述符。
在这里插入图片描述

在这里插入图片描述

GDT 的数据结构

GDT 描述符格式
GDT 中的每个描述符通常由 8 字节(64 位)组成,其中包含以下字段:

  • Limit (界限):
    • 20 位表示段的最大长度,用于限制段的大小。
    • 21 位 (G) 表示界限是否使用 32 位形式(G=1)还是 16 位形式(G=0)。
  • Base (基地址):
    • 32 位或 48 位,表示段的起始物理地址。
    • 32 位系统中,基地址占用 32 位;在 64 位系统中,可以使用扩展的 48 位基地址。
  • Access byte (访问权限):
    • 8 位,包含了描述符的访问权限,如可读、可写、可执行、特权级别等。
  • Flags (标志):
    • 12 位,包含了其他标志,如 DPL (Descriptor Privilege Level)、P (Present)、D (Direction)、AVL (Available) 等。
GDT 的作用
  1. 段描述符管理:
    • GDT 存储了一系列的段描述符,每个描述符描述了一个内存段(例如代码段、数据段等)的属性,如基地址、界限、访问权限等。
  2. 段选择器绑定:
    • CPU 中的段寄存器(如 CS、DS、ES、SS 等)被设置为指向 GDT 中特定描述符的选择器,从而决定了程序对内存段的访问方式。
  3. 访问控制:
    • GDT 中的描述符包含了访问权限信息,如可读、可写、可执行等,以及特权级别(ring),用于实现对内存的访问控制。
  4. 虚拟地址转换:
    • GDT 描述符中的基地址用于构建线性地址,进而通过页表进行虚拟地址到物理地址的转换。
总结

这些函数共同管理进程的调度和执行、处理信号和报警、管理进程属性以及控制软盘驱动器的操作。schedule() 函数是调度器的核心,它根据进程的状态和优先级决定下一个运行的进程。其他函数支持进程管理的不同方面以及硬件控制。

源码
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#include <signal.h>

#define _S(nr) (1<<((nr)-1))
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

void show_task(int nr,struct task_struct * p)
{
	int i,j = 4096-sizeof(struct task_struct);

	printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state);
	i=0;
	while (i<j && !((char *)(p+1))[i])
		i++;
	printk("%d (of %d) chars free in kernel stack\n\r",i,j);
}

void show_stat(void)
{
	int i;

	for (i=0;i<NR_TASKS;i++)
		if (task[i])
			show_task(i,task[i]);
}

#define LATCH (1193180/HZ)

extern void mem_use(void);

extern int timer_interrupt(void);
extern int system_call(void);

union task_union {
	struct task_struct task;
	char stack[PAGE_SIZE];
};

static union task_union init_task = {INIT_TASK,};

long volatile jiffies=0;
long startup_time=0;
struct task_struct *current = &(init_task.task);
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&(init_task.task), };

long user_stack [ PAGE_SIZE>>2 ] ;

struct {
	long * a;
	short b;
	} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 */
void math_state_restore()
{
	if (last_task_used_math == current)
		return;
	__asm__("fwait");
	if (last_task_used_math) {
		__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
	}
	last_task_used_math=current;
	if (current->used_math) {
		__asm__("frstor %0"::"m" (current->tss.i387));
	} else {
		__asm__("fninit"::);
		current->used_math=1;
	}
}

/*
 *  'schedule()' is the scheduler function. This is GOOD CODE! There
 * probably won't be any reason to change this, as it should work well
 * in all circumstances (ie gives IO-bound processes good response etc).
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */
void schedule(void)
{
	int i,next,c;
	struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
		if (*p) {
			if ((*p)->alarm && (*p)->alarm < jiffies) {
					(*p)->signal |= (1<<(SIGALRM-1));
					(*p)->alarm = 0;
				}
			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
			(*p)->state==TASK_INTERRUPTIBLE)
				(*p)->state=TASK_RUNNING;
		}

/* this is the scheduler proper: */

	while (1) {
		c = -1;
		next = 0;
		i = NR_TASKS;
		p = &task[NR_TASKS];
		while (--i) {
			if (!*--p)
				continue;
			if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
				c = (*p)->counter, next = i;
		}
		if (c) break;
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)
				(*p)->counter = ((*p)->counter >> 1) +
						(*p)->priority;
	}
	switch_to(next);
}

int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule();
	return 0;
}

void sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE;
	schedule();
	if (tmp)
		tmp->state=0;
}

void interruptible_sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp=*p;
	*p=current;
repeat:	current->state = TASK_INTERRUPTIBLE;
	schedule();
	if (*p && *p != current) {
		(**p).state=0;
		goto repeat;
	}
	*p=NULL;
	if (tmp)
		tmp->state=0;
}

void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;
		*p=NULL;
	}
}

/*
 * OK, here are some floppy things that shouldn't be in the kernel
 * proper. They are here because the floppy needs a timer, and this
 * was the easiest way of doing it.
 */
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};
static int  mon_timer[4]={0,0,0,0};
static int moff_timer[4]={0,0,0,0};
unsigned char current_DOR = 0x0C;

int ticks_to_floppy_on(unsigned int nr)
{
	extern unsigned char selected;
	unsigned char mask = 0x10 << nr;

	if (nr>3)
		panic("floppy_on: nr>3");
	moff_timer[nr]=10000;		/* 100 s = very big :-) */
	cli();				/* use floppy_off to turn it off */
	mask |= current_DOR;
	if (!selected) {
		mask &= 0xFC;
		mask |= nr;
	}
	if (mask != current_DOR) {
		outb(mask,FD_DOR);
		if ((mask ^ current_DOR) & 0xf0)
			mon_timer[nr] = HZ/2;
		else if (mon_timer[nr] < 2)
			mon_timer[nr] = 2;
		current_DOR = mask;
	}
	sti();
	return mon_timer[nr];
}

void floppy_on(unsigned int nr)
{
	cli();
	while (ticks_to_floppy_on(nr))
		sleep_on(nr+wait_motor);
	sti();
}

void floppy_off(unsigned int nr)
{
	moff_timer[nr]=3*HZ;
}

void do_floppy_timer(void)
{
	int i;
	unsigned char mask = 0x10;

	for (i=0 ; i<4 ; i++,mask <<= 1) {
		if (!(mask & current_DOR))
			continue;
		if (mon_timer[i]) {
			if (!--mon_timer[i])
				wake_up(i+wait_motor);
		} else if (!moff_timer[i]) {
			current_DOR &= ~mask;
			outb(current_DOR,FD_DOR);
		} else
			moff_timer[i]--;
	}
}

#define TIME_REQUESTS 64

static struct timer_list {
	long jiffies;
	void (*fn)();
	struct timer_list * next;
} timer_list[TIME_REQUESTS], * next_timer = NULL;

void add_timer(long jiffies, void (*fn)(void))
{
	struct timer_list * p;

	if (!fn)
		return;
	cli();
	if (jiffies <= 0)
		(fn)();
	else {
		for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)
			if (!p->fn)
				break;
		if (p >= timer_list + TIME_REQUESTS)
			panic("No more time requests free");
		p->fn = fn;
		p->jiffies = jiffies;
		p->next = next_timer;
		next_timer = p;
		while (p->next && p->next->jiffies < p->jiffies) {
			p->jiffies -= p->next->jiffies;
			fn = p->fn;
			p->fn = p->next->fn;
			p->next->fn = fn;
			jiffies = p->jiffies;
			p->jiffies = p->next->jiffies;
			p->next->jiffies = jiffies;
			p = p->next;
		}
	}
	sti();
}

void do_timer(long cpl)
{
	extern int beepcount;
	extern void sysbeepstop(void);

	if (beepcount)
		if (!--beepcount)
			sysbeepstop();

	if (cpl)
		current->utime++;
	else
		current->stime++;

	if (next_timer) {
		next_timer->jiffies--;
		while (next_timer && next_timer->jiffies <= 0) {
			void (*fn)(void);
			
			fn = next_timer->fn;
			next_timer->fn = NULL;
			next_timer = next_timer->next;
			(fn)();
		}
	}
	if (current_DOR & 0xf0)
		do_floppy_timer();
	if ((--current->counter)>0) return;
	current->counter=0;
	if (!cpl) return;
	schedule();
}

int sys_alarm(long seconds)
{
	int old = current->alarm;

	if (old)
		old = (old - jiffies) / HZ;
	current->alarm = (seconds>0)?(jiffies+HZ*seconds):0;
	return (old);
}

int sys_getpid(void)
{
	return current->pid;
}

int sys_getppid(void)
{
	return current->father;
}

int sys_getuid(void)
{
	return current->uid;
}

int sys_geteuid(void)
{
	return current->euid;
}

int sys_getgid(void)
{
	return current->gid;
}

int sys_getegid(void)
{
	return current->egid;
}

int sys_nice(long increment)
{
	if (current->priority-increment>0)
		current->priority -= increment;
	return 0;
}

void sched_init(void)
{
	int i;
	struct desc_struct * p;

	if (sizeof(struct sigaction) != 16)
		panic("Struct sigaction MUST be 16 bytes");
	set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
	set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
	p = gdt+2+FIRST_TSS_ENTRY;
	for(i=1;i<NR_TASKS;i++) {
		task[i] = NULL;
		p->a=p->b=0;
		p++;
		p->a=p->b=0;
		p++;
	}
/* Clear NT, so that we won't have troubles with that later on */
	__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
	ltr(0);
	lldt(0);
	outb_p(0x36,0x43);		/* binary, mode 3, LSB/MSB, ch 0 */
	outb_p(LATCH & 0xff , 0x40);	/* LSB */
	outb(LATCH >> 8 , 0x40);	/* MSB */
	set_intr_gate(0x20,&timer_interrupt);
	outb(inb_p(0x21)&~0x01,0x21);
	set_system_gate(0x80,&system_call);
}

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

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

相关文章

Midjourney咒语之手机壁纸国画艺术

手机壁纸 Mountains, surfaces, mysterious landscapes --ar 9:16 Abstract shapes of billowing flowing colorful gauze fabric, --ar 9:16 国画艺术 Peony is

如何快速看完一个网页上的视频

如何快速看完一个视频 懂的都懂。 Edge浏览器 添加下面两个书签&#xff1a; javascript:document.querySelector("video").dispatchEvent(new Event("ended"))javascript:var vdocument.querySelector("video");if(v){v.mutedtrue;v.playba…

从艺术创作到作物生长,农业AI迎来“GPT“时刻

&#xff08;于景鑫 国家农业信息化工程技术研究中心&#xff09;"GPT"一词早已不再神秘,其在文本、图像生成领域掀起的风暴正以摧枯拉朽之势席卷全球。人们惊叹于ChatGPT对话之智能、思维之敏捷,更对Stable Diffusion、Midjourney创作的艺术画作赞叹不已。而大语言模…

无代码开发AI服务 - 利用向量库Kendra和Llama大模型在亚马逊云科技AWS上创建RAG知识库

简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 上次我们介绍了我们利用ElasticSearch作为向量…

网鼎杯comment二次注入

靶机网址&#xff1a;BUUCTF在线评测 进来就是这个界面&#xff0c;点击发帖后需要进行登录。 从界面可以看出用户是zhangwei&#xff0c;密码是zhangwei***&#xff0c;密码的最后三位需要进行暴力破解。 这里需要用到工具Burp Suite进行抓包。 这就是抓到的包&#xff0c;我…

【大模型从入门到精通8】openAI API 提升机器推理:高级策略2

这里写目录标题 示例定义处理输入的函数链式思考提示示例&#xff1a;结构化系统和用户提示获取并展示模型的回答实现内心独白结论与最佳实践 示例 设置环境 在深入实施之前&#xff0c;设置必要的环境至关重要。这包括加载 OpenAI API 密钥并导入相关的 Python 库。以下代码块…

Chapter 25 面向对象

欢迎大家订阅【Python从入门到精通】专栏&#xff0c;一起探索Python的无限可能&#xff01; 文章目录 前言一、初识对象二、成员方法三、类和对象 前言 面向对象编程&#xff08;OOP&#xff09;是Python编程中的一个核心概念&#xff0c;它能帮助程序员更好地组织和管理代码…

01 计算机系统基础-2

操作系统 进程管理 进程管理是操作系统的核心&#xff0c;但如果设计不当&#xff0c;就会出现死锁的问题。如果一个进程在等待一件不可能发生的事&#xff0c;则进程就死锁了。而如果一个或多个进程产生死锁&#xff0c;就会造成系统死锁。基于死锁产生机制及解决方案&#…

LeetCode Hard|【460. LFU 缓存】

力扣题目链接 LFU全称是最不经常使用算法&#xff08;Least Frequently Used&#xff09;&#xff0c;LFU算法的基本思想和所有的缓存算法一样&#xff0c;一定时期内被访问次数最少的页&#xff0c;在将来被访问到的几率也是最小的。 相较于 LRU 算法&#xff0c;LFU 更加注重…

MATLAB霍夫曼表盘识别系统

MATLAB霍夫曼表盘识别系统 一、介绍 本设计为基于MATLAB的表盘指针识别&#xff0c;算法原理是基于hough变换。可检测压力表&#xff0c;石英手表&#xff0c;电表刻度&#xff0c;气压表等带指针刻度的表盘。通过hough检测直线和圆的关系&#xff0c;得出指针夹角&#xff0…

保形分位数回归(CQR)

目录 简介1 介绍提纲式总结 分位数回归从数据中估计分位数 3 共性预测4 保形分位数回归(CQR)两个定理 6 实验7 结论 简介 保形预测是一种构造在有限样本中获得有效覆盖的预测区间的技术&#xff0c;无需进行分布假设。尽管有这种吸引力&#xff0c;但现有的保形方法可能是不必…

(C题老外游中国)2024年华数杯大学生数学建模竞赛解题思路完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

Open3D 三维重建-Delaunay Triangulation (德劳内三角剖分)

目录 一、概述 1.1原理 1.2实现步骤 1.3应用 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2重建后点云 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSD…

MySQL--日志管理

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、日志简介 MySQL日志主要分为4类&#xff0c;使用这些日志文件&#xff0c;可以查看MySQL内部发生的事情。这4类日志分别是: 错误日志&#xff1…

程序编译及链接

你好&#xff01;感谢支持孔乙己的新作&#xff0c;本文就程序的编译及链接与大家分析我的思路。 希望能大佬们多多纠正及支持 &#xff01;&#xff01;&#xff01; 个人主页&#xff1a;爱摸鱼的孔乙己-CSDN博客 ​ ​ 1.翻译译环境与运行环境 当我们进行程序设计时&…

Android Graphics 显示系统 - 计算FPS的原理与探秘Present Fence

“ 最近忙着新房子装修的事情&#xff0c;这篇计划内的文章拖了好久一直没有足够的时间来写作&#xff0c;终于挤出些儿时间来继续研究学习了。” 整了四个晚上终于拼凑出一篇文章&#xff0c;虽说是讲FPS计算原理&#xff0c;但该文涉及的知识点还是蛮多的&#xff0c;特别是对…

webpack的loader机制

webpack的loader机制 loader本质上就是导出函数的JavaScript模块。导出的函数&#xff0c;可以用来实现内容的转换。 /* * param{string|Buffer} content 源文件的内容 * param{object} [map] SourceMap数据 * param{any} [meta] meta数据&#xff0c;可以是任何数据 * */ fu…

黑马头条vue2.0项目实战(五)——首页—频道编辑

目录 1. 使用页面弹出层 1.1 页面弹出层简单使用 1.2 创建频道编辑组件 1.3 页面布局 2. 展示我的频道 3. 展示推荐频道列表 3.1 获取所有频道 3.2 处理展示推荐频道 4. 添加频道 5. 编辑频道 5.1 处理编辑状态 5.2 切换频道 5.3 让激活频道高亮 5.4 删除频道 6.…

K8S Docker搭建RocketMQ Dledger高可用集群

本篇文章回顾在华润基于K8S和Docker云设施搭建初步高可用具备failover的RocketMQ集群。RocketMQ版本是5.0.0。 目前现状 采用Dledger模式部署集群&#xff0c;3台namesrv&#xff0c;3台broker&#xff0c;namesrv每台1g的Docker部署&#xff0c;broker每台2g的Docker部署。测…

Hyper-V创建虚拟机安装OpenEulerOS

文章目录 下载OpenEulerHyper-V创建虚拟机 下载OpenEuler 进入官网下载&#xff0c;我选择的是 openEuler 24.03 LTS &#xff0c;选择第一个版本即可&#xff1a; Hyper-V创建虚拟机 点击新建->虚拟机&#xff1a; 点击下一步&#xff1a; 输入虚拟机名称&#xff0c…