当前位置: 首页 > news >正文

【Linux 内核源码分析笔记】系统调用

在Linux内核中,系统调用是用户空间程序与内核之间的接口,它允许用户空间程序请求内核执行特权操作或访问受保护的内核资源。系统调用提供了一种安全可控的方式,使用户程序能够利用内核功能而不直接访问底层硬件。

系统调用:

  1. 通过系统调用,用户程序可以请求内核访问底层硬件设备,如磁盘、网络设备等。
  2. 系统调用允许用户程序创建、打开、读写和关闭文件,并进行进程管理操作,如创建新进程、发送信号等。
    3.通过系统调用,用户程序可以使用网络套接字进行网络通信操作,如建立连接、发送和接收数据等。
  3. 系统调用能够进行身份验证和权限检查,确保只有经过授权的进程才能执行特权操作。

特点:

  1. 当应用程序发起系统调用时,会导致从用户态切换到内核态执行相应的操作。这涉及到CPU状态的转换以及堆栈的切换。
  2. 由于所有对底层资源的访问都是通过系统调用进行的,内核可以对这些请求进行验证和控制,确保只有合法且经过权限检查的操作被执行。
    3.系统调用涉及到用户态到内核态的切换,这会引入一定的性能开销。因此,在设计应用程序时需要权衡系统调用的次数和性能需求。
  3. 不同架构和硬件平台上的用户程序可以使用相同的系统调用接口来与内核进行交互,这提供了跨平台兼容性。

与内核通信

Linux内核系统调用在用户空间进程和硬件设备之间添加了一个中间层。在Linux操作系统中,用户空间和内核空间是分离的。用户空间是指应用程序运行的环境,而内核空间是指操作系统内核运行的环境。

当用户空间进程需要进行一些需要操作系统的特权级别才能执行的操作时(例如访问硬件设备、创建新进程等),它必须通过系统调用来向内核发出请求。系统调用提供了一组接口,允许用户空间进程以标准化的方式与内核进行通信,并请求内核执行特定的操作。

当用户空间进程调用系统调用时,处理过程如下:

  • 用户空间进程调用系统调用函数,将请求传递给内核。
  • 内核根据系统调用的类型和参数,执行相应的操作。
  • 内核完成操作后,将结果返回给用户空间进程。
  • 用户空间进程继续执行后续的指令。

API、POSIX 和 C 库

printf函数实际上是一个用户空间的C库函数。当应用程序调用printf函数时,它会通过标准输入输出(stdio)库将数据发送给内核。然后,stdio库使用系统调用write将数据传递给内核。

在这个过程中,应用程序和C库之间有一个用户态到内核态的切换。应用程序通过软件中断(例如int 0x80或sysenter指令)触发系统调用,并将参数传递给相应的寄存器。然后,内核根据系统调用号找到相应的处理函数,并执行所需的操作。

对于printf函数而言,在内部它会使用一系列的写入操作来将数据写入stdout文件描述符(通常对应终端或标准输出)。接着,C库会调用write系统调用来向内核传递数据。
在这里插入图片描述

应用编程接口(API)

应用编程接口(API)是一组定义、协议和工具的集合,用于构建软件和应用程序。API允许不同的软件应用相互交互,是实现应用程序之间通信和数据共享的一种方式。API可以分为几种不同类型,包括操作系统级API、远程API、Web API等。

操作系统级API,如前面提到的Linux系统调用,提供了应用程序访问操作系统服务的方式。这些API使应用程序能够执行文件操作、进程控制、内存管理等功能。例如,当一个程序需要读取文件时,它会使用操作系统提供的API来执行这个操作。

POSIX 标准

POSIX(可移植操作系统接口,Portable Operating System Interface)是一系列 IEEE 标准,旨在促进应用程序与多个操作系统之间的兼容性。POSIX 定义了一套标准的操作系统 API,包括文件系统、设备、进程控制、信号、线程和网络通信等方面。这些标准通过提供一致的接口来帮助开发者编写在不同UNIX风格操作系统上都能运行的软件。

兼容性:Linux 作为一个类 UNIX 系统,大部分遵循了 POSIX 标准。这意味着编写符合 POSIX 的程序在 Linux 系统上通常可以不做修改或者只需很少修改就能运行。

系统调用实现:虽然 POSIX 定义了应用程序应该怎样与操作系统交互,但具体的实现细节由操作系统决定。Linux 内核提供的系统调用实现了 POSIX 标准中的许多功能。

非POSIX扩展:Linux 内核包含了一些非 POSIX 标准的系统调用和特性,这些通常为了利用 Linux 特有的功能或者为了提高性能等考虑。

C 库

Linux 系统中的标准 C 库(如 glibc)提供了 POSIX API 的实现。这些库函数通常会封装一层或多层系统调用,使得应用程序可以通过标准的 POSIX 接口与 Linux 内核通信。

系统调用

  • 调用库函数:通常情况下,程序员不会直接使用系统调用,而是会调用C标准库(如glibc)提供的封装函数,这些函数在内部会执行相应的系统调用。

  • 指定系统调用编号:每个系统调用都有一个唯一的编号,这个编号会告诉内核需要执行哪一个系统调用。

  • 设置参数:如果系统调用需要参数,这些参数必须按照规定的方式放置在寄存器中,以便内核能够读取。

  • 触发陷阱(中断):在x86架构中,这通常是通过执行int 0x80指令或者syscall指令实现的,在ARM架构中,这可能是通过svc指令。这个步骤将触发一个软件中断,将CPU从用户模式切换到内核模式。

  • 系统调用处理:内核中的系统调用处理程序接收到中断后,根据传递过来的系统调用编号找到对应的服务例程。

  • 执行系统调用:内核执行相应的服务例程,处理用户请求。

  • 返回结果:服务例程完成后,结果通过寄存器返回给用户空间,CPU切换回用户模式,应用程序继续执行。

如果一个程序想要读取文件,它可能会调用C库中的read()函数。read()函数内部会设置适当的参数(如文件描述符、缓冲区的指针、要读取的字节数等),然后执行syscall指令,并传递read系统调用的编号。内核接管控制权,执行文件读取操作,并将结果返回给用户空间的应用程序。

当系统调用发生错误时,大多数系统调用会返回一个特定的错误码。在C语言中,这些错误码通常通过一个名为errno的全局变量来报告。errno是一个由C标准库提供的、在发生错误时由系统设置的整数变量。通过调用 perror()库函数,可以把该变量翻译成用户可以理解的错误字符串。

asmlinkage 是一个用于定义内核中系统调用函数的宏。它告诉编译器和链接器使用特定的调用约定来处理这些函数。

在大多数体系结构上,用户空间代码和内核空间代码之间的函数调用有所不同。一般情况下,用户空间函数使用标准的C调用约定进行参数传递和返回值处理,而内核空间函数需要使用特殊的调用约定。

asmlinkage 宏通常与内核中的系统调用函数一起使用。它会告知编译器以及链接器使用适当的调用约定,以确保正确地传递参数和返回值。

系统调用号

系统调用号是操作系统内核提供的一组接口函数,用户程序可以通过这些接口函数来请求操作系统执行特定的功能。每个系统调用都有一个唯一的数字标识符,即系统调用号。

在Linux中,系统调用号被定义为一个整数,并通过汇编指令 int 0x80 或 syscall 来触发执行相应的系统调用。不同的操作系统和体系结构可能具有不同的方式来实现和管理系统调用。

通常,用户程序需要使用库函数(如C语言中的libc库)来封装底层的系统调用,以方便使用和处理错误。库函数会将高级语言风格的参数传递转换为底层内核接口所需的形式,并负责处理返回结果和错误码。

在Linux中,可以通过查看头文件 <sys/syscall.h> 或者参考文档来获取各个系统调用对应的编号。

在编译完用户程序中不保存系统调用函数的地址,而是保存一个系统调用号,内核通过该系统调用号查找对应的系统调用函数的地址。用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。

系统调用表

Linux内核中维护了一个称为系统调用表的数据结构,它是一个数组或者类似的结构,用于存储系统调用的函数指针。系统调用表将系统调用的编号与对应的处理函数联系起来。

当用户程序发起系统调用时,通过软中断或其他机制触发内核执行相应的操作。内核根据用户提供的系统调用编号在系统调用表中查找对应的处理函数,并跳转到该函数进行相应操作。这样可以实现用户空间与内核空间之间的交互和通信。

具体实现上,Linux使用一个名为sys_call_table的全局数组来表示系统调用表。每个数组元素都是一个函数指针,对应不同的系统调用处理函数。不过需要注意的是,由于安全性和稳定性等原因,在最新版本的Linux内核中,并没有直接暴露sys_call_table给用户态程序使用,而是通过特定方式进行访问。

系统调用处理程序

系统调用处理程序位于内核的特定部分,被称为系统调用表或者系统调用向量表。这个表中存储了每个系统调用对应的处理函数的地址。

当用户程序发起一个系统调用请求时,Linux内核会根据系统调用号来索引系统调用表,并执行相应的处理函数。具体步骤如下:

  • 用户程序使用int 0x80指令、syscall指令或者软件中断等方式触发一个中断。
  • 中断处理程序将控制权交给内核,并获取寄存器中保存的系统调用号和参数。
  • 根据系统调用号,查找系统调用表中对应的处理函数地址。
  • 执行对应的处理函数,在处理函数内部进行具体的操作,可能涉及到进程管理、文件操作、网络通信等。
  • 处理完成后,将返回值写入到适当的寄存器中,并返回到用户程序继续执行。

在x86架构上,常见的是使用int 0x80指令进行软中断;而在x86_64架构上,则多数情况下使用syscall指令。

通知内核的机制是靠软件中断实现的:

在x86架构上,用户程序可以使用int 0x80指令触发一个软中断。

当用户程序执行int 0x80指令时,CPU会暂停当前任务的执行,并将控制权交给内核中相应的软中断处理程序。内核根据传递给它的参数(例如系统调用号和参数)进行相应的操作,并返回结果给用户程序。

在x86_64架构上,通常使用syscall指令来触发软中断。

  • 用户程序触发异常,将系统调用号保存到寄存器
  • 进入中断,系统切换到内核态
  • 执行中断号 128(int 0x80) 的中断处理程序(注意软件中断和硬件中断入口不同),也就是系统调用处理程序system_call(),参数(系统调用号)从寄存器获取

执行系统调用处理程序

每个系统调用都有一个唯一的系统调用号(syscall number),用户程序通过传递这个系统调用号和相关参数来发起系统调用。当内核接收到系统调用请求时,它会根据系统调用号来确定要执行的具体操作,并从用户程序传递的参数中获取必要的数据。

一旦确定了要执行的操作,内核会进入特权模式,在内核空间中进行操作。例如,如果是文件读取操作,内核会打开对应的文件描述符,并从文件中读取数据;如果是网络通信操作,内核会管理套接字和网络协议栈等等。

完成所需的操作后,内核将结果返回给用户程序,并将控制权交还给用户程序继续执行。

根据寄存器内保存的系统调用号查系统调用表获取系统调用函数地址

要获取系统调用函数地址,可以按照以下步骤进行:

  1. 在内核代码中找到与系统调用相关的头文件(例如unistd.h),该头文件定义了系统调用号。
  2. 通过宏定义或常量获取对应平台和架构下的系统调用号变量(如 __NR_read)。
  3. 使用寄存器值(EAX或RAX)与该变量进行比较以确定具体的系统调用号。
  4. 通过索引方式访问系统调用表,在x86架构下,可使用 sys_call_table 符号来访问。
  5. 根据确定的索引,在表中找到相应位置上的函数指针,即为所需的系统调用函数地址。
call *sys_call_table(,%rax,8)

在x86-64架构下,系统调用表的地址通常存储在一个名为 sys_call_table 的全局变量中。

该行代码使用了 call 汇编指令和 *sys_call_table(,%rax,8) 表达式进行了间接寻址。这里使用了 %rax 寄存器作为索引,每个元素的大小为 8 字节(因此乘以 8)。然后使用 call 指令将对应索引位置上的函数地址作为目标进行调用。

参数传递

在x86-64架构下,系统调用使用以下约定:

  1. 系统调用号存储在 %rax 寄存器中。
  2. 系统调用的参数依次存储在 %rdi, %rsi, %rdx, %r10, %r8%r9 寄存器中,分别对应第一个、第二个、第三个、第四个、第五个和第六个参数。

例如,如果要执行 write 系统调用,则需要将文件描述符放入 %rdi,缓冲区地址放入 %rsi,以及要写入的字节数放入 %rdx
在这里插入图片描述

系统调用的实现

系统调用设计

  1. 系统调用号:每个系统调用都有一个唯一的标识符,称为系统调用号。这个号码被用户程序使用来指定要执行的具体系统调用。在Linux中,每个系统调用号都对应着一个特定的功能。

  2. 系统调用表:Linux维护了一个称为"系统调用表"的数据结构,其中存储了所有可用的系统调用函数对应的地址。该表以数组或哈希表等形式组织,并且由内核初始化并注册。

  3. 参数传递:当用户程序发起系统调用时,参数需要传递给内核。通常情况下,参数通过寄存器传递给内核函数。不同架构可能会有不同的寄存器约定。

  4. 中断与异常处理:当用户程序发起系统调用时,会触发一次从用户模式切换到内核模式(特权模式)的过程。这通常通过中断或异常来实现,在x86架构上可以使用陷阱门(trap gate)或任务门(task gate)来处理。

  5. 上下文切换:当进入内核空间进行系统调用处理时,会发生一次上下文切换。这包括保存当前进程的状态并加载目标进程的状态等操作。

  6. 返回值和错误码:每个系统调用在完成后都会返回一个结果给用户程序。如果系统调用出现错误,通常会返回一个特定的错误码,以便用户程序可以根据错误码采取相应的处理措施。

参数验证

Linux内核系统调用在处理时通常会对传递的参数进行检查和验证。这是为了确保参数的有效性和合法性,以避免安全漏洞或错误使用导致系统异常或数据损坏。

在内核中,可以使用各种方法来检查参数。一种常见的方法是通过函数调用中的条件判断语句或者条件分支来验证参数。例如,可以检查指针是否为空、长度是否合理、权限是否足够等。

参数可能包含指针类型的数据。这些指针可以用于传递或引用内存区域,从而让内核执行相应的操作。在处理参数中包含的指针时,通常需要进行一些额外的检查和验证,以确保指针的有效性和合法性。

当内核接收一个用户空间的指针时,内核必须确保以下几点:

  1. 检查指针的合法性:内核需要验证指针是否有效,并且指向用户空间。可以使用函数如access_ok()来进行检查。

  2. 权限检查:内核需要确保当前进程具有足够的权限来访问和操作指针所引用的用户空间数据。

  3. 边界检查:在处理用户空间指针时,必须注意边界条件。内核应该验证传递给它的长度参数,以避免越界访问或缓冲区溢出漏洞。

  4. 安全拷贝:如果需要将用户空间数据拷贝到内核空间进行处理,内核应该使用安全可靠的函数(如copy_from_user())来执行拷贝操作,以防止潜在的安全问题。

capable()函数是Linux内核中用于检查权限的一个函数。它的作用是判断当前进程是否有足够的权限来执行指定资源的操作。

capable()函数接受一个参数,表示要进行操作的资源类型或操作标志。如果返回值非零,则表示当前进程有权进行相应操作;如果返回值为0,则表示当前进程无权进行该操作。

系统调用上下文

系统调用上下文是指在执行系统调用时,进程所处的环境和状态。当进程执行一个系统调用时,它会切换到内核模式,并将控制权转移到操作系统内核中执行相应的内核函数来完成请求的操作。这个过程涉及到用户态和内核态之间的切换。

在系统调用上下文中,进程的用户空间堆栈和寄存器状态会被保存,然后切换到内核空间的堆栈和寄存器状态。在内核中处理完相应操作后,再将结果返回给用户空间并恢复用户态的执行。

在系统调用上下文中,进程可以访问一些特定于系统调用的参数、返回值以及其他与系统调用相关的数据结构。此外,在内核模式下,进程可以使用更高权限级别进行更底层的操作。

当进程处于内核空间执行系统调用时,虽然当前上下文属于内核,但仍然可以发生进程休眠和被抢占的情况。在多任务操作系统中,一个进程在执行系统调用期间可能被其他高优先级的进程抢占,并且切换到新的进程去执行。

为了保证可重入性,内核需要采取适当的措施来确保多个进程可以同时安全地调用相同的系统调用。这通常涉及使用锁、原子操作或其他同步机制来避免竞态条件和数据损坏等问题。

当系统调用处理程序(system_call())完成对系统调用的处理后,控制权确实会返回给该用户进程,使其能够继续执行。在处理完系统调用后,系统调用处理程序负责将用户进程的上下文恢复,并将控制权切换回用户空间,让用户进程继续从系统调用之后的位置开始执行。这样,用户进程就可以继续执行其后的指令,完成相应的操作。

系统调用处理程序(system_call())在完成系统调用处理之后,通常会使用一些特殊的机制(如中断机制或返回指令等)将控制权交还给用户进程。

注册系统调用

  1. 在内核源代码中定义系统调用号:在include/linux/syscalls.h文件中,为新的系统调用添加一个对应的宏定义。这个宏定义包含了系统调用号以及函数名。

  2. 实现系统调用函数:在合适的地方编写实现新系统调用功能的代码,并将其添加到内核源码中。

  3. arch/<架构>/kernel/syscall_table.S文件中更新系统调用表:根据所使用的架构,在对应目录下找到syscall_table.S文件,并将新的系统调用函数名添加到相应位置。

  4. 更新头文件和Makefile:根据需要,可能需要更新相关头文件和Makefile来确保新的系统调用能够正确编译和链接。

  5. 重新编译和安装内核:通过编译和安装修改后的内核源码,使之生效。

从用户空间访问系统调用

  1. 包含必要的头文件:首先,需要包含相关的头文件,以便在用户程序中使用系统调用的函数和常量。

  2. 使用系统调用号:每个系统调用都有一个唯一的系统调用号。你可以通过查看相应的文档或头文件来找到所需的系统调用号。

  3. 调用系统调用函数:使用定义好的系统调用号,可以直接通过 C 或 C++ 的库函数(如syscall)来进行系统调用。具体语法可能因编程语言而异。

  4. 处理返回值:在成功完成系统调用后,它将返回一个值表示操作是否成功。根据返回值进行适当的错误处理或继续执行程序逻辑。

通常依赖于C库(如glibc)提供的中间函数。用户程序可以通过这些中间函数来访问底层的系统调用。

用户程序通常使用标准C库(如glibc)提供的API函数进行编程。当用户程序调用这些API函数时,它们实际上会调用相应的中间函数。这些中间函数负责将参数传递给底层的系统调用,并处理与系统调用相关的细节。然后,中间函数通过软件中断或者其他机制将控制权传递给内核空间执行相应的系统调用操作。

通常,系统调用靠 C 库支持。用户程序通过如 glibc 提供的中间函数访问系统调用。

如果需要调用的系统调用未被 glibc 库支持,可以使用以下几种方法:

  1. 直接使用底层的系统调用接口:每个系统调用都有一个对应的编号,在Linux中以整数形式表示。你可以使用syscall函数来直接发起对底层系统调用的请求,传递相应的参数和编号。这样可以绕过glibc提供的中间函数,直接与内核进行交互。但是这种方法需要更深入地了解底层操作系统和硬件架构,并且可移植性较差。

  2. 编写自己的封装函数:如果需要频繁地调用某个未被glibc库支持的系统调用,你可以编写自己的封装函数来实现该功能。通过编写一个简单的C语言或汇编语言代码来包装底层系统调用,使其更易于使用和管理。这样可以提高代码的可读性和可维护性。

  3. 使用其他第三方库或工具:除了glibc之外,还存在一些其他第三方库或工具可以访问未被glibc支持的系统调用。例如,在Linux上可以使用libsyscall库或者直接使用libc子集(如musl libc)等。这些库可能提供对更多、更特定系统调用的支持。

相关文章:

【Linux 内核源码分析笔记】系统调用

在Linux内核中&#xff0c;系统调用是用户空间程序与内核之间的接口&#xff0c;它允许用户空间程序请求内核执行特权操作或访问受保护的内核资源。系统调用提供了一种安全可控的方式&#xff0c;使用户程序能够利用内核功能而不直接访问底层硬件。 系统调用&#xff1a; 通过…...

mysql清空并重置自动递增初始值

需求&#xff1a;当上新项目时&#xff0c;测试环境数据库导出来的表id字段一般都有很大的初始递增值了&#xff0c;需要重置一下 先上代码&#xff1a; -- 查看当前自动递增值 SHOW CREATE TABLE table_name; -- 重建自动递增索引&#xff08;可选&#xff09; ALTER TABLE t…...

计算机算法之二分算法

文章目录 前言核心问题遍历查找思路遍历查找代码实现遍历查找缺点二分查找思路二分查找代码实现二分查找优点二分查找的变种问题一解题思路代码实现问题二解题思路代码实现 前言 大家好&#xff0c;我是醉墨居士&#xff0c;今天聊一下计算机中的经典算法 - 二分算法 核心问题…...

获取当前设备的IP

背景&#xff1a; 在本地使用自带webUI的项目时&#xff0c;需要制定webUI的访问地址。 一般本地访问使用&#xff1a;127.0.0.1&#xff0c;配置为可以从其他设备访问时&#xff0c;需要指定当前设备的IP&#xff0c;或者指定为0.0.0.0。 例如&#xff1a;使用locust的时候&a…...

koa2文件的上传下载功能

const Router require(“koa-router”); const upload new Router(); const bodyParser require(“koa-bodyparser”); const multer require("koa/multer"); const path require(“path”); const article require("…/utils/sql"); const { getCur…...

test-02-test case generate 测试用例生成 EvoSuite 介绍

拓展阅读 junit5 系列 基于 junit5 实现 junitperf 源码分析 Auto generate mock data for java test.(便于 Java 测试自动生成对象信息) Junit performance rely on junit5 and jdk8.(java 性能测试框架。性能测试。压测。测试报告生成。) 拓展阅读 自动生成测试用例 什么…...

1.单表查询

作业要求 素材&#xff1a; 表名&#xff1a;worker-- 表中字段均为中文&#xff0c;比如 部门号 工资 职工号 参加工作 等 CREATE TABLE worker ( 部门号 int(11) NOT NULL, 职工号 int(11) NOT NULL, 工作时间 date NOT NULL, 工资 float(8,2) NOT NULL, 政治面貌 varc…...

FFmpeg 的使用与Docker安装流媒体服务器

本文阐述的均为命令行的使用方式&#xff0c;并不牵扯FFmpeg 的 C音视频开发内容&#xff0c;补充一句&#xff0c;C的资料真的少&#xff0c;能把C学好的人&#xff0c;我真的是觉得巨佬。 我主要是使用FFmpeg 推流方面的知识&#xff0c;案例大都是靠近这方面。 一、FFmpeg…...

Qt QListWidget列表框控件

文章目录 1 属性和方法1.1 外观1.2 添加条目1.3 删除条目1.4 信号和槽 2 实例2.1 布局2.2 代码实现 Qt中的列表框控件&#xff0c;对应的类是QListWidget 它用于显示多个列表项&#xff0c;列表项对应的类是QListWidgetitem 1 属性和方法 QListWidget有很多属性和方法&#xf…...

小知识分享2

文章目录 1.TCP/IP协议2.四次挥手断开连接3.TCP的三次握手和四次挥手4.在什么情况下需要设置WINS Proxy&#xff1f;5.用户与用户账户有什么不同&#xff1f;为什么需要使用用户账户&#xff1f; 1.TCP/IP协议 1、TCP/IP、Transmission Control Protocol/internet Protocol,传…...

【Golang开源项目】Golang高性能内存缓存库BigCache设计与分析

项目地址 BigCache 是一个快速&#xff0c;支持并发访问&#xff0c;自淘汰的内存型缓存&#xff0c;可以在存储大量元素时依然保持高性能。BigCache将元素保存在堆上却避免了GC的开销。 背景介绍 BigCache的作者在项目里遇到了如下的需求&#xff1a; 支持http协议支持 10…...

Elasticsearch 7.8.0从入门到精通

安装Elasticsearch 7.8.0 官网&#xff1a;Elasticsearch 7.8.0 | Elastic 大家下载所需要的安装包即可。然后解压缩&#xff1a; Elasticsearch是通过java编写的&#xff0c;所以自带jdk。多好&#xff0c;下载Elasticsearch赠送jdk 0.0&#xff0c;不过一般我们用自己的jdk…...

寻找最富裕的小家庭 - 华为OD统一考试

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 在一棵树中,每个节点代表一个家庭成员,节点的数字表示其个人的财富值,一个节点及其直接相连的子节点被定义为一个小家庭现给你一棵树,请计算出最富裕的小家庭的财富和。 输入描述 第一行为一个数N,…...

ssm基于Java的药店药品信息管理系统的设计与实现论文

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;药品信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广大…...

Word插件-大珩助手-手写电子签名

手写签名 支持鼠标写&#xff0c;支持触摸屏写&#xff0c;点击画笔按钮切换橡皮擦&#xff0c;支持清空画板重写&#xff0c;点击在word中插入签名&#xff0c;可插入背景透明的签字图 素材库-保存签名 将写好的签字图复制粘贴到素材库中&#xff0c;以便永久使用&#xff…...

Edge扩展插件安装位置

根据所获取的信息&#xff0c;Microsoft Edge的扩展插件安装位置和配置方式可以通过不同的方法管理。以下是一个大纲&#xff0c;概述了如何配置和管理Edge扩展插件&#xff1a; Edge扩展插件安装和管理大纲 了解扩展插件的安装模式 安装模式的选项&#xff1a;了解allowed、…...

Git将本地项目上传到Gitee仓库

1.右键点击文件&#xff0c;点击Git Bash Here,进入git窗口 2.初始化本地仓库 git init3.将本地仓库与远程仓库建立连接 git remote add origin 远程仓库地址远程仓库地址在gitee仓库复制即可 4.将远程仓库的文件拉到本地仓库中 git pull origin master5.将本地文件全部上传…...

linux环境安装docker

一、Docker是什么? 当我们开发一个应用程序时&#xff0c;通常需要配置和安装各种软件、库和依赖项。而这些环境配置可能会因为不同的操作系统或版本而存在差异&#xff0c;导致应用在不同环境中运行出现问题。 Docker就像是一个集装箱&#xff0c;可以将应用程序及其所有依…...

机器人技能学习-robosuite-0-入门介绍

文章目录 前言模块介绍实战案例1&#xff1a;从 demo 中创建自己的 env案例2&#xff1a;更换属于自己的物体 前言 资料太少、资料太少、资料太少&#xff0c;重要的事说三边&#xff0c;想根据自己实际场景自定义下机器人&#xff0c;结果发现无路可走&#xff0c;鉴于缺少参…...

【工具】tmux简单用法

tmux 是一个终端复用工具&#xff0c;允许你在单个终端窗口中运行多个终端会话&#xff0c;并在它们之间切换。它提供了分割窗格、多窗口和会话管理等功能&#xff0c;使得在终端中更加高效地工作。 以下是一些 tmux 的基本概念和简单应用&#xff1a; 会话 (Session): 一个 t…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …...

OPENCV图形计算面积、弧长API讲解(1)

一.OPENCV图形面积、弧长计算的API介绍 之前我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天我们主要结合轮廓检测的API去计算图形的面积&#xff0c;这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能&#xff0c;常用的API…...

数据挖掘是什么?数据挖掘技术有哪些?

目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...

Ubuntu 安装 Mysql 数据库

首先更新apt-get工具&#xff0c;执行命令如下&#xff1a; apt-get upgrade安装Mysql&#xff0c;执行如下命令&#xff1a; apt-get install mysql-server 开启Mysql 服务&#xff0c;执行命令如下&#xff1a; service mysql start并确认是否成功开启mysql,执行命令如下&am…...