la3_系统调用(上)

news2024/10/7 6:39:30

1. 实验内容

  1. 理解操作系统接口;
  2. 系统调用的实现:
  • 应用程序 调用库函数 (API)
  • API 将 系统调用号 放入 EAX 中, 然后通过中断调用 使系统进入内核态;
  • 内核中的中断处理函数 根据系统调用号, 调用对应的内核函数(系统调用);
  • 系统调用 完成相应功能, 将返回值存入 EAX, 返回到中断处理函数;
  • 中断处理函数 返回到 API 中;
  • API 将 EAX 返回给 应用程序。
  1. 添加两个系统调用函数

1.1 iam() 函数

Linux 0.11 上添加两个系统调用,并编写两个简单的应用程序测试它们

第一个系统调用是 iam(),其原型为:

int iam(const char * name);

该函数的作用:

将 字符串参数name 的内容, 从内存中的用户地址空间 拷贝到 内存中的内核地址空间保存起来,
要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL

kernal/who.c 中实现此系统调用。

errno
errno 是一种传统的错误代码返回机制。

当一个函数调用出错时,通常会返回 -1 给调用者。但 -1 只能说明出错,不能说明错是什么。为解决此问题,全局变量 errno 登场了。错误值被存放到 errno 中,于是调用者就可以通过判断 errno 来决定如何应对错误了。

各种系统对 errno 的值的含义都有标准定义。Linux 下用“man errno”可以看到这些定义。

1.2 whoami() 函数

第二个系统调用是 whoami(),其原型为:

int  whoami(char* name, unsigned int size);

作用: 将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中, 同时确保不会对name 越界访存, name 的大小由size 说明, 返回值是拷贝的字符数,
如果size 小于 需要的空间, 返回-1, 并且置 errno 为 EINVAL

同样在kernel/who.c 中执行;

1.3 测试程序

运行添加过新系统调用的 Linux 0.11,
在其环境下编写两个测试程序 iam.c 和 whoami.c。最终的运行结果是:

$ ./iam lizhijun

$ ./whoami

lizhijun

2. 系统调用的过程

在通常情况下,调用系统调用和调用一个普通的自定义函数在代码上并没有什么区别,

但调用后发生的事情有很大不同。

调用自定义函数是通过 call 指令直接跳转到该函数的地址,继续运行。

2.1 系统调用

而调用系统调用,是调用系统库中为该系统调用编写的一个接口函数,
叫 API(Application Programming Interface)。

API 并不能完成系统调用的真正功能,它要做的是去调用真正的系统调用,过程是:

  • 将系统调用的编号存入EAX 寄存器中;
  • 把函数参数存入其他 通用寄存器中;
  • 触发 0x80 号中断, (int 0x80);

linux-0.11 的 lib 目录下有一些已经实现的 API。

Linus 编写它们的原因是在内核加载完毕后,会切换到用户模式下,做一些初始化工作,然后启动 shell。

而用户模式下的很多工作需要依赖一些系统调用才能完成,因此在内核中实现了这些系统调用的 API。

后面的目录如果没有特殊说明,都是指在 ~/oslab/linux-0.11 中。
比如下面的 lib/close.c,是指 ~/oslab/linux-0.11/lib/close.c。

2.1 lib/close.c

我们不妨看看 lib/close.c,研究一下 close() 的 API:

#define __LIBRARY__
#include <unistd.h>

_syscall1(int, close, int,fd)

其中, _syscall1 是一个宏, 在 include/unistd.h 中定义

#define _syscall1(type, name, atype, a) \
type name(atype a)\
{\
long __res; \
__asm__ volatile ("int $0x80"\
	:  "=a" (__res) \
	: "0" (__NR_##name), "b"  ((long)(a))  ); \

if (__res >= 0) \
    return  (type)  __res; \
errno = -__res; \
return -1; \
}

_syscal1(int, close, int, fd) 进行宏展开, 可以得到:

  1. 它先将宏 __NR_close 存入 EAX,
  2. 将参数 fd 存入 EBX,
  3. 然后进行 0x80 中断调用。
  • 调用返回后,从 EAX 取出返回值,存入 __res,
  • 再通过对 __res 的判断决定传给 API 的调用者什么样的返回值。
int close(int fd)
{
	long __res;
	__asm__  volatile("int %0x80"
		: "=a" (__res)
		: "0" (__NR_close), "b" ((long) (fd))  );
	if (__res >= 0)
		return (int)  __res;
	errno   = -__res;
	return -1;
}

其中 __NR_close 就是系统调用的编号,在 include/unistd.h 中定义:

#define __NR_close    6
/*
所以添加系统调用时需要修改include/unistd.h文件,
使其包含__NR_whoami和__NR_iam。
*/
#define __NR_sgetmask	68
#define __NR_ssetmask	69
#define __NR_setreuid	70
#define __NR_setregid	71
#define __NR_iam 72
#define __NR_whoami 73

在 0.11 环境下编译 C 程序,包含的头文件都在 /usr/include 目录下
注意, 这里是 启动bochs 硬件模拟器后, 查看的;

该目录下的 unistd.h 是标准头文件(它和 0.11 源码树中的 unistd.h 并不是同一个文件,虽然内容可能相同),没有 __NR_whoami 和 __NR_iam 两个宏,需要手工加上它们,也可以直接从修改过的 0.11 源码树中拷贝新的 unistd.h 过来。

/*
而在应用程序中,要有:
*/

/* 有它,_syscall1 等才有效。详见unistd.h */
#define __LIBRARY__

/* 有它,编译器才能获知自定义的系统调用的编号 */
#include "unistd.h"

/* iam()在用户空间的接口函数 */
_syscall1(int, iam, const char*, name);

/* whoami()在用户空间的接口函数 */
_syscall2(int, whoami,char*,name,unsigned int,size);

2.2 宏汇编的嵌入

在这里插入图片描述


3. 进入内核函数

3.1 int 0x80 的处理过程

int 0x80 触发后,接下来就是内核的中断处理了。先了解一下 0.11 处理 0x80 号中断的过程。

在内核初始化时,主函数 在 init/main.c 中调用了 sched_init() 初始化函数;

(Linux 实验环境下是 main(),Windows 下因编译器兼容性问题被换名为 start())

void main(void)
{
//    ……
    time_init();
    sched_init();
    buffer_init(buffer_memory_end);
//    ……
}

sched_init()kernel/sched.c 中定义为:

void sched_init(void)
{
//    ……
    set_system_gate(0x80,&system_call);
}

set_system_gate 是个宏,在 include/asm/system.h 中定义为:

#define set_system_gate(n,addr) \
    _set_gate(&idt[n],15,3,addr)

_set_gate 的定义是:

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
    "movw %0,%%dx\n\t" \
    "movl %%eax,%1\n\t" \
    "movl %%edx,%2" \
    : \
    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
    "o" (*((char *) (gate_addr))), \
    "o" (*(4+(char *) (gate_addr))), \
    "d" ((char *) (addr)),"a" (0x00080000))

3.2 system_call 函数

然看起来挺麻烦,但实际上很简单,就是填写 IDT(中断描述符表),将 system_call 函数地址写到 0x80 对应的中断描述符中,也就是在中断 0x80 发生后,自动调用函数 system_call。具体细节请参考《注释》的第 4 章。

接下来看 system_call。该函数纯汇编打造,定义在 kernel/system_call.s 中:


!……
! # 这是系统调用总数。如果增删了系统调用,必须做相应修改
nr_system_calls = 72
!……

.globl system_call
.align 2
system_call:

! # 检查系统调用编号是否在合法范围内
    cmpl \$nr_system_calls-1,%eax
    ja bad_sys_call
    push %ds
    push %es
    push %fs
    pushl %edx
    pushl %ecx

! # push %ebx,%ecx,%edx,是传递给系统调用的参数
    pushl %ebx

! # 让ds, es指向GDT,内核地址空间
    movl $0x10,%edx
    mov %dx,%ds
    mov %dx,%es
    movl $0x17,%edx
! # 让fs指向LDT,用户地址空间
    mov %dx,%fs
    call sys_call_table(,%eax,4)
    pushl %eax
    movl current,%eax
    cmpl $0,state(%eax)
    jne reschedule
    cmpl $0,counter(%eax)
    je reschedule

system_call 用 .globl 修饰为其他函数可见。

Windows 实验环境下会看到它有一个下划线前缀,这是不同版本编译器的特质决定的,没有实质区别。

call sys_call_table(,%eax,4) 之前是一些压栈保护,修改段选择子为内核段,
call sys_call_table(,%eax,4) 之后是看看是否需要重新调度,这些都与本实验没有直接关系,

此处只关心 call sys_call_table(,%eax,4) 这一句。

根据汇编寻址方法它实际上是:
call sys_call_table + 4 * %eax,其中 eax 中放的是系统调用号,即 __NR_xxxxxx。

显然,sys_call_table 一定是一个函数指针数组的起始地址,它定义在 include/linux/sys.h 中:

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,…

增加实验要求的系统调用,需要在这个函数表中增加两个函数引用 ——sys_iam 和 sys_whoami。当然该函数在 sys_call_table 数组中的位置必须和 __NR_xxxxxx 的值对应上。

同时还要仿照此文件中前面各个系统调用的写法,加上:

extern int sys_whoami();
extern int sys_iam();

不然,编译会出错的。

3.3 实现函数

添加系统调用的最后一步,是在内核中实现函数 sys_iam() 和 sys_whoami()。

每个系统调用都有一个 sys_xxxxxx() 与之对应,它们都是我们学习和模仿的好对象。

比如在 fs/open.c 中的 sys_close(int fd):

int sys_close(unsigned int fd)
{
//    ……
    return (0);
}

它没有什么特别的,都是实实在在地做 close() 该做的事情。

所以只要自己创建一个文件:kernel/who.c,然后实现两个函数就万事大吉了。

如果完全没有实现的思路,不必担心,
本实验的 “在用户态和核心态之间传递数据” 还会有提示。

3.4 修改 Makefile

要想让我们添加的 kernel/who.c 可以和其它 Linux 代码编译链接到一起,必须要修改 Makefile 文件。

Makefile 里记录的是所有源程序文件的编译、链接规则,《注释》3.6 节有简略介绍。我们之所以简单地运行 make 就可以编译整个代码树,是因为 make 完全按照 Makefile 里的指示工作。

Makefile 在代码树中有很多,分别负责不同模块的编译工作。我们要修改的是 kernel/Makefile

需要修改两处。

第一处:

OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o

修改为,添加了 who.o

OBJS  = sched.o system_call.o traps.o asm.o fork.o \
        panic.o printk.o vsprintf.o sys.o exit.o \
        signal.o mktime.o who.o

第二处:

### Dependencies:
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h

添加了 who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h

### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h

Makefile 修改后,和往常一样 make all 就能自动把 who.c 加入到内核中了。

如果编译时提示 who.c 有错误,就说明修改生效了。所以,有意或无意地制造一两个错误也不完全是坏事,至少能证明 Makefile 是对的。

4. 调试内核

用 printk() 调试内核:

oslab 实验环境提供了基于 C 语言和汇编语言的两种调试手段。除此之外,适当地向屏幕输出一些程序运行状态的信息,也是一种很高效、便捷的调试方法,有时甚至是唯一的方法,被称为“printf 法”。

要知道到,printf() 是一个只能在用户模式下执行的函数,而系统调用是在内核模式中运行,所以 printf() 不可用,要用 printk()。

printk() 和 printf() 的接口和功能基本相同,只是代码上有一点点不同。printk() 需要特别处理一下 fs 寄存器,它是专用于用户模式的段寄存器。

看一看 printk 的代码(在 kernel/printk.c 中)就知道了:

int printk(const char *fmt, ...)
{
//    ……
    __asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
            "pushl $buf\n\t"
            "pushl $0\n\t"
            "call tty_write\n\t"
            "addl $8,%%esp\n\t"
            "popl %0\n\t"
            "pop %%fs"
            ::"r" (i):"ax","cx","dx");
//    ……
}

显然,printk() 首先 push %fs 保存这个指向用户段的寄存器,在最后 pop %fs 将其恢复,printk() 的核心仍然是调用 tty_write()。查看 printf() 可以看到,它最终也要落实到这个函数上。

5. 编写测试程序

激动地运行一下由你亲手修改过的 “Linux 0.11 pro++”!然后编写一个简单的应用程序进行测试。

比如在 sys_iam() 中向终端 printk() 一些信息,让应用程序调用 iam(),从结果可以看出系统调用是否被真的调用到了。

可以直接在 Linux 0.11 环境下用 vi 编写(别忘了经常执行“sync”以确保内存缓冲区的数据写入磁盘),也可以在 Ubuntu 或 Windows 下编完后再传到 Linux 0.11 下。无论如何,最终都必须在 Linux 0.11 下编译。编译命令是:

$ gcc -o iam iam.c -Wall

gcc 的 “-Wall” 参数是给出所有的编译警告信息,“-o” 参数指定生成的执行文件名是 iam,用下面命令运行它:


$ ./iam

如果如愿输出了你的信息,就说明你添加的系统调用生效了。否则,就还要继续调试,祝你好运!

6. 用户态与 核心态 之间 传递数据

针参数传递的是应用程序所在地址空间的逻辑地址,在内核中如果直接访问这个地址,访问到的是内核空间中的数据,不会是用户空间的。所以这里还需要一点儿特殊工作,才能在内核中从用户空间得到数据。

要实现的两个系统调用参数中都有字符串指针,非常像 open(char *filename, ……),所以我们看一下 open() 系统调用是如何处理的。

int open(const char * filename, int flag, ...)
{
//    ……
    __asm__("int $0x80"
            :"=a" (res)
            :"0" (__NR_open),"b" (filename),"c" (flag),
            "d" (va_arg(arg,int)));
//    ……
}

可以看出,系统调用是用 eax、ebx、ecx、edx 寄存器来传递参数的。

其中 eax 传递了系统调用号,而 ebx、ecx、edx 是用来传递函数的参数的
ebx 对应第一个参数,ecx 对应第二个参数,依此类推。
如 open 所传递的文件名指针是由 ebx 传递的,也即进入内核后,通过 ebx 取出文件名字符串。open 的 ebx 指向的数据在用户空间,而当前执行的是内核空间的代码,如何在用户态和核心态之间传递数据?

接下来我们继续看看 open 的处理:

system_call: //所有的系统调用都从system_call开始
!    ……
    pushl %edx
    pushl %ecx
    pushl %ebx                # push %ebx,%ecx,%edx,这是传递给系统调用的参数
    movl $0x10,%edx            # 让ds,es指向GDT,指向核心地址空间
    mov %dx,%ds
    mov %dx,%es
    movl $0x17,%edx            # 让fs指向的是LDT,指向用户地址空间
    mov %dx,%fs
    call sys_call_table(,%eax,4)    # 即call sys_open

由上面的代码可以看出,获取用户地址空间(用户数据段)中的数据依靠的就是段寄存器 fs,下面该转到 sys_open 执行了,在 fs/open.c 文件中:

int sys_open(const char * filename,int flag,int mode)  //filename这些参数从哪里来?
/*是否记得上面的pushl %edx,    pushl %ecx,    pushl %ebx?
  实际上一个C语言函数调用另一个C语言函数时,编译时就是将要
  传递的参数压入栈中(第一个参数最后压,…),然后call …,
  所以汇编程序调用C函数时,需要自己编写这些参数压栈的代码…*/
{
    ……
    if ((i=open_namei(filename,flag,mode,&inode))<0) {
        ……
    }
    ……
}

它将参数传给了 open_namei()。

再沿着 open_namei() 继续查找,文件名先后又被传给dir_namei()、get_dir()。

6.1 get_dir()

在 get_dir() 中可以看到:

static struct m_inode * get_dir(const char * pathname)
{
    ……
    if ((c=get_fs_byte(pathname))=='/') {
        ……
    }
    ……
}

处理方法就很显然了:用 get_fs_byte() 获得一个字节的用户空间中的数据。

所以,在实现 iam() 时,调用 get_fs_byte() 即可。

但如何实现 whoami() 呢?即如何实现从核心态拷贝数据到用心态内存空间中呢?

猜一猜,是否有 put_fs_byte()?有!看一看 include/asm/segment.h :

extern inline unsigned char get_fs_byte(const char * addr)
{
    unsigned register char _v;
    __asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));
    return _v;
}
extern inline void put_fs_byte(char val,char *addr)
{
    __asm__ ("movb %0,%%fs:%1"::"r" (val),"m" (*addr));
}

他俩以及所有 put_fs_xxx() 和 get_fs_xxx() 都是用户空间和内核空间之间的桥梁,在后面的实验中还要经常用到。

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

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

相关文章

通过postgres_fdw实现跨库访问

瀚高数据库 目录 文档用途 详细信息 介绍Postgresql跨库访问中postgres_fdw的使用方法 详细信息 PostgreSQL 外部数据包装器&#xff0c;即 PostgreSQL Foreign Data Wrappers&#xff0c;是现实数据库使用场景中一个非常实用的功能&#xff0c;PostgreSQL 的 FDW 类似于 Ora…

2022年12月编程语言排行榜,数据来了!

2022年迎来了最后一个月&#xff0c;我们可以看到&#xff0c;在这一年中编程语言起起伏伏&#xff0c;有的语言始终炙手可热&#xff0c;而有的语言却逐渐“没落”… 日前&#xff0c;全球知名TIOBE编程语言社区发布了12月编程语言排行榜&#xff0c;有哪些新变化&#xff1f…

木聚糖-聚乙二醇-透明质酸,Hyaluronicacid-PEG-Xylan,透明质酸-PEG-木聚糖

木聚糖-聚乙二醇-透明质酸,Hyaluronicacid-PEG-Xylan,透明质酸-PEG-木聚糖 中文名称&#xff1a;木聚糖-透明质酸 英文名称&#xff1a;Xylan-Hyaluronicacid 别称&#xff1a;透明质酸修饰木聚糖&#xff0c;HA-木聚糖 存储条件&#xff1a;-20C&#xff0c;避光&#xff…

农产品商城毕业设计,农产品销售系统毕业设计,农产品电商毕业设计论文方案需求分析作品参考

项目背景和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于web网页的多用户商城系统&#xff0c;整个网站项目使用了B/S架构&#xff0c;基于python的Django框架下开发&#xff1b;用户通过登录网站&#xff0c;查询商品&#xff0c;购买商品&#xff0c;下单&am…

奋勇拼搏绿茵场,永不言败足球魂——2022卡塔尔世界杯纪念

“我从来都不惧怕压力,老实说,我享受这种压力。”——C罗 第一部分&#xff1a;&#x1f1f6;&#x1f1e6;卡塔尔世界杯 2022年卡塔尔世界杯&#xff08;英语&#xff1a;FIFA World Cup Qatar 2022&#xff09;是第二十二届世界杯足球赛&#xff0c;是历史上首次在卡塔尔和中…

Apple官方优化Stable Diffusion绘画教程

Apple官方优化Stable Diffusion绘画教程 苹果为M1芯片优化Stable Diffusion模型&#xff0c;其中Mac Studio (M1 Ultra, 64-core GPU)生成512*512的图像时间为9秒。想要1秒出图&#xff0c;可以在线体验3090显卡AI绘画。 AI绘图在线体验 二次元绘图 在线体验地址:Stable Di…

AI模型神预测谁是卡塔尔世界杯冠军

推荐教程&#xff1a;AI模型神预测谁是冠军 2022年卡塔尔世界杯 猜猜他们是谁&#xff1f; 谁是最后的冠军&#xff1f; 2022年FIFA世界杯已经拉开帷幕&#xff0c;全世界的球迷都热切地想要知道&#xff1a;谁将获得那梦寐以求的 大力神杯&#xff1f; 2018年俄罗斯世界杯 方…

1,2-二苯基-1,2-二(4-羧基苯)乙烯 ;CAS: 1609575-40-7

英文名称&#xff1a; 4,4-(1,2-Diphenylethene-1,2-diyl)dibenzoic acid 中文名称&#xff1a; 1,2-二苯基-1,2-二(4-羧基苯)乙烯 MF&#xff1a; C28H20O4 MW&#xff1a; 420.46 CAS&#xff1a; 1609575-40-7 AIE聚集诱导发光材料的特点&#xff1a; 1.在固态下有强…

学编程:Python入门考级必备[11]

目录 1.查找字符串 2.字符串的格式化 3.字符串的转义字符 \ \" 4. 修改字符串 5.字符串连接与分割 附件代码&#xff1a; 炼 知识模块(11) 名符其实--字符串 1.查找字符串 # 1.1用 in 函数 a aa in acacacacaabaac print(a) # 1.2 用index 找不到就报错 b h…

ArcGIS_地质多样性评价方法

详细内容请自行查看参考文献 [1] Forte J P , Brilha J , Pereira D I , et al. Kernel Density Applied to the Quantitative Assessment of Geodiversity[J]. Geoheritage, 2018, 10:205-217. https://doi.org/10.1007/s12371-018-0282-3 本文只介绍如何在ArcGIS中实现该…

PostGIS数据测试-一百万点要素

PostGIS数据测试-一百万点要素 小小测试一下&#xff0c;看看单表百万数据的情况 服务器配置 系统版本&#xff1a;Centos7.9.2009CPU&#xff1a;两颗Intel Xeon Gold 6226R CPU 2.90GHz处理器&#xff0c;共32核心内存&#xff1a;DDR4 256G硬盘&#xff1a;Raid5 共24T数…

树选择排序(Tree Selection Sorting)介绍

简介 或许你有一个疑问&#xff1a;为什么堆排序使用二叉树&#xff0c;但是叫堆排序&#xff0c;而不是树排序&#xff1f; 因为堆排序的前身正是叫做树选择排序&#xff08;Tree Selection Sorting&#xff09;&#xff0c;使用树结构&#xff0c;但是要稍微简单一些。 高德…

CNN卷积参数量计算

参考&#xff1a;轻量级网络-Mobilenet系列(v1,v2,v3) - 知乎 盘点下每种类型的层计算可学习参数的数量 Input layer&#xff1a;输入层所做的只是读取输入图像&#xff0c;因此这里没有可以学习的参数。 Convolutional layers&#xff1a;一个卷积层&#xff0c;其输入为l个特…

cassandra安装及配置

Cassandra介绍 Cassandra是一个开源的、分布式、无中心节点、弹性可扩展、高可用、容错、一致性协调、面向列的NoSQL数据库。 Cassandra的主要组成部分主要有: • 节点(Node):Cassandra节点是存储数据的地方。 • 数据中心(Data center):数据中心是相关节点的集合。 • 集群…

Vue3 企业级优雅实战 - 组件库框架 - 8 搭建组件库 cli

前面的文章分享了组件库的开发、example、组件库文档&#xff0c;本文分享组件库 cli 开发。 1 为什么要开发组件库 cli 回顾一个新组件的完整开发步骤&#xff1a; 1 在 packages 目录下创建组件目录 xxx&#xff1a; 1.1 使用 pnpm 初始化 package.json&#xff0c;修改 n…

【正点原子FPGA连载】第二十八章 以太网ARP测试实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十八章 以太…

(十七) 共享模型之工具【JUC】【读写锁】

一、ReentrantReadWriteLock&#xff08;P247&#xff09; 当读操作远远高于写操作时&#xff0c;这时候使用 【读写锁】让 【读-读】可以并发&#xff0c;提高性能。 类似于数据库中的 select ... from ... lock in share mode 提供一个 数据容器类内部分别使用读锁保护数据的…

【论文阅读 CIKM‘2021】Learning Multiple Intent Representations for Search Queries

文章目录Original PaperMotivationMethodTask Description and Problem FormulationNMIR Framework: A High-Level OverviewModel Implementation and TrainingDataOriginal Paper Learning Multiple Intent Representations for Search Queries More related papers can be …

基于Electron的桌面端应用开发和实践

引言 如果开发跨桌面端的应用开发的话&#xff0c;我相信&#xff0c;electron目前绝对是不可避免的技术方案。web应用大家都知道&#xff0c;通过浏览器访问的应用就是web应用&#xff0c;那什么是桌面端&#xff1f;桌面端有两个重要特点&#xff1a; 具备独立运行于操作系…

学习压力容器中卡箍快开结构的强度计算

导读:压力容器的设计一定要考虑安全性、经济性、环保及健康问题。首先安全是核心问题&#xff0c;在保证安全的前提下尽可能的再做到经济合理。 本文从强度计算软件SW6-2011 V3.1补丁二&#xff08;单机版&#xff09;和&#xff08;网络版&#xff09;所解决的问题&#xff0…