book-riscv-rev1.pdf 翻译(自用,更新完成)第一章 操作系统接口

news2024/11/25 20:33:58

Job of operating system:

操作系统使得多个程序分享一台计算机,提供一系列仅靠硬件无法支持的服务。

  1. 管理与抽象低级别硬件(如:文件处理程序不需要关注使用哪种硬盘)
  2. 使得多个程序分享硬件(programs that can run at the same time)
  3. 为程序提供可控的相互交流(分享数据或工作)的方式。

操作系统是通过interface为用户程序提供服务,然而设计良好的接口并不容易:

一方面,希望接口简单且应用场景单一,以实现起来更简单;

一方面,希望提供复杂的feature给应用。

So,设计依赖机制较少,但可结合起来以提供更具通用性的接口。

        本书使用一个操作系统作为例子来诠释操作系统的概念。xv6 系统提供了一些由 Ken Thompson 与 Dennis Ritchie 的 Unix系统引入的基本接口,同时也模仿了Unix 系统的内部设计。Unix 系统提供的狭窄接口能很好底结合起来,以实现令人惊喜的通用性。此接口是如此的成功,以至于现代操作系统,BSD,Linux,Mac OS X, Solaris, 甚至Microsoft Windows 都具有类 Unix 的接口。理解 xv6 是理解这些操作系统的不错的开端。

        正如 图1.1所示,xv6 采用了传统的kernel形式,内核是向用户程序提供服务的特定程序。每个运行的程序称之为进程(process),都具有其对应的内存空间(memory),内存中包含指令、数据与栈(instructions, data, and a stack)。

  1. 指令 (instructions) 实现程序的计算 (computation)
  2. 计算基于变量 (data)
  3. 栈组织程序的过程调用 (procedure calls)

一台计算机通常有很多进程,但仅一个内核。

        当进程需要 invoke 内核服务时,程序 invoke 系统调用 (system call),系统调用进入内核,内核服务并返回,所以进程在用户空间 user space 与内核空间 kernel space 之间交替执行。

        内核使用CPU的硬件保护机制 (hardware protection mechanisms) 以保证每个执行在用户空间的程序只能访问自己的内存。内核拥有实现此硬件保护机制所需的硬件特权 (hardware privileges) ,用户程序 (user program) 无此特权。当用户程序 invokes 一个系统调用时,硬件会提高特权级别并开始执行内核中的 pre-arranged 函数。

        内核提供的系统调用集是用户程序可见的 interface。Xv6内核提供的服务与系统调用,是传统 UNIX内核提供的子集。

        本章的剩余部分将提到 xv6 系统的服务:进程,内存,文件描述符,管道以及一个文件系统,通过代码段与讨论shell使用这些服务的方法,来详述xv6服务。shell(Unix的命令行用户接口)对系统调用的使用显示了这些系统调用设计的精巧。

        shell 读取用户输入并执行,其作为一个用户程序,而非内核的一部分,也体现了 system call interface 的强大之处。shell 并不特殊,易被取代, 现代UNIX系统有一系列shell可供选择。The xv6 shell is a simple implementation of the essence of the Unix Bourne shell.

1.1 进程与内存

        一个 xv6 进程包含用户空间的内存(指令,数据与栈),以及仅内核可见的各进程状态。xv6 为等待执行的进程间分配CPU秒。当一个进程不在执行时,xv6 保存其CPU寄存器,下次执行时恢复。内核为每个进程关联一个进程标识符(PID)。

        一个进程(父进程)可以通过 fork 系统调用创建新进程。fork 创建的新进程称之为子进程,子进程拥有内存的内容与父进程相同。fork 在子进程与父进程都返回,具体而言,父进程中返回子进程的 PID,子进程中返回 0。例如,下面代码段

int pid = fork();
if(pid > 0){
    printf("parent: child=%d\n", pid); //1
    pid = wait((int *) 0);
    printf("child %d is done\n", pid);
} else if(pid == 0){
    printf("child: exiting\n");//2
    exit(0);
} else {
    printf("fork error\n");
}

        exit 系统调用终止调用进程的执行,释放资源,如内存及打开的文件。Exit 接受一个整型数,一般 0 表示成功,1 表示失败。wait 系统调用返回当前进程的某个退出/终止的子进程的PID,拷贝子进程的退出状态,并传递给 wait 的地址参数,如果调用进程无子进程退出,wait 等待某个子进程退出;若调用进程无子进程,wait 立刻返回 -1。当然,如果调用进程不在乎子进程的退出状态,完全可以给 wait 传递一个 0 地址。

(注意1,2 处的打印顺序不一定)

        尽管,开始子进程的内存内容与父进程相同,但是,二者通过不同的内存与寄存器执行,改变某个进程中的某个变量不会影响另一个进程中该变量的值。如,wait 返回值存在父进程的 pid 中,这不会改变子进程的pid 变量,其值依然为0。

        exec 系统调用使用加载自文件的新 memory image取代调用进程的内存。使用的文件必须为特定格式,以指明文件的哪个部分为指令、数据,以及从哪个指令开始执行等。xv6 使用 ELF 格式,第三章将讨论更多细节。当 exec 成功执行时,不会返回到调用进程,加载自文件的指令从ELF header 中声明的入口点开始执行。exec 有两个参数:文件名,字符串数组。

char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");

        此代码段用 参数列表为 “echo hello” 的/bin/echo 程序实例取代调用进程。大多数程序忽略参数数组的首元素(一般为程序名)。

        

        xv6 shell 使用以上的系统调用代替用户执行程序。shell 的主要结构很简单,参考 user/sh.c 内的main函数。主循环使用 getcmd 读取用户的一行输入。然后调用 fork 拷贝 shell 进程。父进程调用 wait,而子进程执行命令。例如,如果用户向 shell 输入 “echo hello”,runcmd 被调用的同时被传递了参数 "echo hello",runcmd 实际执行命令。对于 "echo hello",exec 将被调用。若 exec 成功,子进程将执行来自 echo 而非 runcmd 的指令。某个点,echo 调用 exit ,这将使得父进程从 wait 返回。

        为什么 fork 与 exec 不作为一个调用整体实现呢?之后我们将发现这种分隔在shell 实现 IO重定向时大有用处。立即替换(exec)一个刚刚被创建的复制进程是一种浪费,为了避免这种浪费,通过虚拟内存技术,如写时复制,内核优化了 fork 的实现。

        xv6 大多数时候分配内存是显式的:fork 分配子进程所需用来拷贝父进程内存的内存,exec 分配存储可执行文件的内存。一个进程在运行时若需要更多空间,可调用 sbrk 实现增长 n 字节的数据内存。sbrk 返回新内存的位置。

1.2 I/O 与 文件描述符

        文件描述符是小的整型,指代一个进程可读写的,受内核管理的对象。一个进程可以通过打开一个文件、目录、设备或创建一个管道,或复制一个已存的描述符来获得一个文件描述符。为了简要起见,我们通常将一个文件描述符指向的对象视为一个 “文件”,文件描述符接口通过抽象,消除了文件、管道、设备之间的差异性,使之都可被视为字节流。输入输出将被视为 I/O

        xv6 内核使用文件描述符作为一个各进程都拥有的一张表的索引,每个进程有一个保存从0开始的文件描述符的私有空间。传统上,一个进程从 0 文件描述符(标准输入)读取,向 1 文件描述符(标准输出)写入,向 2 文件描述符(标准错误) 写入错误消息。正如我们即将看到的那样,shell 基于这种惯例实现 IO重定向与管道。shell 确保总是有三个文件描述符为开启状态,也正是console 的默认文件描述符。

        

        read write 系统调用分别从或向文件描述符指代的打开文件读取或写入一定的字节数。read (fd, buf, n)  从文件描述符 fd 读取至多 n 字节,并拷贝内容到 buf 内,同时返回成功读到的字节数。每个文件描述符指代的文件都对应的偏移量 offset。Read 从文件当前的偏移量开始读取数据,并且偏移量会增长实际读到的字节数:后面的 read 返回的字节数会紧随着前一个 read 返回的字节。若无更多字节可读,read 返回 0 来表示文件末尾。

        write (fd, buf, n) 从 buf 取n字节写入到文件描述符 fd,并返回成功写入的字节数。除非发生错误才会写入少于 n 字节。就像 read,write 从文件当前的偏移量开始写入,并且偏移量会增长实际读到的字节数。每个 write 调用从之前的偏移量开始。

        

        下面代码段(cat 程序的核心)从标准输入读取,向标准输出写入。若发生错误,则向标准错误写入一条消息。

char buf[512];
int n;
for(;;){
    n = read(0, buf, sizeof buf);

    if(n == 0)
        break;

    if(n < 0){
        fprintf(2, "read error\n");
        exit(1);
    }

    if(write(1, buf, n) != n){
        fprintf(2, "write error\n");
        exit(1);
    }
}

        需要注意的是,cat 不知道它读取数据的来源是文件,console 还是管道。类似的,cat 也不知道被写入的是文件还是什么。文件描述符的使用,加上0作为标准输入,1 作为标准输出的惯例,使得 cat 的简单实现成为可能。

        

        close 系统调用释放一个文件描述符,使之可以被之后的 open, pipe, 或 dup 系统调用重用。新分配的文件描述符总是当前进程未使用的最小描述符。

        

        文件描述符与 fork 交互可以简单地实现 I/O 重定向。fork 拷贝父进程的文件描述符表到其子集的内存,子进程开始拥有与父进程完全相同的文件描述符。exec 系统调用替换调用进程的内存,但是会保留文件表。这样,shell 可以通过 fork,重新打开子进程中的指定文件描述符,然后调用 exec 运行新程序来实现 I/O重定向。下面展示了 shell 对应命令 " cat < input.txt " 执行了怎样的代码(简化后的)。

char *argv[2];
argv[0] = "cat";
argv[1] = 0;
if(fork() == 0) {
 close(0);
 open("input.txt", O_RDONLY);
 exec("cat", argv);
}

        子进程关闭文件描述符0后,open 必然使用0,即当前可获取的最小的文件描述符,指向新打开的文件 input.txt。cat 执行时 0 指向文件 input.txt。当然父进程的文件描述符不会改变。

        ·xv6 shell 的 I/O重定向代码就是如此运行的(user/sh.c 82)。记住,代码到这里 shell 已经创建(forked)了 子shell,而 runcmd 将调用 exec 来加载新程序。

        open 系统调用的第二个参数由一系列以 比特位 表示的flag值组成,这些 flag 值决定了 open 的行为。可能的值在 fcntl 头(kernel/fcntl.h)内有定义:O_RDONLY, O_WRONLY, O_RDWR, O_CREATE 与 O_TRUNC,对应 open 会以读、写、读写,或若文件不存在则创建的方式打开文件并文件长度截取为0 的方式打开文件。

        现在就很清楚了,为什么 fork 与 exec 是各自独立的系统调用,在二者之间,shell 有机会在不干扰主 shell 的 I/O 的前提下重定向子 shell 的 I/O。假设有一个结合二者的名为 forkexec 的系统调用,但是这样实现 I/O 重定向就有点尴尬了。shell 可以在调用 forkexec 前设置 I/O,之后再恢复设置;或者 forkexec 可以将 I/O 重定向的指令作为参数;或者每个程序如 cat 可以自己实现重定向。

        尽管 fork 拷贝文件描述符表,底层每个文件的偏移量都被父子进程共享。考虑下面这个例子:

if(fork() == 0) {
    write(1, "hello ", 6);
    exit(0);
} else {
    wait(0);
    write(1, "world\n", 6);
}

        代码段尾,文件描述符1指向的文件将包含 " hello world ",因为由 wait,父进程中的 write 将接着子进程write 写后的位置继续写入。这一行为,可以根据 shell 命令的顺序产生序列化的输出,就像 echo hello; echo world > output.txt。

        dup 系统调用拷贝已存的文件描述符,返回指向同一底层 I/O对象的新文件描述符。两个文件描述符共享偏移量,就像 fork 拷贝的描述符那样。下面是向文件写入 hello world 的另一种方式。

fd = dup(1);
write(1, "hello ", 6);
write(fd, "world\n", 6);

        如果两个文件描述符因顺序调用的 fork 与 dup 而从一个原始的文件描述符产生,则共享偏移量。否则,尽管对同一个文件调用 open 返回,文件描述符也不共享偏移量。dup 允许 shell 这样实现命令:ls existing-file non-existing-file > tmp1 2>&1。2>&1 告诉 shell 为该命令分配文件 描述符2 作为描述符1 的拷贝。existing file 的名字,与 non existing file 的错误消息都将出现在 tmp1 文件内。xv6 shell 不支持错误文件描述符的 I/O 重定向,但是现在你知道怎样实现它。

        文件描述符的抽象是很强大的,因为它隐藏了其指向对象的细节:向 1文件描述符写入的进程可能在向一个文件,设备(如 console)或管道写入。

1.3 管道

        一个管道实际上是一块内核buffer,以一对文件描述符的形式提供给进程,一个作为读取,另一个作为写入。向管道的一段写入数据,则数据在管道的另一端可读。管道提供了进程间相互通信的方式。

        下列代码通过将标准输入关联到管道读端来执行程序 wc。

int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);
if(fork() == 0) {
    close(0);
    dup(p[0]); // 读端 - 标准输入
    close(p[0]);
    close(p[1]);
    exec("/bin/wc", argv);
} else {
    close(p[0]); //关闭读端
    write(p[1], "hello world\n", 12);//向写端输入
    close(p[1]);
}

        调用pipe创建新管道,在数组p中记录读写文件描述符。Fork之后,父子进程都有指向该管道的文件描述符。子进程调用 close dup 使得0描述符指向管道的读端,再关闭p数组的文件描述符,调用 exec 指向wc程序。wc从标准输入读取时,实际j就从管道读取。父进程关闭管道的读端,向管道写入,然后关闭写端。

        如果没有可读数据,作用于管道的read等待数据写入,或指向写端的全部文件描述符被关闭,后续的read返回0,就像到达文件的末尾。read会阻塞直到不可能有新数据到达,这有一个重大影响,即子进程在执行wc之前要关闭管道写端。如果wc的某个文件描述符指向管道的写端,则wc永远到达不了end-of-file。

        通过类似上面代码的方式,Xv6 shell实现了如 grep fork sh.c | wc -l 这样的pipeline。子进程创建连接pipeline左右两端的管道,然后为pipeline的左右两端分别调用 fork 与 runcmd,等待其完成。Pipeline 右端可能是自身包含管道的命令,如a|b|c,该命令自己fork两个子进程(a | b | c)。因此,shell可能创建进程树。树的叶子是命令,内部节点是等待左右子进程结束的进程。

        理论上,可以用内部节点执行pipeline的左侧,但这样可能使得实现更加复杂。考虑做下面的修改: sh.c 不在内部节点为了 p->left 调用 fork 以及执行运行 runcmd (p->left)。例如:echo hi | wc 不会产生输出,因为当 echo hi 在 runcmd中退出时,内部进程退出,就不会再调用 fork 以执行管道的右端。当然,通过取消内部进程的 runcmd取消调用 exit 可以修复此行为,但是同时会加大了代码的复杂度:runcmd 需要知道当前是否是一个 内部进程。取消为了 runcmd (p->right) 而调用 fork也会提高复杂度。例如,sleep 10 | echo hi 会立刻打印 “hi” 而不是先等待10秒,因为 echo 立刻运行并退出,不等待 sleep 完成。既然 sh.c的设计旨在尽可能简单,就不会刻意避免创建内部进程。

        

        管道也许看上去并没有比临时文件强到哪里,比如下面的pipeline:

        echo hello world | wc

        完全可以不借助管道机制实现:

        echo hello world > /tmp/xyz; wc < /tmp/xyz

        实际上,就这种场景而言,管道至少有4点优于临时文件。首先,管道会自动清理,shell不必特意删除 /tmp/xyz;其二,管道能传递任意长度的数据流,而文件重定向需要磁盘(disk)上有足够的可用空间以存储数据;其三,管道允许并行的pipeline,而文件方法必须等待前一程序完成后再执行下一程序;最后,进程间通信时,管道的读写阻塞机制相比文件的非阻塞行为更有效率。

1.4 文件系统

        xv6 文件系统由文件与目录组成,其中文件包含无解释字节数组,目录包含指向文件或其它目录的名字。目录有树结构,从 root 目录开始。以 /a/b/c路径为例,其指向一个名为c的文件或目录,c在b目录下,b在a目录下,a在根目录 / 下。不以 / 开头的路径为相对路径,相对于调用进程的当前路径,chdir系统调用可以改变调用进程的当前目录。下面代码打开同一文件。

chdir("/a");
chdir("b");
open("c", O_RDONLY);

open("/a/b/c", O_RDONLY);

        第一段代码改变进程的当前路径为 /a/b;第二个未改变。

        有些系统调用可创建新的文件与目录:mkdir 创建目录,通过O_CREATE 标志位调用的open 创建文件,mknod创建新设备文件。例子:

mkdir("/dir");
fd = open("/dir/file", O_CREATE|O_WRONLY);
close(fd);

mknod("/console", 1, 1);

        

        mknod 创建指向设备的特殊文件,mknod 的两个参数为关联到设备文件的主要及次要设备号,这两个参数唯一标识了内核设备。当一个进程打开设备文件时,内核将read 与 write 系统调用转向到内核设备实现,而非将这两个系统调用传递给文件系统。

        一个文件的名字不等同于文件自身。同一个底层文件,称之 inode节点,可以有多个名字,称之 links(链接)。每个link 由目录中的一条数据项组成,该数据想包含文件名与inode节点的引用。一个inode节点存储了对应文件的metadata(元数据),包含类型(文件、目录、设备),长度,文件内容在磁盘上的位置,指向该文件的链接数量。

        fstat 系统调用从文件描述符指向的inode节点取到信息,为一个定义在 kernel/stat.h里的struct stat 结构体赋值:

#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEVICE 3 // Device
struct stat {
    int dev; // File system’s disk device
    uint ino; // Inode number
    short type; // Type of file
    short nlink; // Number of links to file
    uint64 size; // Size of file in bytes
};

        link 系统调用在文件系统创建另一个指向同一 inode 的名字。下面代码创建了一个新文件,该文件有两个名字:a 与 b。

open("a", O_CREATE|O_WRONLY);
link("a", "b");

        向a 读写等价于向b读写。每个inode由唯一的inode编号标识。上面的代码执行后,可以通观察 fstat 的结果判断是否a,b指向同一个底层文件:返回相同的inode 编号(ino),nlink 计数设为2。

        ulink 系统调用从文件系统删除一个名字。文件的inode与存储文件内容的磁盘空间仅当文件的链接计数为0 且无文件描述符指向时才被释放。因此,添加下面一行代码,就只能通过b获取inode与文件内容。进一步,下面是一种常用的创建无名临时inode节点的手法,这样当进程关闭 fd 或退出时,该inode被清理。

fd = open("/tmp/xyz", O_CREATE|O_RDWR);
unlink("/tmp/xyz");

        Unix 提供shell可调用的文件工具作为用户级别的程序,例如 mkdir rm。这种设计使得用户可通过添加新的用户级别程序来扩展命令行接口。这种设计现在看来是理所当然的,但是,与Unix同时代其它系统一般是将这些命令嵌入 shell (built into)。

        一个特例是 cd,这个是嵌入 shell的(user/sh.c)。cd必须改变shell自身的当前工作目录。如果cd 作为一个普通命令运行,然后shell会创建一个子进程,子进程运行cd,cd会改变子进程的工作目录。父进程(即shell)的工作目录不会改变。

1.5 真实世界

        Unix将标准的文件描述符、管道及对应的便利的操作特性结合起来,对于编写通用的可重用程序是一个重大进步。这催生了一种称之为软件工具(software tools)的文化,这种文化对Unix的强大与普及有重大影响,shell则是首个被成为脚本语言的。在今天的BSD,Linux与Mac OS X系统中仍然可见Unix系统调用接口(interface)的影子。

        Unix系统调用接口通过 POSIX 实现了标准化。Xv6 不是POSIX标准的,缺少了很多系统调用(包括基础接口如 lseek),它提供的很多系统调用也与标准的不同。Xv6 系统的目标在于实现类UNIX系统调用的同时保持简洁。几个人为了运行基本的UNIX程序,为Xv6 扩展了一些系统调用与一个简单的C库。现代内核,提供了多得多的系统调用,以及比xv6多得多的内核服务。比如,支持网络,窗口系统,用户级别的线程,设备驱动等等。现代内核持续地快速迭代,提供了POSIX外的很多特点。

        UNIX用一个简单的文件名与文件描述符接口的集合,统一了多种资源的入口(文件,目录,设备)。当然这种设计也适用于其它资源,一个很好的例子是 Plan9,这个将“资源即文件”的理念拓展到网络,图像等等。然而,大多数源自UNIX的操作系统并没有遵循。

        文件系统于文件描述符的抽象是很强大的。尽管如此,操作系统接口也有其它的模型。Multics,作为UNIX之前的操作系统,将文件存储抽象得像内存一样,因此接口的风格很不同。Multics系统的设计对UNIX系统有直接的影响,尽管UNIX希望实现更简单的设计。

        Xv6并没有用户的概念,更不会保护用户不受其他用户的影响。用UNIX的术语来说,所有的xv6进程作为root进程运行。

        这本书说明了xv6怎样实现类UNIX接口,但是这些理念适用范围远不止UNIX。任意操作系统都必须实现对底层硬件的多路复用,屏蔽进程与其它进程,提供受控的进程间通信机制。学习xv6 系统后,可以理解更复杂的操作系统,其它操作系统体现了xv6的一些特点。

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

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

相关文章

797. 差分(C++和Python3)——2023.5.7打卡

文章目录 QuestionIdeasCode Question 输入一个长度为 n 的整数序列。 接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c &#xff0c;表示将序列中 [l,r] 之间的每个数加上 c 。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数 n 和 m 。 第二…

高性能流媒体硬解码

目录 高性能流媒体硬解码前言1. What、Why and How1.1 What1.2 Why&#xff1f;1.3 How&#xff1f; 2. 离散傅里叶变换(DFT)2.1 JPEG编码2.2 哈夫曼编码(Huffman Coding) 3. 视频流媒体前置知识3.1 视频文件的构成3.2 视频的编码与封装3.3 H264编码的分层3.4 RTSP之RTP(Real-t…

【clang】安装clang编译器并将linux默认编译器改为clang

目录 省心操作法手动安装 本文是他人智慧的总结个人实践。 省心操作法 缺点&#xff1a; clang版本不能自己选择&#xff1b;我的系统是ubuntu 20&#xff0c;安装后是clang10.0版本 优点&#xff1a;省心&#xff0c;基本不会踩坑 手动安装 参考这篇文章ubuntu20.04 安装cl…

RabbitMQ --- 死信交换机

一、简介 1.1、什么是死信交换机 什么是死信&#xff1f; 当一个队列中的消息满足下列情况之一时&#xff0c;可以成为死信&#xff08;dead letter&#xff09;&#xff1a; 消费者使用basic.reject或 basic.nack声明消费失败&#xff0c;并且消息的requeue参数设置为false…

超详细-自动化测试从选型到落地,2023年我从10k涨到了18k*14薪...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

系统集成项目管理工程师 下午 真题 及考点(2021年上下半年)

文章目录 2021年下半年试题一&#xff1a;第18章 项目风险管理&#xff0c;风险应对策略&#xff0c;风险的性质&#xff08;客观、偶然、相对、社会、不确定&#xff09;试题二&#xff1a;第9章 项目成本管理&#xff0c;执行绩效&#xff08;即CV和SV&#xff09;&#xff0…

HAL库版FreeRTOS(中)

目录 FreeRTOS 任务切换PendSV 异常PendSV 中断服务函数FreeRTOS 确定下一个要运行的任务函数vTaskSwitchContext()函数taskSELECT_HIGHEST_PRIORITY_TASK() PendSV 异常何时触发FreeRTOS 时间片调度实验功能设计软件设计下载验证 FreeRTOS 内核控制函数FreeRTOS 内核控制函数预…

蓝牙设备的名称与MAC地址及UUID

每个蓝牙设备都具有各自的地址和名称&#xff0c;他们之间通过唯一通过地址和名称进行数据交互。本文详细讲述了蓝牙设备的名称和地址的格式及作用。 名称 蓝牙设备具有各自的名称&#xff0c;通常为字母与数字的组合. MAC地址 与Ethernet相同&#xff0c;MAC地址为48bit的…

VTK安装路径检查

/usr/include/vtk-7.1——————VTK头文件

番剧更新表及番剧详情数据库

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 该项目立足于目前各大平台网站的番剧信息较为分散&#xff0c;用户需要辗转多个平台才能获取较为完整的番剧信息的背景下&#xff0c;实现了各大平台网站番剧信息的整合。将各大平台网站的番剧更新信息及番剧详情信息整合制表…

MATLAB 之 基本概述

文章目录 一、MATLAB 主要功能1. 数值计算功能2. 符号计算功能3. 绘图功能4. 程序设计语言功能5. 工具箱的扩展功能 二、MATLAB 操作界面1. 主窗口2. 命令行窗口3. 当前文件夹窗口4. 工作区窗口5. 搜索路径 三、MATLAB 基本操作1. 交互式命令操作1.1 命令行1.2 续行符1.3 命令行…

开关电源基础01:电源变换器基础(1)-关于缘起

说在开头 我相信各位胖友们通过对《阻容感基础》&#xff0c;《信号完整性基础》以及《半导体器件基础》艰苦卓绝地钻研&#xff0c;已为 “硬功夫” 这门绝世武功&#xff0c;打下了坚实的入门基础&#xff0c;入门之日简直就是指日可待&#xff08;我xxx&#xff0c;都半年了…

【数据结构】单链表详解

☃️个人主页&#xff1a;fighting小泽 &#x1f338;作者简介&#xff1a;目前正在学习C语言和数据结构 &#x1f33c;博客专栏&#xff1a;数据结构 &#x1f3f5;️欢迎关注&#xff1a;评论&#x1f44a;&#x1f3fb;点赞&#x1f44d;&#x1f3fb;留言&#x1f4aa;&…

阿里云服务器镜像系统怎么选择?超详细教程

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云百科来详细说下阿里云服务器操作系统有哪些&#xf…

Buf 教程 - 使用 Protobuf 生成 Golang 代码和 Typescript 类型定义

简介 Buf 是一款更高效、开发者友好的 Protobuf API 管理工具&#xff0c;不仅支持代码生成&#xff0c;还支持插件和 Protobuf 格式化。 我们可以使用 Buf 替代原本基于 Protoc 的代码生成流程&#xff0c;一方面可以统一管理团队 Protoc 插件的版本、代码生成配置&#xff…

测试之路,2023年软件测试市场领域有哪些变化?突破走得更远...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

Linux - 第12节 - 网络编程套接字

1.预备知识 1.1.理解源IP地址和目的IP地址 因特网上的每台计算机都有一个唯一的IP地址&#xff0c;如果一台主机上的数据要传输到另一台主机&#xff0c;那么对端主机的IP地址就应该作为该数据传输时的目的IP地址。但仅仅知道目的IP地址是不够的&#xff0c;当对端主机收到该数…

【Java校招面试】基础知识(七)——数据库

目录 前言一、数据库索引二、数据库锁三、数据库事务四、数据库连接池后记 前言 本篇主要介绍数据库的相关内容。 “基础知识”是本专栏的第一个部分&#xff0c;本篇博文是第六篇博文&#xff0c;如有需要&#xff0c;可&#xff1a; 点击这里&#xff0c;返回本专栏的索引文…

Sourcetree介绍及使用

Sourcetree是一个操作简单但功能强大的免费Git客户端管理工具&#xff0c;可应用在Windows和Mac平台。 Sourcetree的安装&#xff1a; 1.从Sourcetree | Free Git GUI for Mac and Windows 下载SourceTreeSetup-3.4.12.exe&#xff1b; 2.双击SourceTreeSetup-3.4.12.exe&#…

【C++】动态规划

参考博客&#xff1a;动态规划详解 1. 什么是动态规划 动态规划&#xff08;英语&#xff1a;Dynamic programming&#xff0c;简称 DP&#xff09;&#xff0c;是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的&#xff0c;通过把原问题分解为相对简单的子问…