《ORANGE’S:一个操作系统的实现》读书笔记(三十五)内存管理(三)

内存管理到目前为止实现了fork()、exit()和wait(),而我们的目标是实现一个可以执行命令的shell,可无论Init进程fork出多少进程,它也都只是Init而已。所以我们还需要一个系统调用,它就是exec(),这篇文章就来记录exec()的实现。

目录

exec

认识exec

为自己的操作系统编写应用程序

“安装”应用程序

实现exec


exec

认识exec

exec的语义很简单,它将当前的进程映像替换成另一个。也就是说,我们可以从硬盘上读取另一个可执行的文件,用它替换掉刚刚被fork出来的子进程,于是被替换的子进程摇身一变,就成了彻头彻尾的新鲜进程了。

以shell中常见的echo命令为例。我们输入“echo hello world”,shell就会fork出一个子进程A,这时A跟shell一模一样,fork结束后父进程和子进程分别判断自己的fork()返回值,如果是0则表明当前进程为子进程A,这时A马上执行一个exec(),于是进程A的内存映像被echo替换,它就变成echo了。这一过程可用如下代码表示。

代码 kernel/main.c,exec()的执行过程,

void Init()
{
...int pid = fork();if (pid != 0) { /* parent process */printf("parent is running, child pid:%d\n", pid);int s;int child = wait(&s);printf("child (%d) exited with status: %d.\n", child, s);} else { /* child process */execl("/echo", "echo", "hello", "world", 0);}
...
}

上述代码中用到的execl()是exec的一种形式,我们下文会进行说明。无论如何,这一过程看上去很简单,但是我们面临两个问题,一是要实现exec(),二是我们还没有一个可执行的程序echo呢。在这两个问题中,要顺利执行exec()依赖于可执行程序的存在。所以我们先需要写一个echo,下面就来做这项工作。

为自己的操作系统编写应用程序

echo将以操作系统中普通应用程序的身份出现,它跟操作系统的接口是系统调用。其实本质上,一个应用程序只能调用两种东西:属于自己的函数,以及中断(系统调用其实就是软中断)。可是根据我们写程序的经验,一个应用程序通常都会调用一些现成的函数,很少见写程序时里面满是中断调用的。这是因为编译器偷偷地为我们链接了C运行时库(CRT),库里面有已经编译好的可函数代码。这样两者链接起来,应用程序就能正确运行了。

假如我们要写一个echo,最笨的方法就是将send_recv()、printf()、write()等所有用到的系统调用的代码都复制到源文件中,然后编译一下。这肯定是能成功的,但更优雅的做法是制作一个类似C运行时库的东西。我们把之前已经写好的应用程序可以使用的库函数单独链接成一个文件,每次写应用程序的时候直接链接起来就好了。

到目前为止,可以被用来链接成库的文件及其包含的主要函数有这些(文件位置有改变,以下是改变后文件的存放位置):

  • 两个真正的系统调用:sendrec和printx
    • lib/syscall.asm
  • 字符串操作:memcpy、memset、strcpy、strlen
    • lib/string.asm
  • FS的接口
    • lib/open.c
    • lib/read.c
    • lib/write.c
    • lib/close.c
    • lib/unlink.c
  • MM的接口
    • lib/fork.c
    • lib/exit.c
    • lib/wait.c
  • SYS的接口
    • lib/getpid.c
  • 其它
    • lib/misc.c(send_recv()函数被移动到该文件中)
    • lib/vsprintf.c
    • lib/printf.c

我们就把这些函数单独链接成一个库,把它起名为orangescrt.a(也可以命名为其它名字),表明这是我们的C运行时库。做这样一个库的方法非常简单:

ar rcs lib/orangescrt.a lib/syscall.o lib/printf.o lib/vsprintf.o \
lib/string.o lib/misc.o lib/open.o lib/read.o lib/write.o lib/close.o \
lib/unlink.o lib/getpid.o lib/fork.o lib/exit.o lib/wait.o

有了库,我们就可以放心地写一个应用程序了,先写一个最简单的echo,代码如下所示。

代码 command/echo.c。

#include "stdio.h"int main(int argc, char * argv[])
{int i;int num = argc;char **t = argv;for (i = 1; i < num; i++) {printf("%s%s", (i == 1 ? "" : " "), t[i]);}printf("\n");return 0;
}

没有考虑任何异常情况,这个程序写得极为简单。我们对它进行编译链接:

gcc -I ../include/ -m32 -c -fno-builtin -o echo.o echo.c
ld -m elf_i386 -s -Ttext 0x30400 -o echo echo.o ../lib/orangescrt.a

输入命令后,你会发现,编译没有问题,但是链接出现了问题(如下所示):

ld: warning: cannot find entry symbol _start; defaulting to 00030400

这个问题是说找不到符号 _start。你可能想起来了,链接器需要找到 _start 作为入口,没关系,我们马上写一个,代码如下所示。

代码 command/start.asm。

extern main
extern exitbits 32[section .text]global _start_start:push eaxpush ecxcall main; need not clean up the stack herepush eaxcall exithlt ; should never arrive here

千万不要小看_start,虽然只有寥寥几行,但它肩负三项使命:

  • 为main()函数准备参数:argc和argv
  • 调用main()
  • 将main()函数的返回值通过exit()传递给父进程

怎么样?了不起吧,五行代码就做三件事情。之所以小小的start.asm要肩负如此多且重大的使命,原因就在于main()函数本质上只不过是个普通函数,在编译器的眼里,它跟其它任何函数没什么两样。既然是个普通函数,那么自然需要别人为它准备参数,调用它,以及做清理工作。这个角色便是由_start来扮演了。

好了,下面再来链接一下,这次别忘了加上start.o:

gcc -I ../include/ -m32 -c -fno-builtin -o echo.o echo.c
nasm -f elf -o start.o start.asm
ld -m elf_i386 -s -Ttext 0x30400 -o echo echo.o start.o ../lib/orangescrt.a

成功了!我们有了自己的应用程序!可是高兴之后,又一个问题摆在面前:如何将这个程序放进我们的操作系统中呢?

“安装”应用程序

我们还记得,在FS进程启动时,会调用mkfs()创建一个简单的文件系统,里面创建了四个特殊文件:“.”以及“dev_tty[012]”。既然可以创建特殊文件,那么普通文件也是可以创建的,所以我们完全可以在mkfs()中多加几行代码,创建一个普通文件,然后在Linux中用dd命令将文件内容写入磁盘映像文件,一切就大功告成了。

不过这样一来有个明显的缺点,就是以后我们每写一个程序,就得改造mkfs(),很费事,而且容易出错。所以我们可以将这个方法稍作改进,将所有的应用程序文件打成一个tar包,做成一个文件,然后放进去,在操作系统启动时将这个包解开,问题就解决了。这个改进方法需要我们额外付出的努力,就是需要写一小段程序来解开tar包。

总结一下,要想“安装”一些应用程序到我们的文件系统中,需要做如下工作:

  • 编写应用程序,并编译链接。
  • 将链接好的应用程序打成一个tar包:inst.tar。
  • 将inst.tar用工具dd写入磁盘(映像)的某段特定扇区(假设这一段的首扇区的扇区号为X)。
  • 启动系统,这时mkfs()会在文件系统中建立一个新文件cmd.tar,它的inode中的i_start_sect成员会被设为X。
  • 在某个进程中——比如Init——将cmd.tar解包,将其中包含的文件存入文件系统。

这个方法算不上好,如果可以通过软盘或光盘来安装应用程序肯定更好,但那需要写相应的驱动程序,你也看到了,我们现在非常想在我们的操作系统中使用自己编写的应用程序,所以使用这个空降兵硬塞的方法来“安装”应用程序。其实这个方法也没有那么差,我们也可以学习一下如何解开tar包。

我们首先来改造mkfs(),在其中增加一个文件,代码如下所示。

代码 fs/main.c,在mkfs()中增加文件cmd.tar。

/*** <Ring 1> Make a available Orange'S FS in the disk. it will*              - Write a super block to sector 1.*              - Create three special files: dev_tty0, dev_tty1, dev_tty2*              - Create a file cmd.tar*              - Create the inode map*              - Create the sector map*              - Create the inodes of the files*              - Create '/' the root directory*/
PRIVATE void mkfs()
{
.../* inode map */memset(fsbuf, 0, SECTOR_SIZE);for (i = 0; i < (NR_CONSOLES + 3); i++) {fsbuf[0] |= 1 << i;}assert(fsbuf[0] == 0x3F); /* 0011 1111 :*   || ||||*   || |||`--- bit 0 : reserved*   || ||`---- bit 1 : the first inode,*   || ||              which indicates `/'*   || |`----- bit 2 : /dev_tty0*   || `------ bit 3 : /dev_tty1*   |`-------- bit 4 : /dev_tty2*   `--------- bit 5 : /cmd.tar*/WR_SECT(ROOT_DEV, 2);/* sector map */
.../* cmd.tar */int bit_offset = INSTALL_START_SECT - sb.n_1st_sect + 1; /* sect M <-> bit (M - sb.n_1st_sect + 1) */int bit_off_in_sect = bit_offset % (SECTOR_SIZE * 8);int bit_left = INSTALL_NR_SECTS;int cur_sect = bit_offset / (SECTOR_SIZE * 8);RD_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + cur_sect);while (bit_left) {int byte_off = bit_off_in_sect / 8;/* this line is ineffecient in a loop, but I don't care */fsbuf[byte_off] |= 1 << (bit_off_in_sect % 8);bit_left--;bit_off_in_sect++;if (bit_off_in_sect == (SECTOR_SIZE * 8)) {WR_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + cur_sect);cur_sect++;RD_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + cur_sect);bit_off_in_sect = 0;}}WR_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + cur_sect);/* inodes *//* inode of '/' */memset(fsbuf, 0, SECTOR_SIZE);struct inode * pi = (struct inode*)fsbuf;pi->i_mode = I_DIRECTORY;pi->i_size = DIR_ENTRY_SIZE * 5; /* 5 files:* '.'* 'dev_tty0', 'dev_tty1', 'dev_tty2',* 'cmd.tar'*/pi->i_start_sect = sb.n_1st_sect;pi->i_nr_sects = NR_DEFAULT_FILE_SECTS;
.../* inode of '/cmd.tar' */pi = (struct inode*)(fsbuf + (INODE_SIZE * (NR_CONSOLES + 1)));pi->i_mode = I_REGULAR;pi->i_size = INSTALL_NR_SECTS * SECTOR_SIZE;pi->i_start_sect = INSTALL_START_SECT;pi->i_nr_sects = INSTALL_NR_SECTS;WR_SECT(ROOT_DEV, 2 + sb.nr_imap_sects + sb.nr_smap_sects);/* '/' */memset(fsbuf, 0, SECTOR_SIZE);struct dir_entry * pde = (struct dir_entry *)fsbuf;pde->inode_nr = 1;strcpy(pde->name, ".");/* dir entries of '/dev_tty0~2' */for (i = 0; i < NR_CONSOLES; i++) {pde++;pde->inode_nr = i + 2; /* dev_tty0's inode_nr is 2 */sprintf(pde->name, "dev_tty%d", i);}(++pde)->inode_nr = NR_CONSOLES + 2;strcpy(pde->name, "cmd.tar");WR_SECT(ROOT_DEV, sb.n_1st_sect);
}

除了写sector-map的一段代码有点丑,其它都还好。注意其中我们引入的两个宏:

代码 include/config.h,两个宏。

/*** Some sector are reserved for us (the gods of the os) to copy a tar file* there, which will be extracted and used by the OS.*/
#define INSTALL_START_SECT      0x8000
#define INSTALL_NR_SECTS        0x800

INSTALL_START_SECT便是cmd.tar的首扇区的扇区号,注意它不要越过分区的边界。INSTALL_NR_SECTS这里设置成了0x800,也就是说cmd.tar最多1MB。

好了,下面是时候将应用程序打包并降到磁盘映像了:

打包的命令很简单。我们除了将echo打入包中,还加入了另一个简单的程序pwd(程序代码位于command/pwd.c中,里面只是输出了一个“/”)。之所以又加入一个文件,是因为这样可以更好地理解生成的tar文件结构。

写入磁盘的dd命令稍微复杂一点,其实它是在一个命令中嵌入了其它命令,所以显示很凌乱。其中

egrep -e '^ROOT_BASE' ../boot/include/load.inc | sed -e 's/.*0x//g' | sed -e 's/^M//g'

的作用是从load.in中找出ROOT_BASE的定义,并取出其十六进制的值,所以其输出位“6000”(ROOT_BASE需要根据你硬盘映像的划分进行设置,我的是6000)。这是根设备的开始扇区号。其中的

sed -e 's/^M//g'

是用来处理Windows文件下行尾的标识。在Windows机器上编译,然后拿到Linux上运行,有一个隐藏问题,就是格式不对,就是文件每一行末尾都有一个 ^M,但是要处理这个 ^M,不能使用键盘直接打印,需要使用 ctrl+v,ctrl+m 打印出来。如果是在Linux环境下进行的编辑,则不用加这个处理。

这一部分:

egrep -e '#define[[:space:]]*INSTALL_START_SECT' ../include/config.h | sed -e 's/.*0x//g' | sed -e 's/^M//g'

的作用是从config.h中找到INSTALL_START_SECT的定义,并取出其十六进制值,其输出位“8000”。这是cmd.tar的开始扇区号。

有了这两个值,命令通过工具bc计算cmd.tar的字节偏移:

echo "obase=10;ibase=16;(6000+8000)*200" | bc

这是cmd.tar相对于这个磁盘映像的字节偏移。

另外,这一部分:

ls -l inst.tar | awk -F " " '{print $5}'

得到的是inst.tar的文件大小(以字节为单位)。

有了这些,我们就可以用dd命令来写入了,所以最终上面那个负载的命令到最后执行的是:

dd if=inst.tar of=../hd.img seek=29360128 bs=1 count=20480 conv=notrunc

通过这个复杂的命令行,我们也可以管窥shell的威力——命令可以组合起来,完成复杂功能,一个命令抵得上一个小程序。

好了,材料都已齐备,下面就该开始动手做了。我们改造一下Init,让它可以读取cmd.tar,并且将包解开,代码如下所示。

代码 kernel/main.c,解包。

/*** @struct posix_tar_header* Borrowed from GNU 'tar'*/
struct posix_tar_header
{                       /* byte offset */char name[100];     /*   0 */char mode[8];       /* 100 */char uid[8];        /* 108 */char gid[8];        /* 116 */char size[12];      /* 124 */char mtime[12];     /* 136 */char chksum[8];     /* 148 */char typeflag;      /* 156 */char linkname[100]; /* 157 */char magic[6];      /* 257 */char version[2];    /* 263 */char uname[32];     /* 265 */char gname[32];     /* 297 */char devmajor[8];   /* 329 */char devminor[8];   /* 337 */char prefix[155];   /* 345 *//* 500 */
};/*** Extract the tar file and store them.* * @param filename The tar file.*/
void untar(const char * filename)
{printf("[extract '%s'\n", filename);int fd = open(filename, O_RDWR);assert(fd != -1);char buf[SECTOR_SIZE * 16];int chunk = sizeof(buf);while (1) {read(fd, buf, SECTOR_SIZE);if (buf[0] == 0) {break;}struct posix_tar_header * phdr = (struct posix_tar_header *)buf;/* calculate the file size */char * p = phdr->size;int f_len = 0;while (*p) {f_len = (f_len * 8) + (*p++ - '0'); /* octal */}int bytes_left = f_len;int fdout = open(phdr->name, O_CREAT | O_RDWR);if (fdout == -1) {printf("    failed to extract file: %s\n", phdr->name);printf(" aborted]\n");return;}printf("    %s (%d bytes)\n", phdr->name, f_len);while (bytes_left) {int iobytes = min(chunk, bytes_left);read(fd, buf, ((iobytes - 1) / SECTOR_SIZE + 1) * SECTOR_SIZE);write(fdout, buf, iobytes);bytes_left -= iobytes;}close(fdout);}close(fd);printf(" done]\n");
}void Init()
{int fd_stdin = open("/dev_tty0", O_RDWR);assert(fd_stdin == 0);int fd_stdout = open("/dev_tty0", O_RDWR);assert(fd_stdout == 1);printf("Init() is running ...\n");/* extract 'cmd.tar' */untar("/cmd.tar");
...
}

打成tar包的过程,是在每个文件前面加一个512字节的文件头,并把所有文件叠放在一起。所以解包的过程,就是读取文件头,根据文件头里记录的文件大小读出文件,然后是下一个文件头和下一个文件,如此循环。tar文件的文件头定义是从GUN tar的源代码中借过来的,我们只用到了其中两项:name和size。需要注意的是size一项存放的不是个整数,而是个字符串,而且用的是八进制,这需要我们读出来之后转换一下。

好了,我们运行一下,效果如下图所示。

看上去一切良好,通过直接观察磁盘映像,我们可以看到如下内容:

一眼就看到,我们的文件系统多了两个文件。细心又耐心的你可以通过inode等数据结构的值来手工验证文件写入的正确性,此处不再赘述。

实现exec

应用程序已经齐备,终于可以写一个exec()了,我们还是先完成库函数,但这次跟之前不同,因为exec()通常有若干变体,比如你查看exec的manpage,会看到这样的解释:

一阵眩晕,眼花缭乱。不要怕,这些命令是有规律的,其中:

  • l 表示函数直接接收来自命令行的参数(可变参数)
  • p 表示使用PATH环境变量来寻找要执行的文件
  • e 表示指向环境变量的指针直接传递给了子进程
  • v 表示以字符指针的方式传递来自命令行的参数

在这里我们不去实现所有的变体,只选其中的execl()和execv()来实现,代码如下所示。

代码 lib/exec.c,exec(),这是新建的文件。

PUBLIC int execl(const char * path, const char * arg, ...)
{va_list parg = (va_list)(&arg);char **p = (char**)parg;return execv(path, p);
}PUBLIC int execv(const char * path, char * argv[])
{char ** p = argv;char arg_stack[PROC_ORIGIN_STACK];int stack_len = 0;while (*p++) {assert(stack_len + 2 * sizeof(char*) < PROC_ORIGIN_STACK);stack_len += sizeof(char*);}*((int*)(&arg_stack[stack_len])) = 0;stack_len += sizeof(char*);char ** q = (char**)arg_stack;for (p = argv; *p != 0; p++) {*q++ = &arg_stack[stack_len];assert(stack_len + strlen(*p) + 1 < PROC_ORIGIN_STACK);strcpy(&arg_stack[stack_len], *p);stack_len += strlen(*p);arg_stack[stack_len] = 0;stack_len++;}MESSAGE msg;msg.type = EXEC;msg.PATHNAME = (void*)path;msg.NAME_LEN = strlen(path);msg.BUF = (void*)arg_stack;msg.BUF_LEN = stack_len;send_recv(BOTH, TASK_MM, &msg);assert(msg.type == SYSCALL_RET);return msg.RETVAL;
}

execl()最终调用execv()。而execv()所做的其实只是一件事,那就是向MM提供最终供调用exec的进程使用的堆栈。我们知道,main()函数接受两个参数:argc和argv,其中的argv看上去像个细绳,实际上另一端栓着一头牛呢。通过一个argv,我们可以得到用户输入的所有参数,我们来看一下这个过程,请看下图。

这个图描述的是echo的main()函数执行前堆栈的情形(假设我们输入了命令“echo hello world”)。argv是个指向指针数组的指针,图中argv的值为0xC000,这是个地址。在0xC000处是个指针数组,也就是说,数组内放着另外一些指针。图中可以看到,数组内有0xC010,0xC015和0xC01B三个指针。这三个指针分别指向三个字符串:“echo”、“hello”和“world”。这就是echo的main()函数开始执行时内存应该有的样子。换句话说,在将控制权交给main()之前,MM应该先将内存准备成上图所示的样子。

execv()完成的就是这么一件事情,它先准备好一块内存arg_stack[],然后完成以下工作:

  • 遍历其调用者(比如execl())传递给自己的参数,数一数参数的个数
  • 将指针数组的末尾赋零
  • 遍历所有字符串
    • 将字符串复制到arg_stack[]中
    • 将每个字符串的地址写入指针数组的正确位置

这项工作做完之后,它将arg_stack[]的首地址以及其中有效内容的长度等内容通过消息发送给MM,MM就可以进行实际的exec操作了。

我们下面在MM中用do_exec()来处理EXEC消息。代码如下所示。

代码 mm/exec.c,do_exec(),这是新建的文件。

/*** Perform the exec() syscall call.* * @return Zero if successfule, otherwise -1.*/
PUBLIC int do_exec()
{/* get parameters from the message */int name_len = mm_msg.NAME_LEN; /* length of filename */int src = mm_msg.source;        /* caller proc nr. */assert(name_len < MAX_PATH);char pathname[MAX_PATH];phys_copy((void*)va2la(TASK_MM, pathname),(void*)va2la(src, mm_msg.PATHNAME),name_len);pathname[name_len] = 0; /* terminate the string *//* get the file size */struct stat s;int ret = stat(pathname, &s);if (ret != 0) {printl("{MM} MM::do_exec()::stat() returns error. %s", pathname);return -1;}/* read the file */int fd = open(pathname, O_RDWR);if (fd == -1) {return -1;}assert(s.st_size < MMBUF_SIZE);read(fd, mmbuf, s.st_size);close(fd);/* overwrite the current proc image with the new one */Elf32_Ehdr * elf_hdr = (Elf32_Ehdr*)(mmbuf);int i;for (i = 0; i < elf_hdr->e_phnum; i++) {Elf32_Phdr * prog_hdr = (Elf32_Phdr*)(mmbuf + elf_hdr->e_phoff + (i * elf_hdr->e_phentsize));if (prog_hdr->p_type == PT_LOAD) {assert(prog_hdr->p_vaddr + prog_hdr->p_memsz < PROC_IMAGE_SIZE_DEFAULT);phys_copy((void*)va2la(src, (void*)prog_hdr->p_vaddr),(void*)va2la(TASK_MM, mmbuf + prog_hdr->p_offset),prog_hdr->p_filesz);}}/* setup the arg stack */int orig_stack_len = mm_msg.BUF_LEN;char stackcopy[PROC_ORIGIN_STACK];phys_copy((void*)va2la(TASK_MM, stackcopy),(void*)va2la(src, mm_msg.BUF),orig_stack_len);u8 * orig_stack = (u8*)(PROC_IMAGE_SIZE_DEFAULT - PROC_ORIGIN_STACK);int delta = (int)orig_stack - (int)mm_msg.BUF;int argc = 0;if (orig_stack_len) { /* has args */char ** q = (char**)stackcopy;for (; *q != 0; q++, argc++) {*q += delta;}}phys_copy((void*)va2la(src, orig_stack),(void*)va2la(TASK_MM, stackcopy),orig_stack_len);proc_table[src].regs.ecx = argc; /* argc */proc_table[src].regs.eax = (u32)orig_stack; /* argv *//* setup eip & esp */proc_table[src].regs.eip = elf_hdr->e_entry;proc_table[src].regs.esp = PROC_IMAGE_SIZE_DEFAULT - PROC_ORIGIN_STACK;strcpy(proc_table[src].p_name, pathname);return 0;
}

代码分为九部分:

  1. 从消息体中获取各种参数。由于调用者和MM处在不同的地址空间,所以对于文件名这样的一段内存,需要通过获取其物理地址并进行物理地址复制。
  2. 通过一个新的系统调用stat()获取被执行文件的大小。
  3. 将被执行文件全部读入MM自己的缓冲区(MM的缓冲区有1MB),我们姑且假设这个空间足够了。等有一天真的不够了,会触发一个assert,到时我们再做打算)。
  4. 根据ELF文件的程序头(Program Header)信息,将被执行文件的各个段放置到合适的位置。
  5. 建立参数栈——这个栈在execv()中已经准备好了,但由于内存空间发生了变化,所以里面所有的指针都需要重新定位,这个过程并不难,通过一个delta变量即可完成。
  6. 为被执行程序的eax和ecx赋值——还记得_start中我们将eax和ecx压栈码?压的就是argv和argc。它们是在这里被赋值的。
  7. 为程序的eip赋值,这是程序的入口地址,即 _start 处。
  8. 为程序的esp赋值。注意要闪出刚才我们准备好的堆栈的位置。
  9. 最后是将进程的名字改成被执行程序的名字。

现在我们在MM中加上EXEC的消息处理。

代码 mm/main.c。

/* <Ring 1> The main loop of TASK MM. */
PUBLIC void task_mm()
{
...case EXEC:mm_msg.RETVAL = do_exec();break;
...
}

现在我们来看一下获取文件信息的函数stat()。

代码 lib/stat.c,stat(),这是新建的文件。

/*** @param path * @param buf * * @return  On success, zero is returned. On error, -1 is returned.*/
PUBLIC int stat(const char *path, struct stat *buf)
{MESSAGE msg;msg.type = STAT;msg.PATHNAME = (void*)path;msg.BUF = (void*)buf;msg.NAME_LEN = strlen(path);send_recv(BOTH, TASK_FS, &msg);assert(msg.type == SYSCALL_RET);return msg.RETVAL;
}

我们下面在FS中用do_stat()来处理STAT消息。代码如下所示。

代码 fs/misc.c,do_stat()。

/** Perform the stat() syscall.* * @return  On success, zero is returned. On error, -1 is returned.*/
PUBLIC int do_stat()
{char pathname[MAX_PATH]; /* parameter from the caller */char filename[MAX_PATH]; /* directory has been stipped *//* get parameters from the message */int name_len = fs_msg.NAME_LEN; /* length of filename */int src = fs_msg.source;        /* caller proc nr. */assert(name_len < MAX_PATH);phys_copy((void*)va2la(TASK_FS, pathname),    /* to   */(void*)va2la(src, fs_msg.PATHNAME), /* from */name_len);pathname[name_len] = 0; /* terminate the string */int inode_nr = search_file(pathname);if (inode_nr == INVALID_INODE) { /* file not found */printl("FS::do_stat():: search_file() returns invalid inode: %s\n", pathname);return -1;}struct inode * pin = 0;struct inode * dir_inode;if (strip_path(filename, pathname, &dir_inode) != 0) {/* theoretically never fail here* (it would have failed earlier when*  search_file() was called)*/assert(0);}pin = get_inode(dir_inode->i_dev, inode_nr);struct stat s; /* the thing requested */s.st_dev = pin->i_dev;s.st_ino = pin->i_num;s.st_mode= pin->i_mode;s.st_rdev= is_special(pin->i_mode) ? pin->i_start_sect : NO_DEV;s.st_size= pin->i_size;put_inode(pin);phys_copy((void*)va2la(src, fs_msg.BUF), /* to   */(void*)va2la(TASK_FS, &s),     /* from */sizeof(struct stat));return 0;
}

结构体stat的定义在type.h中:

/*** @struct stat* @brief  File status, returned by syscall stat();*/
struct stat {int st_dev;     /* major/minor device number */int st_ino;     /* i-node number */int st_mode;    /* file mode, protection bits, etc. */int st_rdev;    /* device ID (if special file) */int st_size;    /* file size */
};

最后在FS中加上STAT的消息处理。

代码 fs/main.c,STAT消息处理。

/*** <Ring 1> The main loop of TASK FS.*/
PUBLIC void task_fs()
{
...case STAT:fs_msg.RETVAL = do_stat();break;
...
}

好了,我们现在可以编译运行了,不忘忘记更改Makefile,运行效果如下图所示。

“hello world”出现了!虽然你可能已经写过很多个hello world,但估计没有一次可以和这次相比。这是我们在自己操作系统上用自己的应用程序打印的hello world。

欢迎关注我的公众号


 

公众号中对应文章附有当前文章代码下载说明。

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

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

相关文章

最长公共前缀(Leetcode14)

例题&#xff1a; 分析&#xff1a; 我们可以先定义两个变量 i &#xff0c; j&#xff0c; j表示数组中的每一个字符串&#xff0c; i 表示每个字符串中的第几个字符。一列一列地进行比较&#xff0c;先比较第一列的字符&#xff0c;若都相同&#xff0c;则 i &#xff0c;继…

成功解决「MySQL问题1」启动mysql时:发生系统错误5拒绝访问

我 | 在这里 &#x1f575;️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 &#x1f3e0; 工作 | 广州 ⭐ Java 全栈开发&#xff08;软件工程师&#xff09; ✈️公众号 | 热爱技术的小郑 。文章底部有个人公众号二维码。回复 Java全套视频教程 或 前端全套视频教程 即可获取 300G 教程资…

【数据结构】 顺序表的基本操作 (C语言版)

一、顺序表 1、顺序表的定义&#xff1a; 线性表的顺序存储结构&#xff0c;即将表中的结点按逻辑顺序依次存放在一组地址连续的存储单元里。这种存储方式使得在逻辑结构上相邻的数据元素在物理存储上也是相邻的&#xff0c;可以通过数据元素的物理存储位置来反映其逻辑关系。…

漫漫数学之旅009

文章目录 经典格言数学习题古今评注拓展学习&#xff08;一&#xff09;大数定理&#xff08;二&#xff09;伯努利级数 经典格言 真正的问题&#xff0c;不在于机器是否思考&#xff0c;而在于人们是否思考。——BF斯金纳&#xff08;B. F. Skinner&#xff09; BF斯金纳&…

2024首更---Web Service 教程

Web Services 简介 Web Services 可使您的应用程序成为 Web 应用程序。 Web Services 通过 Web 进行发布、查找和使用。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对下面的知识有基本的了解&#xff1a; HTMLXML 如果您希望首先学习这些项目&#xff0c;请在…

2024年华数杯国际赛A题:放射性废水处理建模 思路模型代码解析

2024年华数杯国际赛A题&#xff1a;放射性废水处理建模&#xff08;Radioactive Wastewater from Japan&#xff09; 一、问题描述 2011年3月&#xff0c;日本东海岸发生了地震&#xff0c;引发了福岛第一核电站事故&#xff0c;导致三个核反应堆熔毁&#xff0c;并在一场巨大…

[java基础揉碎]基本数据类型转换

介绍 当java程序在进行赋值或者运算时&#xff0c;精度小的类型自动转换为精度大的数据类型&#xff0c; 这个就是自动类型转换。 数据类型按精度&#xff08;容量&#xff09;大小排序为: 自动类型转换注意和细节 1.有多种类型的数据混合运算时&#xff0c;系统首先自动…

如何在浏览器上设置 VPN 网络虚拟专用网络

如何在DT浏览器上设置 VPN 网络 虚拟专用网络&#xff08;VPN&#xff09;是一种用公用网络架设专用网络的技术。如何在DT浏览器上设置 VPN 呢&#xff0c;先下载DT浏览器&#xff0c;建议在官方网站下载最新版&#xff0c;安装&#xff0c;在DT浏览器首页点更多&#xff0c;网…

php基础学习之变量

php使用变量的必要性 PHP 是一种动态网站开发的脚本语言&#xff0c;动态语言特点是交互性&#xff0c;会有数据的传递&#xff0c;而 PHP作为“中间人”&#xff0c;需要进行数据的传递&#xff0c;传递的前提就是 PHP 能自己存储数据(临时存储) php变量的使用 定义&#xf…

HTML以及CSS相关知识总结(一)

近日就开始回顾html和css相关知识啦&#xff0c;并且会学习html5和css3的新知识&#xff0c;以下是我对记忆不太深刻的地方以及新知识点的总结&#xff1a; Web标准&#xff1a; 结构&#xff1a;用于对网页元素进行整理和分类&#xff0c;即HTML 表现&#xff1a;用于设置网页…

使用 Node 创建 Web 服务器

Node.js 提供了 http 模块&#xff0c;http 模块主要用于搭建 HTTP 服务端和客户端&#xff0c;使用 HTTP 服务器或客户端功能必须调用 http 模块&#xff0c;代码如下&#xff1a; var http require(http); 以下是演示一个最基本的 HTTP 服务器架构(使用 8080 端口)&#x…

Python实现稳健线性回归模型(rlm算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 稳健回归可以用在任何使用最小二乘回归的情况下。在拟合最小二乘回归时&#xff0c;我们可能会发现一些…

python写完程序怎么运行

python有两种运行方式&#xff0c;一种是在python交互式命令行下运行; 另一种是使用文本编辑器直接在命令行上运行。 注&#xff1a;以上两种运行方式均由CPython解释器编译运行。 当然&#xff0c;也可以将python代码写入eclipse中&#xff0c;用JPython解释器运行&#xff0c…

Spring MVC的原理

Spring MVC中的MVC即模型-视图-控制器&#xff0c;该框架围绕一个DispatcherServlet设计而成&#xff0c;DispatcherServlet会把请求分发给各个处理器&#xff0c;并支持可配置的处理器映射和视图渲染等功能。Spring MVC的具体工作流程如下&#xff1a; &#xff08;1&#xff…

微服务不死 — 共享变量在策略引擎项目的落地详解

01 背景 1、共享变量的提出 前段时间&#xff0c;来自亚马逊 Prime Video 团队的一个案例研究在开发者社区中掀起了轩然大波。大体是这样一件事&#xff0c;作为一个流媒体平台&#xff0c;Prime Video每天都会向客户提供成千上万的直播流。为了确保客户无缝接收内容&#xff0…

【动态规划】【数学】【C++算法】805 数组的均值分割

作者推荐 【动态规划】【数学】【C算法】18赛车 本文涉及知识点 动态规划 数学 805 数组的均值分割 给定你一个整数数组 nums 我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中&#xff0c;使得 A 数组和 B 数组不为空&#xff0c;并且 average(A) average(B)…

Java基础(2)

一 String、StringBuffer、StringBuilder String 为什么要设计成不可变的&#xff1f; String是不可变的&#xff08;修改String时&#xff0c;不会在原有的内存地址修改&#xff0c;而是重新指向一个新对象&#xff09;&#xff0c;String用final修饰&#xff0c;不可继承&…

两个bbox的IoU计算步骤分析

IoU&#xff1a;交并比&#xff0c;数值上等于交集面积除以并集面积。 两个bbox的位置关系无外乎以上三种情况&#xff1a;&#xff08;1&#xff09;部分相交。&#xff08;2&#xff09;不相交。&#xff08;3&#xff09;包含。 计算步骤&#xff1a; 计算交集&#xff08…

操作系统-操作系统引导(磁盘 操作系统引导过程)

文章目录 总览一个刚买来的磁盘&#xff08;硬盘&#xff09;往磁盘安装操作系统后操作系统引导过程例&#xff1a;windows操作系统的初始化程序 总览 一个刚买来的磁盘&#xff08;硬盘&#xff09; 此时空空如也 往磁盘安装操作系统后 操作系统在C盘 主引导记录不属于某…

java idea 中的 Scratches and Consoles

IDEA 中&#xff0c;"Scratches and Consoles" 是一个用于临时代码编辑和交互式开发的工具窗口&#xff0c;作用如下&#xff1a;Scratches&#xff08;草稿&#xff09;&#xff1a;Scratches 是一个用于临时编写和运行代码片段的工具&#xff0c;你可以在其中创建临…