MIT6.s081 2021 Lab System calls

xv6系统调用实现

不同于 Lab1 利用已实现的系统调用来实现一些用户态下的命令行程序,本 Lab 是要在内核层面实现一些系统调用。这其中难免涉及到一些对内核数据结构的操作,以及处理器体系结构(本系列 Lab 基于 RISCV)相关的内容,那么首先有必要梳理一下 xv6 下系统调用的实现过程。

xv6 系统调用的实现:

  1. trace 系统调用为例,用户通过调用 user/user.h 中的函数 trace 进行系统调用。
  2. 通过调用 Perl 脚本 user/usys.pl 生成的一系列汇编代码,该汇编代码的作用是设置寄存器的内容并实现用户态到内核态的切换,内核后续针对寄存器中的内容执行相应的系统调用操作。以下是对 user/usys.pl 代码的逐行解析:
  1. #!/usr/bin/perl -w:这是一个Perl脚本的“shebang”行,指定使用/usr/bin/perl解释器执行此脚本,并开启警告(-w)选项。

  2. print "# generated by usys.pl - do not edit\n";:打印注释说明此文件是由usys.pl脚本自动生成的,不应手动编辑。

  3. print "#include \"kernel/syscall.h\"\n";:输出一条预处理器指令,包含一个名为syscall.h的头文件,该文件可能包含了系统调用相关的常量和宏定义。

  4. sub entry {...}:定义了一个名为entry的子程序(函数),它接受一个参数(系统调用名称)。

  5. my $name = shift;:在entry函数内部,使用shift函数获取传入的第一个参数(系统调用名称),并将其存储在变量$name中。

  6. 接下来的几行print语句构造了每个系统调用存根的汇编代码:

    • .global $name:声明一个全局标签(函数名),使得链接器能够找到它。
    • ${name}:\n:定义了一个标签,对应于系统调用函数的开始。
    • li a7, SYS_${name}\n:装载(load immediate)指令,将系统调用号(通过宏SYS_${name}得到)放入寄存器a7中。在RISC-V架构中,a7寄存器通常用于存放系统调用号。
    • ecall:执行系统调用指令,这会触发处理器进入内核模式并执行相应的内核服务。
    • ret:返回指令,从系统调用中返回到用户程序。
  7. 最后,脚本通过多次调用entry函数(传入不同的系统调用名称,如fork, exit, wait等),为每一个列出的系统调用生成对应的汇编代码存根。

  1. 内核在执行系统调用时,只是调用 kernel/syscall.c 中的 syscall 函数,该函数读取寄存器 a7 的值,将其作为系统调用号,执行实际的系统调用函数(如sys_trace),并将函数返回值放入寄存器 a0 中,调用结束。

System call tracing

思路

理解了上述的系统调用过程,就可以开始着手完成系统调用的添加了。

由题干可知,用户态系统调用函数 trace 的参数为一个整型 mask,该 mask 用来表示哪些系统调用需要被追踪,如果 mask 的第 i 位为 1,则系统调用号 i 对应的系统调用将被追踪。

首先,在 user/user.huser/usys.plkernel/syscall.h 中添加 trace 的声明。

接下来,在 kernel/sysproc.c 中实现系统调用函数 sys_trace,该函数获取用户态传递的 trace 函数的参数 mask,并存入当前进程的 PCB(进程控制块,xv6 中为 kernel/proc.h 中的 struct proc 结构体)中。获取参数的操作,可以查看如下 xv6 文档的描述,并参考 kernel/sysproc.c 中其它系统调用函数的实现。由于参数类型为整型且数量只有一个(存放在 a0 寄存器中),因此调用 argint(0, &(myproc()->mask))。另外需要注意的是,struct proc 的初始定义中并没有 mask 段的内容,需要自行添加。

Because user code calls system call wrapper functions, the arguments are initially where the RISC-V C calling convention places them: in registers. The kernel trap code saves user registers to the current process’s trap frame, where kernel code can find them. The kernel functions argint, argaddr, and argfd retrieve the n ’th system call argument from the trap frame as an integer, pointer, or a file descriptor. They all call argraw to retrieve the appropriate saved user register (kernel/syscall.c:35).

然后,修改 kernel/proc.cfork 函数的定义,为 mask 字段添加拷贝操作,将父进程的 mask 字段传递给子进程,以此实现对子进程的追踪。

np->mask = p->mask;

最后,修改 kernel/syscall.c 中的 syscall 函数,判断当前的系统调用号是否位于被追踪的范围内,如果是,则按照要求格式将要追踪的信息打印出来:其中进程号为 myproc()->pid;函数调用名可手动创建一个系统调用名称表,通过将系统调用号作为下标来获取;函数返回值位于寄存器 a0 中,可通过 myproc()->trapframe->a0 来获取。

问题

最后再记录一下本 Lab 遇到的一些问题:

make失败

请添加图片描述

被这个错误困扰了挺久,甚至还为此使用 git reset 回退了版本,最后发现是在 $U/_trace\ 的末尾多了一个空格。。。

系统调用名称表添加出错

请添加图片描述

原因是我将系统调用名称表添加在了 kernel/syscall.h 中,但该头文件后续是会被 user/usys.pl 用于生成汇编的,因此不能包含 C 语言语句,最后是选择直接添加在了 kernel/syscall.c 中。

代码

由于本 Lab 主要是在原先的内核代码上进行修改,涉及的文件较多,因此代码部分以 git diff 的形式展现。

diff --git a/Makefile b/Makefile
index c926b7e..6647da5 100644
--- a/Makefile
+++ b/Makefile
@@ -193,6 +193,7 @@ UPROGS=\$U/_grind\$U/_wc\$U/_zombie\
+	$U/_trace\diff --git a/kernel/proc.c b/kernel/proc.c
index 22e7ce4..f4bd5c2 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -314,6 +314,9 @@ fork(void)acquire(&np->lock);np->state = RUNNABLE;release(&np->lock);
+  
+  // here
+  np->mask = p->mask;return pid;}
diff --git a/kernel/proc.h b/kernel/proc.h
index f6ca8b7..e83d456 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -105,4 +105,7 @@ struct proc {struct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)
+  
+  // here
+  int mask;					   // Mask of trace};
diff --git a/kernel/syscall.c b/kernel/syscall.c
index c1b3670..b5b8291 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -104,6 +104,7 @@ extern uint64 sys_unlink(void);extern uint64 sys_wait(void);extern uint64 sys_write(void);extern uint64 sys_uptime(void);
+extern uint64 sys_trace(void);static uint64 (*syscalls[])(void) = {[SYS_fork]    sys_fork,
@@ -127,6 +128,16 @@ static uint64 (*syscalls[])(void) = {[SYS_link]    sys_link,[SYS_mkdir]   sys_mkdir,[SYS_close]   sys_close,
+[SYS_trace]   sys_trace,
+};
+
+// here
+static char *syscall_names[] = {	
+	"dummy",  "fork",  "exit",  "wait",  "pipe",
+	"read",   "kill",  "exec",  "fstat", "chdir",
+	"dup",    "getpid","sbrk",  "sleep", "uptime",
+	"open",   "write", "mknod", "unlink", "link",
+	"mkdir",  "close", "trace",};void
@@ -138,6 +149,11 @@ syscall(void)num = p->trapframe->a7;if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {p->trapframe->a0 = syscalls[num]();
+	
+	// here
+	if (p->mask & (1 << num)) {  // if mask contains current syscall num
+		printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
+	}} else {printf("%d %s: unknown sys call %d\n",p->pid, p->name, num);
diff --git a/kernel/syscall.h b/kernel/syscall.h
index bc5f356..756d191 100644
--- a/kernel/syscall.h
+++ b/kernel/syscall.h
@@ -20,3 +20,4 @@#define SYS_link   19#define SYS_mkdir  20#define SYS_close  21
+#define SYS_trace  22 // here
\ No newline at end of file
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index e8bcda9..3ff51d9 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -95,3 +95,11 @@ sys_uptime(void)release(&tickslock);return xticks;}
+
+// here
+uint64 sys_trace(void) {
+	if (argint(0, &(myproc()->mask)) < 0) {
+		return -1;
+	}
+	return 0;
+}
\ No newline at end of file
diff --git a/user/user.h b/user/user.h
index b71ecda..16107d6 100644
--- a/user/user.h
+++ b/user/user.h
@@ -23,6 +23,7 @@ int getpid(void);char* sbrk(int);int sleep(int);int uptime(void);
+int trace(int); // here// ulib.cint stat(const char*, struct stat*);
diff --git a/user/usys.pl b/user/usys.pl
index 01e426e..76c64ec 100755
--- a/user/usys.pl
+++ b/user/usys.pl
@@ -36,3 +36,4 @@ entry("getpid");entry("sbrk");entry("sleep");entry("uptime");
+entry("trace"); # here
\ No newline at end of file

Sysinfo

思路

在完整添加了一个新的系统调用,熟悉了整体流程之后,本题相对就比较轻松了。声明添加的操作就跳过不谈了,这里主要关注 sys_sysinfo 的实现:即获取 freememnproc 的信息并将其填充到参数 sysinfo 指针对应的地址处。

这个大的目标可以拆分为 3 个小目标:

  1. 如何获取 freemem 的信息?
  2. 如何获取 nproc 的信息?
  3. 如何将数据填充入指定的地址中(用户空间)?

获取 freemem 的信息

仔细阅读 kernel/kalloc.c 的代码,可以发现一些关键信息:

  • struct run:用来内存分配单元的数据结构,本身的地址即为所指向的内存空间的起始地址,包含一个 next 指针,用于实现链表。
  • kmem.freelist:空闲链表,存储着一系列指向空闲空间的指针。
  • PGSIZE:内存分配页的大小,即每个 struct run * 所指向的内存空间的大小。

了解了上述信息后,计算空闲空间的大小就很简单了,只需要计算空闲链表的长度 n,空闲内存的空间大小即为 n * PGSIZE

uint64 freemem_bytes(void) {uint64 bytes = 0;struct run *r;for (r = kmem.freelist; r; r = r->next) {bytes += PGSIZE;}return bytes;
}

获取 nproc 的信息

与上面一样,阅读 kernel/proc.c 的代码,可知:

  • struct proc proc[NPROC] :进程数组,存储着所有进程的 struct proc.
  • UNUSEDstruct procenum procstate 的类型之一,代表本 struct proc 未被使用。

那么要得到当前系统中进程的数量,只需要遍历整个 proc,计算未处于 UNUSED 状态的进程数量即可。

uint64 proc_num(void) {struct proc *p;uint64 num = 0;for(p = proc; p < &proc[NPROC]; ++p) {num += (p->state != UNUSED);}return num;
}

将数据填充入指定的地址中

得到 freememnproc 之后,就需要将数据写入 sysinfo 的参数 struct sysinfo * 指向的内存区域,获取参数的方法和 tracing 类似,不过由于参数是指针类型,因此采用 argaddr。最后,仿照 kernel/file.c 中的操作,使用 copyout 将内核区域的数据写入用户空间中。

uint64 sys_sysinfo(void) {struct proc *p = myproc();struct sysinfo info;uint64 addr;info.freemem = freemem_bytes();info.nproc = proc_num();// get argument addrif (argaddr(0, &addr) < 0) {return -1;}// copy data of info to addrif (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) {return -1;}return 0;
}

代码

diff --git a/Makefile b/Makefile
index 6647da5..cfb5119 100644
--- a/Makefile
+++ b/Makefile
@@ -194,6 +194,7 @@ UPROGS=\$U/_wc\$U/_zombie\$U/_trace\
+	$U/_sysinfotest\diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..b2dbb8d 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -63,6 +63,7 @@ void            ramdiskrw(struct buf*);void*           kalloc(void);void            kfree(void *);void            kinit(void);
+uint64          freemem_bytes(void); // here// log.cvoid            initlog(int, struct superblock*);
@@ -104,6 +105,7 @@ void            yield(void);int             either_copyout(int user_dst, uint64 dst, void *src, uint64 len);int             either_copyin(void *dst, int user_src, uint64 src, uint64 len);void            procdump(void);
+uint64          proc_num(void); // here// swtch.Svoid            swtch(struct context*, struct context*);
diff --git a/kernel/kalloc.c b/kernel/kalloc.c
index fa6a0ac..686d84e 100644
--- a/kernel/kalloc.c
+++ b/kernel/kalloc.c
@@ -80,3 +80,15 @@ kalloc(void)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r;}
+
+// here
+uint64 freemem_bytes(void) {
+	uint64 bytes = 0;
+	struct run *r;
+	
+	for (r = kmem.freelist; r; r = r->next) {
+		bytes += PGSIZE;
+	}
+	
+	return bytes;
+}
\ No newline at end of file
diff --git a/kernel/proc.c b/kernel/proc.c
index f4bd5c2..ed6eec4 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c
@@ -657,3 +657,15 @@ procdump(void)printf("\n");}}
+
+// here
+uint64 proc_num(void) {
+	struct proc *p;
+	uint64 num = 0;
+  
+	for(p = proc; p < &proc[NPROC]; ++p) {
+		num += (p->state != UNUSED);
+	}
+	
+	return num;
+}
\ No newline at end of file
diff --git a/kernel/syscall.c b/kernel/syscall.c
index b5b8291..6fed4f2 100644
--- a/kernel/syscall.c
+++ b/kernel/syscall.c
@@ -105,6 +105,7 @@ extern uint64 sys_wait(void);extern uint64 sys_write(void);extern uint64 sys_uptime(void);extern uint64 sys_trace(void);
+extern uint64 sys_sysinfo(void);static uint64 (*syscalls[])(void) = {[SYS_fork]    sys_fork,
@@ -129,6 +130,7 @@ static uint64 (*syscalls[])(void) = {[SYS_mkdir]   sys_mkdir,[SYS_close]   sys_close,[SYS_trace]   sys_trace,
+[SYS_sysinfo] sys_sysinfo,};// here
@@ -137,7 +139,7 @@ static char *syscall_names[] = {"read",   "kill",  "exec",  "fstat", "chdir","dup",    "getpid","sbrk",  "sleep", "uptime","open",   "write", "mknod", "unlink", "link",
-	"mkdir",  "close", "trace",
+	"mkdir",  "close", "trace", "sysinfo",};void
diff --git a/kernel/syscall.h b/kernel/syscall.h
index 756d191..7954d98 100644
--- a/kernel/syscall.h
+++ b/kernel/syscall.h
@@ -20,4 +20,5 @@#define SYS_link   19#define SYS_mkdir  20#define SYS_close  21
-#define SYS_trace  22 // here
\ No newline at end of file
+#define SYS_trace  22 // here
+#define SYS_sysinfo 23
\ No newline at end of file
diff --git a/kernel/sysproc.c b/kernel/sysproc.c
index 3ff51d9..644638f 100644
--- a/kernel/sysproc.c
+++ b/kernel/sysproc.c
@@ -6,6 +6,7 @@#include "memlayout.h"#include "spinlock.h"#include "proc.h"
+#include "sysinfo.h"uint64sys_exit(void)
@@ -101,5 +102,26 @@ uint64 sys_trace(void) {if (argint(0, &(myproc()->mask)) < 0) {return -1;}
+	return 0;
+}
+
+uint64 sys_sysinfo(void) {
+	struct proc *p = myproc();
+	struct sysinfo info;
+	uint64 addr;
+	
+	info.freemem = freemem_bytes();
+	info.nproc = proc_num();
+	
+	// get argument addr
+	if (argaddr(0, &addr) < 0) {
+		return -1;
+	}
+	
+	// copy data of info to addr
+	if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) {
+		return -1;
+	}
+	return 0;}
\ No newline at end of file
diff --git a/user/user.h b/user/user.h
index 16107d6..37d15a5 100644
--- a/user/user.h
+++ b/user/user.h
@@ -1,5 +1,6 @@struct stat;struct rtcdate;
+struct sysinfo; // here// system callsint fork(void);
@@ -24,6 +25,7 @@ char* sbrk(int);int sleep(int);int uptime(void);int trace(int); // here
+int sysinfo(struct sysinfo *);// ulib.cint stat(const char*, struct stat*);
diff --git a/user/usys.pl b/user/usys.pl
index 76c64ec..fde7c87 100755
--- a/user/usys.pl
+++ b/user/usys.pl
@@ -36,4 +36,5 @@ entry("getpid");entry("sbrk");entry("sleep");entry("uptime");
-entry("trace"); # here
\ No newline at end of file
+entry("trace"); # here
+entry("sysinfo")
\ No newline at end of file

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

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

相关文章

什么是慢查询——Java全栈知识(26)

1、什么是慢查询 慢查询&#xff1a;也就是接口压测响应时间过长&#xff0c;页面加载时间过长的查询 原因可能如下&#xff1a; 1、聚合查询 2、多表查询 3、单表数据量过大 4、深度分页查询&#xff08;limit&#xff09; 如何定位慢查询&#xff1f; 1、Skywalking 我们…

IND83081芯片介绍(一)

一、芯片介绍 IND83081是indiemicro推出的一款高性能的汽车矩阵LED照明控制器&#xff0c;集成了四个子模块&#xff0c;每个子模块包含三个串联的MOSFET开关&#xff0c;每个开关均可通过12位PWM内部信号控制&#xff0c;可配置的上升和下降速率及相位移以实现精确控制&#x…

JOSEF约瑟 JOXL-J拉绳开关 整定范围宽

用途 双向拉绳开关的壳体采用金属材料铸造&#xff0c;具有足够的机械强度,抵抗并下工作时脱落的岩石&#xff0c;爆块等物体的撞击不被破坏&#xff0c;当胶带输送机发生紧急事故时&#xff0c;启动拉绳开关,可立即停机报警&#xff0c;防止事故的扩大,保证工作现场的人身安全…

java 操作 milvus 2.1.4

1. 确认 docker 运行的 milvus容器镜像版本情况&#xff1a; 2. pom 依赖&#xff1a; <dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.1.0</version><exclusions><exclusi…

Java学习 - Redis慢查询与发布订阅与流水线

慢查询 慢查询是什么 慢查询本质是慢查询日志&#xff0c;它记录了一些执行速度很慢的命令 慢查询与生命周期 生命周期 ------- ------------------------------------------ | | 1.发送请求 | redis服务端 …

Simulink缓存文件有什么用?

在使用Simulink进行仿真的过程中&#xff0c;经常会发现目录下存在一些后缀为.slxc的文件&#xff0c;这些其实就是Simulink模型的缓存文件&#xff08;.slx cache&#xff09;。 Simulink缓存文件的主要作用是提高仿真和代码生成的效率。 借助缓存文件&#xff0c;可以避免…

Web浏览器读写NFC Ntag标签

本示例使用的发卡器&#xff1a;RS232串口USB转COM读写器IC卡发卡器WEB浏览器二次开发JS编程SDK-淘宝网 (taobao.com) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&g…

不锈钢氩弧焊丝ER316L

说明&#xff1a;TG316L 是超低碳的不锈钢焊丝。熔敷金属耐蚀、耐热、抗裂性能优良。防腐蚀性能良好。 用途:用于石油化工、化肥设备等。也可用于要求焊接后不进行热处理的高Cr钢的焊接。

真实评测:可道云teamOS文件上传功能丝滑到爱不释手

对于每日沉浸在图片与视频海洋中的媒体工作者而言&#xff0c;与海量的多媒体文件打交道几乎成了家常便饭。 文件的上传和存储&#xff0c;对他们而言&#xff0c;不仅仅是工作中的一个环节&#xff0c;更像是将一天的辛勤与付出妥善安置的仪式。无论是突发现场的精彩瞬间&am…

海报在线制作系统源码小程序

轻松设计&#xff0c;创意无限 一款基于ThinkPHPFastAdminUniApp开发的海报在线制作系统&#xff0c; 本系统不包含演示站中的素材模板资源。​ 一、引言&#xff1a;设计新纪元&#xff0c;在线海报制作引领潮流 在数字时代&#xff0c;海报已成为传播信息、展示创意的重要媒…

配音软件哪个好用?推荐5款智能配音软件

随着期末考来袭&#xff0c;校园里的空气似乎都凝固了&#xff0c;每个角落都充满了紧张的气氛。 然而&#xff0c;在这紧张的氛围中&#xff0c;有一群学生却显得格外从容&#xff0c;因为他们掌握了一种秘密武器——配音软件。这些软件就像是他们的个人学习助理&#xff0c;…

git 中有关 old mode 100644、new mode 10075的问题解决小结

问题&#xff1a; 同一个文件被修改后&#xff0c;最后代码没有变&#xff0c;文件变了&#xff0c;导致提交了一个空文件 git diff 提示 filemode 发生改变&#xff08;old mode 100644、new mode 10075&#xff09; 解决办法 &#xff1a; 原来是 filemode 的变化&#xff…

虚拟机字节码执行引擎之运行时栈帧结构

概述 执行引擎是Java虚拟机核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约地定…

彩虹PLM系统:引领汽车行业的数字化转型

彩虹PLM系统&#xff1a;引领汽车行业的数字化转型 彩虹PLM系统作为汽车行业数字化转型的引领者&#xff0c;凭借其卓越的技术实力和丰富的行业经验&#xff0c;为汽车行业带来了全面的解决方案。以下是彩虹PLM系统如何引领汽车行业数字化转型的详细分析&#xff1a; 一、整合全…

约课健身管理系统小程序源码

健身达人的智能助手 一款基于FastAdminThinkPHPUniapp开发的米扬约课健身管理系统&#xff0c;应用于健身房&#xff0c;健身工作室&#xff0c;运动会所&#xff0c;运动场馆&#xff0c;瑜伽馆&#xff0c;拳馆等泛健身行业的场馆中。米扬约课健身致力于为各种健身场馆打造真…

Verilog刷题笔记49——Fsm1同步复位

题目&#xff1a; 解题&#xff1a; module top_module(clk,reset,in,out);input clk;input reset;input in;output out;parameter A0,B1;reg [1:0]current_state,next_state;always(posedge clk)beginif(reset)current_stateB;elsecurrent_statenext_state;endalways(*)beg…

手机图片怎么上传到电脑记事本 保姆级教程

在这个数字化时代&#xff0c;手机成了我们随身携带的摄影棚。无论是旅途中的美景&#xff0c;还是与朋友欢聚的瞬间&#xff0c;手机总能轻松捕捉。然而&#xff0c;手机存储空间有限&#xff0c;那些珍贵的照片和视频&#xff0c;怎样才能安全又方便地保存下来&#xff0c;供…

PFA量杯:精确、实用、高颜值的量杯选择!

您是否在烹饪、实验室实验或日常使用中需要一个精确、实用且高颜值的量杯&#xff1f;PFA量杯是您最佳的选择&#xff01; PFA量杯采用高品质材料制成&#xff0c;具有良好的耐腐蚀性、耐高温性&#xff0c;使用寿命长。透明度极高的杯身&#xff0c;使您在使用时能够清晰地看…

LLM在软件测试中的革新应用

一、引言 随着人工智能技术的蓬勃发展&#xff0c;大语言模型&#xff08;Large Language Model&#xff0c;简称LLM&#xff09;如GPT系列&#xff0c;在自然语言处理领域取得了显著进展。LLM不仅能够处理复杂的自然语言任务&#xff0c;还在多个领域展现出广泛的应用潜力。在…

深圳网页设计收费情况

深圳是中国最具活力和发展速度最快的城市之一&#xff0c;随着经济的快速发展&#xff0c;各种行业都飞速发展&#xff0c;尤其是互联网行业。网页设计是互联网行业的重要组成部分&#xff0c;深圳的网页设计师数量也是非常庞大的。那么&#xff0c;深圳网页设计师的收费情况是…