LOADING

加载过慢请开启缓存 浏览器默认开启

zero two

xv6-用户调用-过程

操作系统 2025/5/26

好的,我们来通过一个具体的用户程序调用 write 系统调用的例子,串联起用户空间(user)和内核空间(kernel)的交互过程。假设我们有一个简单的用户程序 write_hello.c,它想向标准输出(文件描述符 1)写入 “Hello, xv6!” 字符串。

// write_hello.c
#include "types.h"
#include "user.h"
#define MSG "Hello, xv6!\n"
#define MSG_LEN 10 // "Hello, xv6!\n" 的长度
int main(void) {
    write(1, MSG, MSG_LEN); // 调用 write 系统调用
    exit(); // 退出程序
}

让我们编译并运行它(在 QEMU 模拟的 xv6 环境中):

$ cc write_hello.c
$ ./write_hello
Hello, xv6!
$ 

现在,我们一步步追踪这个 write(1, MSG, MSG_LEN) 调用:

  1. 用户程序调用 write 函数:
    • write_hello.c 中,main 函数调用了 write 函数。
    • 由于 write_hello.c 包含了 user.h (#include "user.h"),它看到的 write 函数原型定义在 user.h 中,通常看起来像这样:
      // user.h
      int write(int fd, char *buf, int n);
      
    • 这个 write 函数是一个用户空间的库函数(在 xv6 中,这些库函数通常非常简单,直接调用系统调用)。
  2. 用户空间库函数调用 syscall:
    • user.c 文件中实现了 write 函数。它会检查参数(如 fd 是否有效,buf 是否指向用户空间地址等),然后调用一个通用的系统调用接口函数,通常是 syscall
    • user.c 中的 write 函数实现大致如下:
      // user.c
      #include "user.h"
      #include "syscall.h" // 包含系统调用号定义
      int write(int fd, char *buf, int n) {
          // 参数检查 (简化)
          if (fd < 0 || n < 0) {
              return -1;
          }
          // 调用通用的系统调用函数
          return syscall(SYS_write, fd, (int)(uint64)buf, n); // 注意 buf 的类型转换
      }
      
    • syscall 函数也是 user.c 中定义的,它的作用是准备系统调用所需的参数,并触发一个软件中断(在 RISC-V 上通常是 ecall 指令)来进入内核模式。
  3. 触发 ecall 指令:
    • syscall 函数会将要调用的系统调用号(SYS_write,定义在 syscall.h 中)和参数(fd, buf 的地址, n)放到特定的寄存器中(例如 RISC-V 的 a7 存放系统调用号,a0, a1, a2 存放参数)。
    • 然后,syscall 函数执行 ecall 指令。这会触发一个异常(Trap),将 CPU 从用户模式切换到内核模式,并跳转到内核中预先设置好的异常处理入口点(通常是 trap 处理程序)。
  4. 内核 trap 处理程序:
    • 内核的 trap 处理程序(通常在 kernel/trap.c 中)被 ecall 指令触发。
    • trap 处理程序首先保存当前的处理器状态(如寄存器值),以便稍后恢复用户程序。
    • 它需要确定发生了什么事件。由于 ecall 是一种同步异常,trap 处理程序会检查 a7 寄存器中的值(系统调用号)。
    • 如果 a7 的值是 SYS_writetrap 处理程序就知道这是一个 write 系统调用请求。
  5. 内核调用 sys_write 函数:
    • trap 处理程序会调用内核中实现 write 系统调用的具体函数,通常是 sys_write(定义在 kernel/sysfile.c 中)。
    • trap 处理程序会将用户传递的参数(从 a0, a1, a2 寄存器获取)传递给 sys_write
    • sys_write 函数实现实际的写入逻辑:
      • 它根据文件描述符 fd 查找对应的打开文件结构(struct file)。
      • 检查调用者是否有写入权限。
      • 检查 buf 是否指向有效的用户空间地址(防止内核访问非法内存)。
      • 如果 fd 对应的是标准输出(通常是控制台),它会调用控制台驱动相关的函数(如 consolewrite)。
      • consolewrite 函数负责将 n 个字节从内核缓冲区(sys_write 会先将用户空间的 buf 内容复制到内核空间的一个临时缓冲区)写入到控制台设备。这通常涉及将字符发送到串口或 VGA 内存。
  6. 返回结果给用户程序:
    • sys_write 函数执行完毕后,会将结果(写入的字节数,或出错时的错误码 -1)存储在一个内核和用户程序都能访问的特定位置,通常是 a0 寄存器。
    • trap 处理程序恢复之前保存的处理器状态。
    • trap 处理程序执行 ret(或类似的指令)返回到用户程序中被 ecall 指令中断的位置。
    • 用户程序继续执行 syscall 函数的下一条指令。syscall 函数从 a0 寄存器获取内核返回的结果,并将其作为 write 函数的返回值返回给 main 函数。
  7. 程序继续执行:
    • main 函数收到 write 的返回值(通常是 MSG_LEN),然后执行 exit() 系统调用,请求内核终止该进程。
      总结串联路径:
      write_hello.c (用户程序) -> user.h (函数声明) -> user.c (write 函数实现) -> syscall (通用系统调用接口) -> ecall (触发内核) -> trap (内核异常处理) -> sys_write (内核系统调用实现) -> consolewrite (设备驱动) -> 控制台输出 -> 返回结果 -> user.c (syscall 返回) -> user.c (write 返回) -> write_hello.c (main 继续执行)。这个过程清晰地展示了用户程序如何通过系统调用接口请求内核服务,以及内核如何处理这些请求并返回结果。user.c, user.h, syscall.c (如果存在,或其逻辑在 user.c 中) 构成了用户空间的桥梁,而 trap.c, sysfile.c, console.c 等构成了内核空间的处理逻辑。
阅读全文

解决启动windows search 错误3

解决方案 2025/5/25

如果你因为删除了某些分区导致注册表混乱,然后无法正确指向,那么你可以看一下这个链接,在你尝试了多种方法后都没有成功,因为他确实指出了注册表问题并且提出了解决方法。请仔细查看他们的恢复。
(Unable to Start Windows Search Service – “Error 3: The System Cannot Find the Path Specified”)[https://answers.microsoft.com/en-us/windows/forum/all/unable-to-start-windows-search-service-error-3-the/dc734fdc-8f9f-4ae9-b873-ea864f3663fc?page=4]

阅读全文

关于鸿蒙mini系统编译不过的问题的个人解决方案

OpenHarmony 2025/5/23

个人总结

wsl ubuntu 22.04
拉取源码时的命令:repo init -u git@gitee.com:openharmony/manifest.git -b master -g ohos:mini --no-repo-verify
bash build/prebuilts_download.sh 命令之后会出现一些什么pls check 我的理解就是mini系统不需要,至少我编译成功helloword的时候我没有去管,如果后面需要再去解决。
还有windows和linux联合的时候vscode远程链接之后vscode打不开那个deveco device tool
在使用全量的时候也出现了子系统名称不对,忘了解决没有反正后面还有问题就不管了
忘了忘了
当时的文档是v5.0.3 Release
还有如果提示格式错误就检查有没有格式错误,没有删掉那个.build文件

配置文件

./applications/sample/wifi-iot/app/my_first_app下的BUILD.gn文件

static_library(“my_first_app”) {
sources = [
“hello_world.c”
]
include_dirs = [
“//utils/native/lite/include”
]
}

build/lite/components/communication.json

{
  "component": "hello_world_app",
  "description": "hello world samples.",
  "optional": "true",
  "dirs": [
    "applications/sample/wifi-iot/app/my_first_app"
  ],
  "targets": [
    "//applications/sample/wifi-iot/app/my_first_app:my_first_app"
  ],
  "rom": "",
  "ram": "",
  "output": [],
  "adapted_kernel": [ "liteos_m" ],
  "features": [],
  "deps": {
    "components": [],
    "third_party": []
  }
}

vendor/hisilicon/hispark_pegasus/config.json

这里我没配置也通过了,下面连接他们说上面那个communication也可以不写,但是我会出错好像是报json的格式错误?去看那个文件也是错误的,那个文件是生成的所以应该找源头

\\wsl$\Ubuntu22.04\root\ohos_master\applications\sample\wifi-iot\app\BUILD.gn

# Copyright © 2020-2022 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the “License”);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an “AS IS” BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import(“//build/lite/config/component/lite_component.gni”)

lite_component(“app”) {
features = [ “startup”,“my_first_app” ]
}

\\wsl$\Ubuntu22.04\root\ohos_master\applications\sample\wifi-iot\app\my_first_app\BUILD.gn

static_library(“my_first_app”) {
sources = [
“hello_world.c”
]
include_dirs = [
“//utils/native/lite/include”
]
}

最后

这些在那个文档都是有配置规则的,不过是在后面,为了写hello world去看也不太可能
为了写hello world,先是获取源码,买了块移动硬盘发现是qlc就退了,想了想其实100多GB空间还是够的,就强行把五个分区合并成两个了(不是不知道危害,就是突然不想管了就想着前进,五个分区要正常解决要处理太多了,能卸载的卸载,一些文件或工具之类的就直接剪切,想着也没有空间去存那些先移出来的数据(写到这里触发了既视感),最后一些电脑组件损坏了,索引服务启动不了搜索界面出现一大块东西,注册表乱成一团,还好慢慢地恢复到能使用的情况了),然后去寻找减少空间的方法,发现可以只下载mini。按照教程来出现的问题也很多,就又下载small,后来又下载全量,还是很多问题,还有出现不同的问题,最后还是下载mini了,毕竟刚开始小总是好的,确定的东西总是能根据不同的状态出现不同的问题。还有挺多问题已经记不起来了,抱歉。

一些链接,希望能有帮助

轻量编译没必要完全按照,太精细反而不太好
json格式验证
Ubuntu22.04 搭建 OpenHarmony 命令行开发环境
[OpenHarmony5.0][环境][教程]OpenHarmony 5.0源码在WSL2 Ubuntu22.04 编译环境搭建教程
一键配置Ubuntu的OpenHarmony基础编译环境23年的
关于在Ubuntu中搭建DevEco Device Tool环境出现报错Unable to locate package python-venv
如何向OpenHarmony中编译构建自己的子系统、部件和模块
这里也很多链接找不到了

阅读全文

mit-6s081-lab02-System_call_tracing

操作系统 2025/5/19

声明

文章仅具有解释意义
希望能为你搭建知识框架

过程

Makefile Modification:

makefile
UPROGS=

$U/_trace
User Space Modifications:

user/user.h:

int trace(int);

user/usys.pl:

entry("trace");

Kernel Modifications:

kernel/syscall.h:

#define SYS_trace 22

kernel/syscall.c:


extern uint64 sys_trace(void);
//这里是自己添加不是将syscalls修改
static char *syscall_names[] = {   
    [SYS_fork]    "fork",
    [SYS_exit]    "exit",
    [SYS_wait]    "wait",
    [SYS_pipe]    "pipe",
    [SYS_read]    "read",
    [SYS_kill]    "kill",
    [SYS_exec]    "exec",
    [SYS_fstat]   "fstat",
    [SYS_chdir]   "chdir",
    [SYS_dup]     "dup",
    [SYS_getpid]  "getpid",
    [SYS_sbrk]    "sbrk",
    [SYS_sleep]   "sleep",
    [SYS_uptime]  "uptime",
    [SYS_open]    "open",
    [SYS_write]   "write",
    [SYS_mknod]   "mknod",
    [SYS_unlink]  "unlink",
    [SYS_link]    "link",
    [SYS_mkdir]   "mkdir",
    [SYS_close]   "close",
    [SYS_trace]   "trace",
};

kernel/sysproc.c:

uint64 sys_trace(void) {
    int mask;
    if (argint(0, &mask) < 0)
        return -1;
    struct proc *p = myproc();
    p->trace_mask = mask;
    return 0;
}

kernel/proc.h:

struct proc {
    ...
    int trace_mask;
    ...
};

kernel/proc.c (fork and allocproc):

int fork(void) {
    ...
    np->trace_mask = p->trace_mask;
    ...
}

static struct proc* allocproc(void) {
    ...
    p->trace_mask = 0;
    ...
}
kernel/syscall.c (syscall function):
void syscall(void) {
    int num;
    struct proc *p = myproc();

    num = p->trapframe->a7;
    if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
        p->trapframe->a0 = syscalls[num]();
        if ((p->trace_mask & (1 << num)) != 0) {
            char *name = "unknown";
            if (num < NELEM(syscall_names) && syscall_names[num] != 0) {
                name = syscall_names[num];
            }
            printf("%d: syscall %s -> %d\n", p->pid, name, p->trapframe->a0);
        }
    } else {
        printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);
        p->trapframe->a0 = -1;
    }
}
阅读全文

关于wsl无法使用samba

linux 2025/5/17

问题:
WSL 默认不支持直接运行 smbd
原因:
smbd 需要访问低层网络功能(如绑定到端口 445),WSL2 虚拟环境不允许这样做。
WSL2 的网络和 Windows 是隔离的(类似虚拟机),而且端口 445 通常已被 Windows 自身的 SMB 服务占用,smbd 无法监听。
推荐解决方案:
用 Windows 的 SMB 服务共享 WSL 文件夹
方法:

  1. wsl -l -v
    输出:
    NAME      STATE           VERSION  
    Ubuntu    Running         2  
    
    记住这个 name
  2. 在资源管理器地址栏输入 \\wsl$\name\home\share/home/share 是共享的文件夹,自行寻找)
    此时可以验证在 WSL 里共享文件夹下创建一个文件,在 Windows 查看是否存在
阅读全文

mit-6s081-lab01-xargs

操作系统 2025/5/15

前置知识

exec

exec命令的基本语法
exec命令的基本语法如下:

exec [options] [command] [arguments]

options: 可选参数,用于修改exec的行为。
command: 要执行的可执行文件或命令。
arguments: 传递给command的参数。
如果没有指定command,exec将用于修改当前shell的环境变量、文件描述符等设置。


read/write 参数012

fd:文件描述符,表示将数据写入到哪里。可以是文件、设备、套接字等。文件描述符是一个整数,通常是由 open 系统调用返回的。

0:标准输入(stdin)
1:标准输出(stdout)
2:标准错误(stderr)

sh < xxx.sh

sh < xargstest.sh 是在 xv6 实验环境中用于测试 xargs 实现的指令,其核心作用是通过输入重定向将脚本内容传递给 sh 解释器执行
等效于 cat xargstest.sh | sh,但更高效(避免中间管道进程)
.sh里面可能是一些命令然后重定向给sh执行

代码

#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"

#define MAXLINE 1024

void parse_args(char *line, char **args, int *argc) {
    *argc = 0;
    while (1) {
        // 跳过前导空格
        while (*line == ' ') {
            line++;
        }
        if (*line == '\0') {
            break;
        }
        args[(*argc)++] = line;
        // 寻找参数的结尾
        while (*line != ' ' && *line != '\0') {
            line++;
        }
        // 分割参数
        if (*line == ' ') {
            *line = '\0';
            line++;
        }
        if (*argc >= MAXARG) {
            break;
        }
    }
}

int read_line(int fd, char *buf, int max, int *found_newline) {
    int i = 0;
    *found_newline = 0;
    while (i < max - 1) {
        char ch;
        int n = read(fd, &ch, 1);
        if (n < 0) {
            return -1;
        }
        if (n == 0) {
            break; // EOF
        }
        if (ch == '\n') {
            *found_newline = 1;
            break;
        }
        buf[i++] = ch;
    }
    buf[i] = '\0';
    return i;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        char *msg = "xargs: missing command\n";
        write(2, msg, strlen(msg));
        exit(1);
    }

    while (1) {
        char line[MAXLINE];
        int found_newline;
        int len = read_line(0, line, sizeof(line), &found_newline);
        if (len < 0) {
            char *msg = "xargs: read error\n";
            write(2, msg, strlen(msg));
            exit(1);
        }
        if (len == 0 && !found_newline) {
            break; // EOF
        }

        char *args[MAXARG];
        int new_argc = 0;
        parse_args(line, args, &new_argc);

        char *new_argv[MAXARG];
        int total_args = 0;

        // 添加命令名称
        new_argv[total_args++] = argv[1];

        // 添加原参数
        for (int i = 2; i < argc; i++) {
            if (total_args >= MAXARG) {
                char *msg = "xargs: too many arguments\n";
                write(2, msg, strlen(msg));
                exit(1);
            }
            new_argv[total_args++] = argv[i];
        }

        // 添加新参数
        for (int i = 0; i < new_argc; i++) {
            if (total_args >= MAXARG) {
                char *msg = "xargs: too many arguments\n";
                write(2, msg, strlen(msg));
                exit(1);
            }
            new_argv[total_args++] = args[i];
        }

        new_argv[total_args] = 0; // 参数数组以NULL结尾

        // 创建子进程执行命令
        int pid = fork();
        if (pid < 0) {
            char *msg = "xargs: fork failed\n";
            write(2, msg, strlen(msg));
            exit(1);
        } else if (pid == 0) {
            // 子进程执行命令
            exec(new_argv[0], new_argv);
            // 如果exec失败
            char *msg = "xargs: exec failed\n";
            write(2, msg, strlen(msg));
            exit(1);
        } else {
            // 父进程等待子进程完成
            wait(0);
        }
    }

    exit(0);
}

一些解释

以echo hello too | xargs echo bye为例
首先会执行echo hello too输出到终端缓冲区,通过管道符传递给xargs的标准输入0
然后xargs运行时参数为char *argv[]={‘xargs’,‘echo’,‘bye’}
read_line会读取标准输入里的数据也就是hello too到line字符数组
parse_args解析line存到args指针数组里
此时args里就是{“hello\0”,“too\0”}
构造一个新的命令new_argv
这个新命令的索引:
0:echo
1:bye
添加args
2:hello
3:too
4:0
最后exec执行输出bye hello too,中间的空格应该是自己识别的吧

整理一下AI的总结

在Linux系统中,管道符|通过以下机制将数据从左侧命令传递给右侧命令,具体流程如下:
一、管道符的底层实现原理
匿名管道创建
当用户执行cmd1 | cmd2时,Shell会调用pipe()系统调用创建一个匿名管道(Anonymous Pipe),该管道由两个文件描述符(read_fd和write_fd)表示。
进程间文件描述符重定向
Shell通过fork()创建两个子进程(分别执行cmd1和cmd2),并利用dup2()将文件描述符重定向:
cmd1的标准输出(文件描述符1)被重定向到管道的write_fd,其输出不再直接发送到终端,而是写入管道。
cmd2的标准输入(文件描述符0)被重定向到管道的read_fd,其输入源变为管道。
内核缓冲区的数据传输
左侧命令(cmd1)的输出数据首先写入内核空间的管道缓冲区(默认4KB),而非直接发送到屏幕。
右侧命令(cmd2)通过read()系统调用从管道缓冲区按需读取数据,若缓冲区为空则阻塞等待。

管道是单向通信,数据从echo的stdout流向cat的stdin。

存储位置:当直接执行echo hello时,字符串hello默认通过标准输出(文件描述符1)发送到终端屏幕(即字符设备文件,如/dev/tty) echo内部应该有一个print 从0转变为1

阅读全文

虚拟机Linux超级块不匹配/启动提示无法在分区之外空间读取和写入

Linux 2025/5/14
注意

过程我有些记不清了,可以先看链接处理,主要是后面要进入parted模式
第一次no然后后面的就是yes那里不出意外很快的,我有一次按了很久的y都没成功,虽然不知道有没有影响
然后操作里会有选择start、end就将大小改回来就可以了
下面不保证操作顺序的正确
可以问问ai那些命令是什么意思判断调整顺序

“Either the superblock or the partition table is likely to be corrupt!” after partition resized

  1. 首先下载与你损坏虚拟机版本相同的iso文件,将损坏虚拟机的cd改为该iso,启动后try … install进入,即可使用命令行操作

  2. sudo fdisk -l /dev/whatever 比如/dev/sda(还是sda2了)
    
  3. 将结果抄在纸上。然后修复分区表:

    sudo fdisk /dev/whatever
    
  4. sudo fdisk /dev/sdX  # 替换为你的磁盘设备,如 /dev/sda
    
阅读全文

mit_6s081_lab01_find

操作系统 2025/5/13
知识点:
  1. stat 命令用于显示文件或文件系统状态的详细信息。它提供了比 ls -l 更加详细的输出,包括时间戳、权限、所有者等信息。用法:stat [选项]… 文件…
    显示文件或文件系统的状态。

  1. ‌dirent‌是C语言中用于文件操作的一个头文件,主要提供目录遍历功能,适用于需要访问文件系统的程序。在Linux等Unix-like系统中,它是标准库的一部分,但在Windows系统中需要使用其他API。dirent通过struct dirent结构体和一系列函数,如opendir(), readdir(), 和closedir()等,提供了基本的目录操作接口。

    dirent结构体
    dirent结构体用于描述目录中的条目,主要成员包括:

    ‌d_ino‌:文件的inode号码。
    ‌d_reclen‌:目录项的长度(以字节为单位)。
    ‌d_namlen‌:目录项名称的长度(不包括终止null字符)。
    ‌d_name‌:目录项的名称(以null结尾的字符串)。主要函数
    ‌opendir()‌:打开一个目录流,返回一个指向DIR结构的指针。如果无法打开目录,返回NULL。
    ‌readdir()‌:从目录流中读取下一个目录项,返回一个指向dirent结构体的指针。如果目录流中没有更多目录项,返回NULL。
    ‌closedir()‌:关闭一个目录流,释放与该目录流关联的系统资源。
    ‌rewinddir()‌:将目录流重置到开头。
    ‌seekdir()‌:将目录流移动到指定位置。
    ‌telldir()‌:返回目录流的当前位置。

阅读全文

mit_6s081_lab01_primes

操作系统 2025/5/12

这是关于mit6.s081课程中的lab01中primes实现的记录

阅读全文

Hello World

2023/11/5

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

$ hexo new "My New Post"

More info: Writing

Run server

$ hexo server

More info: Server

Generate static files

$ hexo generate

More info: Generating

Deploy to remote sites

$ hexo deploy

More info: Deployment

阅读全文
1 ... 2
avatar
lAn_rEd

Description
一个喜欢幻想的人

问答