【UCB操作系统CS162项目】Pintos Lab2:用户程序 User Programs(下)

news/2024/5/1 5:21:13/文章来源:https://blog.csdn.net/Altair_alpha/article/details/127177624

在上节中,我们已经完成了 Lab 2 要求的参数传递和系统调用中的 halt, exit 以及向 stdout 输出的 write,最终停在了 wait 的实现之前。本节就先从 wait 和 exec 继续。

Syscall wait + exec:实现父子进程

讲义中 wait 的要求是这样的,相当之长:

在这里插入图片描述

不难读懂,为了实现 wait,我们要先完成父子进程的设计。父进程应该持有一个记录所有子进程信息的列表,但这个列表的成员不能是子进程本身,因为即使子进程已经结束了,父进程应该仍能查询到其信息。所以在 thread.h 中添加一种新的数据结构:

/** Information of a thread's child */
struct child_entry
{tid_t tid;                          /**< Child's tid. */struct thread *t;                   /**< Pointer to child thread. Set to NULL when no longer alive. */bool is_alive;                      /**< Whether the child is still alive (not exited). */int exit_code;                      /**< Child's exit code. */bool is_waiting_on;                 /**< Whether the parent is waiting on the child. */struct semaphore wait_sema;         /**< Semaphore to let parent wait on the child. */struct list_elem elem;
};

这些成员就是父进程需要能获取的关于子进程信息。在 thread 类中:

struct thread
{...struct thread *parent;              /**< Thread's parent. */struct list child_list;             /**< Thread's children. Member type is child_entry. */struct child_entry *as_child;       /**< Thread itself's child_entry. This will be added to its parent's child_list and is heap-allocated so that it lives after the thread dies. */...
};

添加一个指向父进程的指针 parent,列表 child_list 和一个指针 as_child。一个进程作为子进程的信息在创建时(thread_create)中使用 malloc 生成,添加到父进程的 child_list 同时存储到自身的 as_child 指针。这样做一个进程就能方便地更新自己向父进程汇报的信息。初始化:

static void
init_thread (struct thread *t, const char *name, int priority)
{...list_init(&t->child_list); // as_child initialization will be done later, see thread_create()...
}tid_t
thread_create (const char *name, int priority,thread_func *function, void *aux) 
{.../* Initialize thread. */init_thread (t, name, priority);tid = t->tid = allocate_tid ();/* Initialize child entry here because we just got a tid. */t->as_child = malloc(sizeof(struct child_entry));t->as_child->tid = tid;t->as_child->t = t;t->as_child->is_alive = true;t->as_child->exit_code = 0;t->as_child->is_waiting_on = false;sema_init(&t->as_child->wait_sema, 0);/* Link child and parent thread. */t->parent = thread_current();list_push_back(&t->parent->child_list, &t->as_child->elem);
}

有了这些信息我们就能写出 process_wait() 了:查找自身的 child_list,如果不存在目标子线程,返回 -1;如果存在且子线程未结束,也没有 wait 过,用信号量卡住自身等待子线程运行结束;如果 wait 过,返回 -1;如果已经结束了,返回子线程留下来的 exit_code

int
process_wait (tid_t child_tid) 
{struct thread *t_cur = thread_current();struct list_elem *e;for (e = list_begin (&t_cur->child_list); e != list_end (&t_cur->child_list);e = list_next (e)){struct child_entry *entry = list_entry(e, struct child_entry, elem);if (entry->tid == child_tid) {if (!entry->is_waiting_on && entry->is_alive) {entry->is_waiting_on = true;sema_down(&entry->wait_sema); // wait for child process to exitreturn entry->exit_code;}else if (entry->is_waiting_on) { // already waiting on childreturn -1;}else { // child has terminated, retrieve exit_codereturn entry->exit_code;}}}// child_tid is not a child of current processreturn -1;
}

在一个进程结束时,要考虑好其与父子进程的关系。一方面,作为父进程,应该告诉所有它仍存活的子进程自身已退出(将 entry->t->parent 标为 NULL)。另一方面作为子进程,如果发现父进程已经退出了,则 as_child 记录的信息不会再被访问,应释放掉。否则,应该更新 as_child->exit_code(从自身的 exit_code 属性拷贝,因为外部与线程互动时都会设置 threadexit_code);如果父进程正用信号量等待该子进程,up 该信号量;设置 is_alive 为 false 并将指向自身的 t 指针设为 NULL

这样的思路是清晰的,child_entry 的信息既被正确记录,也不会造成资源泄漏。

void
thread_exit (void) 
{...// as a parent, mark the parent of any child that hasn't exited as NULLfor (e = list_begin (&t_cur->child_list); e != list_end (&t_cur->child_list);e = list_next (e)){struct child_entry *entry = list_entry(e, struct child_entry, elem);if (entry->is_alive) {entry->t->parent = NULL;}}// as a child, if the parent thread has exited, it's ok to free the as_child elementif (t_cur->parent == NULL) {free(t_cur->as_child);} else { // otherwise, save our status as parent may visit it later (e.g., in wait())t_cur->as_child->exit_code = t_cur->exit_code;if (t_cur->as_child->is_waiting_on) {sema_up(&t_cur->as_child->wait_sema);}t_cur->as_child->is_alive = false;t_cur->as_child->t = NULL;}...
}

在 wait 的 syscall 函数中,调用 process_wait 即可:

static void 
syscall_wait(struct intr_frame *f)
{int pid = *(int *)(f->esp + ptr_size);f->eax = process_wait(pid);
}

有了父子进程的基础,exec 的实现也比较自然了,讲义要求:

在这里插入图片描述
显然主体就和主线程的创建一样,调用 process_execute()

static void 
syscall_exec(struct intr_frame *f)
{char *cmd = *(char **)(f->esp + ptr_size);f->eax = process_execute(cmd);
}

不过这里有一个新要求是如果子进程没有正常运行起来,父进程要返回 -1。现在的代码中,process_execute() 内用 start_process 创建新线程后就返回了,没有等新线程跑起来;而子进程可能没有正常运行的原因就在 start_process 中:子进程的可执行程序可能因为找不到文件等原因而加载失败。所以要再加一个信号量,让父进程等待:

struct thread
{...struct semaphore sema_exec;         /**< Semaphore for executing (spawning) a new process. "UPed" after knowing whether the child has loaded its executable successfully. */bool exec_success;                  /**< Whether new process successfully loaded its executable. */...
};tid_t
process_execute (const char *proc_cmd) 
{.../* Create a new thread to execute PROC_CMD. */tid = thread_create (proc_name, PRI_DEFAULT, start_process, proc_cmd_copy2);...sema_down(&thread_current()->sema_exec);if (!thread_current()->exec_success) {return -1;}thread_current()->exec_success = false; // reset the flag for next spawnreturn tid;
}

子进程加载是否成功,其实之前的代码已经告诉我们了,就是 success 标识。成功失败两种情况为 exec_success 设置相应的值并 up 信号量即可。

static void
start_process (void *proc_cmd_)
{...success = load (proc_name, &if_.eip, &if_.esp);/* If load failed, quit. */if (!success) {...thread_current()->as_child->is_alive = false;thread_current()->exit_code = -1;sema_up(&thread_current()->parent->sema_exec);thread_exit ();}.../* load success. */thread_current()->parent->exec_success = 1;sema_up(&thread_current()->parent->sema_exec);...
}

这一步做完跑一下测试会发现 exec 和 wait 相关的部分测试已经能通过了,但是有 fail 的,比如下面这个:
在这里插入图片描述
这可真是太暴力了…那么让我们休整一下,进入下一部分的实现吧。

迟来的 Task 3: 用户访存(指针)检查

好吧其实讲义上这部分是在 System Call 之前的,但自己做的时候反正博主是愿意先做正常 case 的逻辑能够早点看到效果,再做特殊情况处理。那么现在就是做后者的时间了。

现代操作系统,理论上应该用户再怎么乱折腾也就是自己的程序崩掉,不至于连着操作系统一块带崩(蓝屏警告doge)。这就要求 OS 要有对用户行为,主要就是通过指针的内存访问的检查功能。讲义告诉我们发现非法访存时处理方式也是立刻结束用户进程并释放资源,并提供了两种可选检测方法:

在这里插入图片描述
第一种在访问用户指针的内存前先做合法性检查:地址是否属于用户内存区域(小于 PHYS_BASE)以及地址是否属于当前进程的内存区域;第二种是仅做前者的检查然后就访问,如果不合法会引发 page fault,然后再处理这个异常。后者通常速度更快一些所以更经常被应用在实际系统中,那么我们也走这条路径。

讲义很贴心的给了我们非法访存引发异常后返回错误的方法:利用以下函数,并在 exception.c 中引发异常后将 EAX 的值拷贝到 EIP 中,然后设为 0xffffffff(-1):

/* Reads a byte at user virtual address UADDR.UADDR must be below PHYS_BASE.Returns the byte value if successful, -1 if a segfaultoccurred. */
static int
get_user (const uint8_t *uaddr)
{int result;asm ("movl $1f, %0; movzbl %1, %0; 1:": "=&a" (result) : "m" (*uaddr));return result;
}/* Writes BYTE to user address UDST.UDST must be below PHYS_BASE.Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte)
{int error_code;asm ("movl $1f, %0; movb %b2, %1; 1:": "=&a" (error_code), "=m" (*udst) : "q" (byte));return error_code != -1;
}

讲义没有告诉我们应该如此做的具体判断条件。来看 exception.c 的代码:


/** Page fault handler.  This is a skeleton that must be filled into implement virtual memory.  Some solutions to project 2 mayalso require modifying this code.At entry, the address that faulted is in CR2 (Control Register2) and information about the fault, formatted as described inthe PF_* macros in exception.h, is in F's error_code member.  Theexample code here shows how to parse that information.  Youcan find more information about both of these in thedescription of "Interrupt 14--Page Fault Exception (#PF)" in[IA32-v3a] section 5.15 "Exception and Interrupt Reference". */
static void
page_fault (struct intr_frame *f) 
{bool not_present;  /**< True: not-present page, false: writing r/o page. */bool write;        /**< True: access was write, false: access was read. */bool user;         /**< True: access by user, false: access by kernel. */void *fault_addr;  /**< Fault address. *//* Obtain faulting address, the virtual address that wasaccessed to cause the fault.  It may point to code or todata.  It is not necessarily the address of the instructionthat caused the fault (that's f->eip).See [IA32-v2a] "MOV--Move to/from Control Registers" and[IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception(#PF)". */asm ("movl %%cr2, %0" : "=r" (fault_addr));/* Turn interrupts back on (they were only off so that we couldbe assured of reading CR2 before it changed). */intr_enable ();/* Count page faults. */page_fault_cnt++;/* Determine cause. */not_present = (f->error_code & PF_P) == 0;write = (f->error_code & PF_W) != 0;user = (f->error_code & PF_U) != 0;/* To implement virtual memory, delete the rest of the functionbody, and replace it with code that brings in the page towhich fault_addr refers. */printf ("Page fault at %p: %s error %s page in %s context.\n",fault_addr,not_present ? "not present" : "rights violation",write ? "writing" : "reading",user ? "user" : "kernel");kill (f);
}

显然异常处理返回应该放在 kill(f) 之前,那么如何判断呢?思考原代码中已经给出的几个 flag,不难发现关键点在于 usersyscall 引起异常处于 kernel 态,而理论上如果 kernel 代码逻辑没有错误的话,其它情况下 kernel 态是不应该产生 page fault 的。所以,如果 user 为 false,一定是因为 syscall 非法访存导致的。这样就找到了判断条件。补全代码如下:

  // user赋值之后:// The only chance that a page fault happens in kernel context is when dealing // with user-provided pointer through system call, because kernel code shouldn't // produce page faults (if we're writing it right...)if (!user) {f->eip = (void (*) (void)) f->eax;f->eax = -1;return;}kill(f);

顺便读一读 kill() 函数,当异常是用户引起时(例如,直接访问 NULL),不属于 syscall,会走 SEL_UCSEG 的分支直接使线程退出。因为我将线程的 exit_code 初始化为了 0(正常退出),这里应该加一句将 exit_code 改为 -1。

/** Handler for an exception (probably) caused by a user process. */
static void
kill (struct intr_frame *f) 
{/* The interrupt frame's code segment value tells us where theexception originated. */switch (f->cs){case SEL_UCSEG:/* User's code segment, so it's a user exception, as weexpected.  Kill the user process.  */printf ("%s: dying due to interrupt %#04x (%s).\n",thread_name (), f->vec_no, intr_name (f->vec_no));intr_dump_frame (f);thread_current()->exit_code = -1;thread_exit (); ...}
}

处理好这里,让我们回到 syscall.c,首先将讲义提供的两个函数加上。这两个函数的功能是检查一个 Byte 的读写是否合法。用户提供的指针可能是指向各种大小的数据的,于是写出以下两个函数,实现任意大小数据的指针检查(如果不合法,直接调用 terminate_process,使线程以 -1 的返回值退出):

/** Check if a user-provided pointer is safe to read from. Return the pointer itself if safe, * or call terminate_process() (which do not return) to kill the process with exit_code -1. */
static void * 
check_read_user_ptr(const void *ptr, size_t size)
{if (!is_user_vaddr(ptr)) {terminate_process();}for (size_t i = 0; i < size; i++) { // check if every byte is safe to readif (get_user(ptr + i) == -1) {terminate_process();}}return (void *)ptr; // remove const
}/** Check if a user-provided pointer is safe to write to. Return the pointer itself if safe, * or call terminate_process() (which do not return) to kill the process with exit_code -1. */
static void * 
check_write_user_ptr(void *ptr, size_t size)
{if (!is_user_vaddr(ptr)) {terminate_process();}for (size_t i = 0; i < size; i++) {if (!put_user(ptr + i, 0)) { // check if every byte is safe to writeterminate_process();}}return ptr;
}

在解析系统调用参数时,这样使用即可:

static void 
syscall_wait(struct intr_frame *f)
{int pid = *(int *)check_read_user_ptr(f->esp + ptr_size, sizeof(int));f->eax = process_wait(pid);
}

其它类型同理。别忘了,入口的系统调用编号也是解引用一个用户指针得到的,也要检查:

static void
syscall_handler (struct intr_frame *f) 
{int syscall_type = *(int *)check_read_user_ptr(f->esp, sizeof(int));...
}

字符串是一种特殊情况,其大小无法通过 sizeof(char *) 中获取,而是以出现 \0 字符为结束标识。为其写一个专用的函数:

/** Check if a user-provided string is safe to read from. Return the string itself if safe, * or call terminate_process() (which do not return) to kill the process with exit_code -1. */
static char * 
check_read_user_str(const char *str)
{if (!is_user_vaddr(str)) {terminate_process();}uint8_t *_str = (uint8_t *)str;while (true) {int c = get_user(_str);if (c == -1) {terminate_process();} else if (c == '\0') { // reached the end of strreturn (char *)str; // remove const}++_str;}NOT_REACHED();
}

使用时,注意先检查字符串的指针,再检查字符串本体:

static void 
syscall_exec(struct intr_frame *f)
{char *cmd = *(char **)check_read_user_ptr(f->esp + ptr_size, ptr_size);check_read_user_str(cmd);f->eax = process_execute(cmd);
}

至此,task 3 用户访存检查任务完成。

Syscall:create, remove, open…:文件系统调用

泪目,Pintos 良心地给了我们一套文件系统的实现,没有让我们从零手写,于是这部分我们主要就是做好线程文件资源的管理,具体的文件操作就是轻松愉悦的调包了(在 filesys.hfile.h 中)。讲义上提到的一些特殊要求,例如打开的文件也能被删除文件系统都帮我们做好了,不用特殊判断。

需要实现的要求之一,是文件系统暂时没有同步机制,需要自己加锁。我把锁放在了 filesys.h 中,所有文件系统相关的调用都要保护,例如:

static void 
syscall_create(struct intr_frame *f)
{char *file_name = *(char **)check_read_user_ptr(f->esp + ptr_size, ptr_size);check_read_user_str(file_name);unsigned file_size = *(unsigned *)check_read_user_ptr(f->esp + 2 * ptr_size, sizeof(unsigned));lock_acquire(&filesys_lock);bool res = filesys_create(file_name, file_size);f->eax = res;lock_release(&filesys_lock);
}

进程需要管理好其打开的文件,每个文件由一个整型 file descriptor 和一个 file 指针描述。FD=0 标识 STDIN,FD=1 标识 STDOUT,其它文件从 2 号开始,分配规则随意,我这里就使其自然增长。定义结构体 file_entry,并在 thread 类中添加以下成员:

/** Information of a thread's opened file */
struct file_entry
{int fd;                             /**< File descriptor. */struct file *f;                     /**< Pointer to file. */struct list_elem elem;
};struct thread
{...struct file *exec_file;             /**< The executable file loaded by the thread. Opened upon*/struct list file_list;              /**< Files opened by the thread. Member type is file_entry. */int next_fd;                        /**< Next file descriptor to be allocated....
};

exec_file 下面再解释。打开一个文件时,新建一个文件记录,添加到 file_list 中:

static void 
syscall_open(struct intr_frame *f)
{char *file_name = *(char **)check_read_user_ptr(f->esp + ptr_size, ptr_size);check_read_user_str(file_name);lock_acquire(&filesys_lock);struct file *opened_file = filesys_open(file_name);lock_release(&filesys_lock);if (opened_file == NULL) {f->eax = -1;return;}struct thread *t_cur = thread_current();struct file_entry *entry = malloc(sizeof(struct file_entry));entry->fd = t_cur->next_fd++;entry->f = opened_file;list_push_back(&t_cur->file_list, &entry->elem);f->eax = entry->fd;
}

文件相关系统调用的参数很多是 fd,这些操作都要针对已打开的文件,于是写一个由 fd 查找文件是否打开的函数:

/** Get pointer to a file entry owned by current process by its fd. * Returns NULL if not found. */
static struct file_entry *
get_file(int fd)
{struct thread *t_cur = thread_current();struct list_elem *e;for (e = list_begin (&t_cur->file_list); e != list_end (&t_cur->file_list);e = list_next (e)){struct file_entry *entry = list_entry(e, struct file_entry, elem);if (entry->fd == fd) {return entry;}}return NULL;
}

各系统调用按照解析参数获取 fd → 根据 fd 获取 file 指针 → 调文件系统函数 → 写入返回值的流程处理即可。read 和 write 中注意 STDINSTDOUT 特殊情况的处理

调用 close 时,从 file_list 中将 file_entry 摘下,调用 file_close 后将 entry 的资源释放。线程退出时,也要把列表中仍存在(未关闭)的文件关闭并释放 entry 块,避免资源泄漏。

void
thread_exit (void) 
{...struct list_elem *e;// close remaining opened fileswhile (!list_empty(&t_cur->file_list)){e = list_pop_front(&t_cur->file_list);struct file_entry *entry = list_entry(e, struct file_entry, elem);lock_acquire(&filesys_lock);file_close(entry->f);lock_release(&filesys_lock);free(entry);}...
}

至此文件系统相关的调用就大体 OK 了,但 Task 5 中还提出了最后一个要求:

  • Add code to deny writes to files in use as executables.
    • Many OSes do this because of the unpredictable results if a process tried to run code that was in the midst of being changed on disk.
    • This is especially important once virtual memory is implemented in project 3, but it can’t hurt even now.

一个进程自己正在跑的可执行文件不应该能被修改,非常合理。于是为每个线程添加一个特殊的文件指针 exec_file,在 start_process 中将自己的可执行文件打开存入该指针,并调用 file_deny_write() 拒绝写入,process_exit 中将其关闭(会自动允许写入):

static void
start_process (void *proc_cmd_)
{...lock_acquire(&filesys_lock);struct file *f = filesys_open(proc_name);file_deny_write(f);lock_release(&filesys_lock);thread_current()->exec_file = f;...
}void
process_exit (void)
{...// close the executable filelock_acquire(&filesys_lock);file_close(t_cur->exec_file);lock_release(&filesys_lock);...
|

恭喜你到达了 Lab 2 的终点! 唯一需要注意的是 multi-oom 这个测试点,会尽可能攻击你的系统直至其资源耗尽(执行也会卡一会)。如果提示执行深度不够过不去,请在整个项目搜索 mallocpalloc_get_page检查你写的代码中是否存在资源泄漏的可能,包括启动进程时分配的那个命令行字符串

在这里插入图片描述
第 100 篇博客留念~

在这里插入图片描述

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

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

相关文章

这几个文字翻译工具确定不试试看?

想问问大家平常会接触到TXT文件吗&#xff1f;这是微软在操作系统上附带的一种文本格式&#xff0c;主要是保存纯文字信息&#xff0c;像我们电脑上自带的记事本工具&#xff0c;就是使用这种文件格式。有时候我们需要将文本内容翻译成中文。那你知道如何实现TXT翻译成中文吗&a…

LRU缓存——哈希表+双向链表

一、题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; 1&#xff09;LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 2&#xff09;int get(int key) 如果关键字 key 存在于缓存中&#xff0c;…

STA系列 - 特殊时序分析multicycle/half-cycle/false path

文章目录什么是require time/arrive timeMulticycle PathHalf PathFalth Path本篇文章介绍的是特殊的时序path, 全文为视频笔记&#xff0c;以及自己的理解https://www.bilibili.com/video/BV1if4y1p7Dq?p10&vd_source84d1070e8334ce7e2bb0bd110abcf1a7什么是require time…

使用服务器跑模型——案例2

案例2 在本案例中我们使用vscode来上传/下载文件&#xff0c;服务器端编程和debug。 下载 vscode 在官网下载vscode正式版&#xff0c;别使用家庭版。下载地址https://code.visualstudio.com/Download。 使用 vscode 连接服务器 在vscode扩展中搜索ssh并下载安装。 安装成功…

【机器学习】熵权算法确定权重 原理+完整MATLAB代码+详细注释+操作实列

【机器学习】熵权算法确定权重 原理完整MATLAB代码详细注释操作实列 文章目录 1. 熵权法确定指标权重 &#xff08;1&#xff09;构造评价矩阵 Ymn &#xff08;2&#xff09;评价矩阵标准化处理 &#xff08;3&#xff09;计算指标信息熵值 Mj &#xff08;4&#xff09…

原生JS项目练习——验证码的生成及教验

一、主要功能介绍&#xff1a; 1、通过for循环生成生成六位随机验证码 2、通过for循环随机生成验证码颜色 3、窗口加载事件&#xff0c;窗口一加载就调用函数&#xff0c;重置验证码 4、按钮点击事件&#xff0c;一点击就调用函数&#xff0c;重置验证码 5、input输入框已失去焦…

Yarn概述

Hadoop系列 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark Flink Kafka Hbase…

公众号网课搜题接口系统

公众号网课搜题接口系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;…

快速开发微信小程序之二-微信支付

一、背景 在面试程序员的时候&#xff0c;有两项经历会带来比较大的加分&#xff0c;第一你是否做过支付金融相关的业务&#xff0c;第二你是否写过底层框架中间件代码&#xff0c;今天我们聊一下微信支付是如何对接的。 二、相关概念 1、微信商户平台 要使用微信支付&#…

一、mini2440_bsp_led

一、芯片手册 1、板子原理图 2、GPIO使用 &#xff08;1&#xff09;GPxCON &#xff08;2&#xff09;GPxDAT 二、实现分析 1、初始化led 设置GPBCON&#xff08;0x56000010&#xff09;为 0x00015400 2、设置led输出&#xff0c;根据原理图引脚输出低电平时灯被点亮 LED1…

K8s-临时容器 Ephemeral Containers

临时容器 Ephemeral Containers 当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec 无用时&#xff0c; 临时容器对于交互式故障排查很有用。尤其是&#xff0c;Distroless 镜像 允许用户部署最小的容器镜像&#xff0c;从而减少攻击面并减少故障和漏洞的暴露。 由于…

C | 枚举?看一遍就够了

CSDN话题挑战赛第2期 参赛话题&#xff1a;学习笔记 啊我摔倒了..有没有人扶我起来学习.... 目录前言枚举1. 枚举的定义2. 枚举的内存大小3. 枚举的优势4. 枚举需要注意的地方前言 结构体、枚举、联合体都是自定义类型&#xff0c;结构体主要知识点结构体内存对齐可参考《C | …

九月SLAM相关论文速递

九月SLAM相关论文速递 论文列表DirectTracker: 3D Multi-Object Tracking Using Direct Image Alignment and Photometric Bundle Adjustment3D VSG: Long-term Semantic Scene Change Prediction through 3D Variable Scene GraphsLeveraging Large Language Models for Robo…

使用服务器跑模型——案例1

案例1 该方法mac&#xff0c;linux&#xff0c;windows都通用。我们使用terminal or cmd进行操作。 假设我们本地具有一个需要跑的模型Unet&#xff0c;我们需要将该模型上传到服务器上跑&#xff0c;步骤如下&#xff1a; 使用tar压缩文件 我们定位到我们需要压缩的模型&a…

云原生之容器编排实践-以k8s的Service方式暴露SpringBoot服务

背景 上一篇文章云原生之容器编排实践-SpringBoot应用以Deployment方式部署到minikube以及弹性伸缩中&#xff0c;我们通过 Deployment 完成了将 SpringBoot 应用部署到 minikube 并测试了其弹性伸缩的丝滑体验。但是 Deployment 部署后我们还面临以下问题&#xff1a; 访问时…

Day761.Redis集群方案:Codis -Redis 核心技术与实战

Redis集群方案&#xff1a;Codis Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于Redis集群方案&#xff1a;Codis Redis 的切片集群使用多个实例保存数据&#xff0c;能够很好地应对大数据量的场景。哨兵集群&#xff0c; Redis 官方提供的切片集群方案 Redis Clus…

SPI总线通信——基于STM32MP157A

SPI总线概念 SPI总线是Motorola首先提出的全双工三线/四线同步串行总线&#xff0c;采用主从模式&#xff08;Master Slave&#xff09;架构&#xff1b;支持多从机&#xff08;slave&#xff09;模式应用&#xff0c;一般仅支持单主机&#xff0c;多从机。 时钟由主机控制&…

java培训技术处理模型数据之 ModelAndView

处理模型数据之 ModelAndView 1 ModelAndView介绍 控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息&#xff0c;也包含模型 数据信息。 2&#xff09;添加模型数据: MoelAndView addObject(String attributeName, Object attributeValue) ModelAndView…

C#-设计模式学习笔记

目录前言&#xff1a;最近得到师傅指点&#xff0c;建议我多学习下设计模式&#xff0c;简单记录下学习过程中的一些知识点1.设计模式&#xff08;创建型&#xff09;1.单例模式&#xff1a;1. 单例模式的主要作用2.单例模式能解决的问题3.单例模式的使用场景4.怎么实现单例模式…

Charles安装和抓包原理

进行APP服务器开发&#xff0c;接口测试、bug定位&#xff0c;抓取移动端请求数据包在所难免&#xff0c;公司使用的Charles&#xff0c;后面有机会使用了其它软件再做对比。Charles并不是安装即可用&#xff0c;涉及一些参数配置&#xff0c;特此记录分享。 1 安装、破解Char…