From be6c94d33ab080589be1a6d73f135d25318dc870 Mon Sep 17 00:00:00 2001 From: Eobard26 <1527198893@qq.com> Date: Wed, 7 Apr 2021 22:03:08 +0800 Subject: [PATCH] rm .git --- codes/.dockerignore | 1 + codes/.gitignore | 13 + codes/.vscode/settings.json | 3 + codes/Dockerfile | 40 ++ codes/LICENSE | 674 ++++++++++++++++++++ codes/Makefile | 8 + codes/README.md | 2 + codes/bootloader/rustsbi-k210.bin | Bin 0 -> 75068 bytes codes/bootloader/rustsbi-qemu.bin | Bin 0 -> 103288 bytes codes/easy-fs-fuse/Cargo.toml | 12 + codes/easy-fs-fuse/src/main.rs | 258 ++++++++ codes/easy-fs/.gitignore | 3 + codes/easy-fs/Cargo.toml | 12 + codes/easy-fs/src/bitmap.rs | 73 +++ codes/easy-fs/src/block_cache.rs | 134 ++++ codes/easy-fs/src/block_dev.rs | 6 + codes/easy-fs/src/efs.rs | 237 +++++++ codes/easy-fs/src/layout.rs | 610 ++++++++++++++++++ codes/easy-fs/src/lib.rs | 19 + codes/easy-fs/src/vfs.rs | 430 +++++++++++++ codes/os/.cargo/config | 7 + codes/os/Cargo.toml | 24 + codes/os/Makefile | 104 ++++ codes/os/build.rs | 6 + codes/os/src/config.rs | 42 ++ codes/os/src/console.rs | 33 + codes/os/src/drivers/block/mod.rs | 30 + codes/os/src/drivers/block/sdcard.rs | 755 +++++++++++++++++++++++ codes/os/src/drivers/block/virtio_blk.rs | 76 +++ codes/os/src/drivers/mod.rs | 3 + codes/os/src/entry.asm | 12 + codes/os/src/fs/inode.rs | 242 ++++++++ codes/os/src/fs/mod.rs | 17 + codes/os/src/fs/pipe.rs | 168 +++++ codes/os/src/fs/stdio.rs | 47 ++ codes/os/src/fs/util.rs | 5 + codes/os/src/lang_items.rs | 19 + codes/os/src/linker-k210.ld | 50 ++ codes/os/src/linker-qemu.ld | 50 ++ codes/os/src/main.rs | 52 ++ codes/os/src/mm/address.rs | 209 +++++++ codes/os/src/mm/frame_allocator.rs | 133 ++++ codes/os/src/mm/heap_allocator.rs | 45 ++ codes/os/src/mm/memory_set.rs | 359 +++++++++++ codes/os/src/mm/mod.rs | 28 + codes/os/src/mm/page_table.rs | 255 ++++++++ codes/os/src/sbi.rs | 43 ++ codes/os/src/syscall/fs.rs | 116 ++++ codes/os/src/syscall/mod.rs | 39 ++ codes/os/src/syscall/process.rs | 117 ++++ codes/os/src/task/context.rs | 18 + codes/os/src/task/manager.rs | 34 + codes/os/src/task/mod.rs | 92 +++ codes/os/src/task/pid.rs | 105 ++++ codes/os/src/task/processor.rs | 95 +++ codes/os/src/task/switch.S | 37 ++ codes/os/src/task/switch.rs | 8 + codes/os/src/task/task.rs | 245 ++++++++ codes/os/src/timer.rs | 18 + codes/os/src/trap/context.rs | 37 ++ codes/os/src/trap/mod.rs | 117 ++++ codes/os/src/trap/trap.S | 69 +++ codes/rust-toolchain | 1 + codes/user/.cargo/config | 7 + codes/user/.gitignore | 1 + codes/user/Cargo.toml | 11 + codes/user/Makefile | 23 + codes/user/src/bin/cat.rs | 34 + codes/user/src/bin/cmdline_args.rs | 16 + codes/user/src/bin/exit.rs | 29 + codes/user/src/bin/fantastic_text.rs | 44 ++ codes/user/src/bin/filetest_simple.rs | 38 ++ codes/user/src/bin/forktest.rs | 34 + codes/user/src/bin/forktest2.rs | 33 + codes/user/src/bin/forktest_simple.rs | 28 + codes/user/src/bin/forktree.rs | 37 ++ codes/user/src/bin/hello_world.rs | 11 + codes/user/src/bin/initproc.rs | 34 + codes/user/src/bin/matrix.rs | 68 ++ codes/user/src/bin/pipe_large_test.rs | 69 +++ codes/user/src/bin/pipetest.rs | 44 ++ codes/user/src/bin/run_pipe_test.rs | 21 + codes/user/src/bin/sleep.rs | 30 + codes/user/src/bin/sleep_simple.rs | 19 + codes/user/src/bin/stack_overflow.rs | 17 + codes/user/src/bin/user_shell.rs | 138 +++++ codes/user/src/bin/usertests.rs | 40 ++ codes/user/src/bin/yield.rs | 17 + codes/user/src/console.rs | 39 ++ codes/user/src/lang_items.rs | 12 + codes/user/src/lib.rs | 108 ++++ codes/user/src/linker.ld | 29 + codes/user/src/syscall.rs | 79 +++ 93 files changed, 7607 insertions(+) create mode 100644 codes/.dockerignore create mode 100644 codes/.gitignore create mode 100644 codes/.vscode/settings.json create mode 100644 codes/Dockerfile create mode 100644 codes/LICENSE create mode 100644 codes/Makefile create mode 100644 codes/README.md create mode 100755 codes/bootloader/rustsbi-k210.bin create mode 100755 codes/bootloader/rustsbi-qemu.bin create mode 100644 codes/easy-fs-fuse/Cargo.toml create mode 100644 codes/easy-fs-fuse/src/main.rs create mode 100644 codes/easy-fs/.gitignore create mode 100644 codes/easy-fs/Cargo.toml create mode 100644 codes/easy-fs/src/bitmap.rs create mode 100644 codes/easy-fs/src/block_cache.rs create mode 100644 codes/easy-fs/src/block_dev.rs create mode 100644 codes/easy-fs/src/efs.rs create mode 100644 codes/easy-fs/src/layout.rs create mode 100644 codes/easy-fs/src/lib.rs create mode 100644 codes/easy-fs/src/vfs.rs create mode 100644 codes/os/.cargo/config create mode 100644 codes/os/Cargo.toml create mode 100644 codes/os/Makefile create mode 100644 codes/os/build.rs create mode 100644 codes/os/src/config.rs create mode 100644 codes/os/src/console.rs create mode 100644 codes/os/src/drivers/block/mod.rs create mode 100644 codes/os/src/drivers/block/sdcard.rs create mode 100644 codes/os/src/drivers/block/virtio_blk.rs create mode 100644 codes/os/src/drivers/mod.rs create mode 100644 codes/os/src/entry.asm create mode 100644 codes/os/src/fs/inode.rs create mode 100644 codes/os/src/fs/mod.rs create mode 100644 codes/os/src/fs/pipe.rs create mode 100644 codes/os/src/fs/stdio.rs create mode 100644 codes/os/src/fs/util.rs create mode 100644 codes/os/src/lang_items.rs create mode 100644 codes/os/src/linker-k210.ld create mode 100644 codes/os/src/linker-qemu.ld create mode 100644 codes/os/src/main.rs create mode 100644 codes/os/src/mm/address.rs create mode 100644 codes/os/src/mm/frame_allocator.rs create mode 100644 codes/os/src/mm/heap_allocator.rs create mode 100644 codes/os/src/mm/memory_set.rs create mode 100644 codes/os/src/mm/mod.rs create mode 100644 codes/os/src/mm/page_table.rs create mode 100644 codes/os/src/sbi.rs create mode 100644 codes/os/src/syscall/fs.rs create mode 100644 codes/os/src/syscall/mod.rs create mode 100644 codes/os/src/syscall/process.rs create mode 100644 codes/os/src/task/context.rs create mode 100644 codes/os/src/task/manager.rs create mode 100644 codes/os/src/task/mod.rs create mode 100644 codes/os/src/task/pid.rs create mode 100644 codes/os/src/task/processor.rs create mode 100644 codes/os/src/task/switch.S create mode 100644 codes/os/src/task/switch.rs create mode 100644 codes/os/src/task/task.rs create mode 100644 codes/os/src/timer.rs create mode 100644 codes/os/src/trap/context.rs create mode 100644 codes/os/src/trap/mod.rs create mode 100644 codes/os/src/trap/trap.S create mode 100644 codes/rust-toolchain create mode 100644 codes/user/.cargo/config create mode 100644 codes/user/.gitignore create mode 100644 codes/user/Cargo.toml create mode 100644 codes/user/Makefile create mode 100644 codes/user/src/bin/cat.rs create mode 100644 codes/user/src/bin/cmdline_args.rs create mode 100644 codes/user/src/bin/exit.rs create mode 100644 codes/user/src/bin/fantastic_text.rs create mode 100644 codes/user/src/bin/filetest_simple.rs create mode 100644 codes/user/src/bin/forktest.rs create mode 100644 codes/user/src/bin/forktest2.rs create mode 100644 codes/user/src/bin/forktest_simple.rs create mode 100644 codes/user/src/bin/forktree.rs create mode 100644 codes/user/src/bin/hello_world.rs create mode 100644 codes/user/src/bin/initproc.rs create mode 100644 codes/user/src/bin/matrix.rs create mode 100644 codes/user/src/bin/pipe_large_test.rs create mode 100644 codes/user/src/bin/pipetest.rs create mode 100644 codes/user/src/bin/run_pipe_test.rs create mode 100644 codes/user/src/bin/sleep.rs create mode 100644 codes/user/src/bin/sleep_simple.rs create mode 100644 codes/user/src/bin/stack_overflow.rs create mode 100644 codes/user/src/bin/user_shell.rs create mode 100644 codes/user/src/bin/usertests.rs create mode 100644 codes/user/src/bin/yield.rs create mode 100644 codes/user/src/console.rs create mode 100644 codes/user/src/lang_items.rs create mode 100644 codes/user/src/lib.rs create mode 100644 codes/user/src/linker.ld create mode 100644 codes/user/src/syscall.rs diff --git a/codes/.dockerignore b/codes/.dockerignore new file mode 100644 index 00000000..df3359dd --- /dev/null +++ b/codes/.dockerignore @@ -0,0 +1 @@ +*/* \ No newline at end of file diff --git a/codes/.gitignore b/codes/.gitignore new file mode 100644 index 00000000..74b65454 --- /dev/null +++ b/codes/.gitignore @@ -0,0 +1,13 @@ +.idea/* +os/target/* +os/.idea/* +os/src/link_app.S +os/Cargo.lock +user/target/* +user/.idea/* +user/Cargo.lock +easy-fs/Cargo.lock +easy-fs/target/* +easy-fs-fuse/Cargo.lock +easy-fs-fuse/target/* +tools/ diff --git a/codes/.vscode/settings.json b/codes/.vscode/settings.json new file mode 100644 index 00000000..25c729f5 --- /dev/null +++ b/codes/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/bin/python3" +} \ No newline at end of file diff --git a/codes/Dockerfile b/codes/Dockerfile new file mode 100644 index 00000000..ac784bc2 --- /dev/null +++ b/codes/Dockerfile @@ -0,0 +1,40 @@ +FROM ubuntu:18.04 +LABEL maintainer="dinghao188" \ + version="1.1" \ + description="ubuntu 18.04 with tools for tsinghua's rCore-Tutorial-V3" + +#install some deps +RUN set -x \ + && apt-get update \ + && apt-get install -y curl wget autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \ + gawk build-essential bison flex texinfo gperf libtool patchutils bc xz-utils \ + zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev git tmux python3 + +#install rust and qemu +RUN set -x; \ + RUSTUP='/root/rustup.sh' \ + && cd $HOME \ + #install rust + && curl https://sh.rustup.rs -sSf > $RUSTUP && chmod +x $RUSTUP \ + && $RUSTUP -y --default-toolchain nightly --profile minimal \ + + #compile qemu + && wget https://ftp.osuosl.org/pub/blfs/conglomeration/qemu/qemu-5.0.0.tar.xz \ + && tar xvJf qemu-5.0.0.tar.xz \ + && cd qemu-5.0.0 \ + && ./configure --target-list=riscv64-softmmu,riscv64-linux-user \ + && make -j$(nproc) install \ + && cd $HOME && rm -rf qemu-5.0.0 qemu-5.0.0.tar.xz + +#for chinese network +RUN set -x; \ + APT_CONF='/etc/apt/sources.list'; \ + CARGO_CONF='/root/.cargo/config'; \ + BASHRC='/root/.bashrc' \ + && echo 'export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static' >> $BASHRC \ + && echo 'export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup' >> $BASHRC \ + && touch $CARGO_CONF \ + && echo '[source.crates-io]' > $CARGO_CONF \ + && echo "replace-with = 'ustc'" >> $CARGO_CONF \ + && echo '[source.ustc]' >> $CARGO_CONF \ + && echo 'registry = "git://mirrors.ustc.edu.cn/crates.io-index"' >> $CARGO_CONF \ No newline at end of file diff --git a/codes/LICENSE b/codes/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/codes/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/codes/Makefile b/codes/Makefile new file mode 100644 index 00000000..2e339762 --- /dev/null +++ b/codes/Makefile @@ -0,0 +1,8 @@ +DOCKER_NAME ?= dinghao188/rcore-tutorial +.PHONY: docker build_docker + +docker: + docker run --rm -it --mount type=bind,source=$(shell pwd),destination=/mnt ${DOCKER_NAME} + +build_docker: + docker build -t ${DOCKER_NAME} . diff --git a/codes/README.md b/codes/README.md new file mode 100644 index 00000000..dd356b5f --- /dev/null +++ b/codes/README.md @@ -0,0 +1,2 @@ +# rCore-Tutorial-v3 +rCore-Tutorial version 3. \ No newline at end of file diff --git a/codes/bootloader/rustsbi-k210.bin b/codes/bootloader/rustsbi-k210.bin new file mode 100755 index 0000000000000000000000000000000000000000..e696137ab183ea5129e14e86ba980af53ae025d2 GIT binary patch literal 75068 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 literal 0 HcmV?d00001 diff --git a/codes/bootloader/rustsbi-qemu.bin b/codes/bootloader/rustsbi-qemu.bin new file mode 100755 index 0000000000000000000000000000000000000000..5ccab433b2cac6c11e47ae827904839c4275dfa7 GIT binary patch literal 103288 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 literal 0 HcmV?d00001 diff --git a/codes/easy-fs-fuse/Cargo.toml b/codes/easy-fs-fuse/Cargo.toml new file mode 100644 index 00000000..d7437b72 --- /dev/null +++ b/codes/easy-fs-fuse/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "easy-fs-fuse" +version = "0.1.0" +authors = ["Yifan Wu <shinbokuow@163.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "2.33.3" +easy-fs = { path = "../easy-fs" } +rand = "0.8.0" diff --git a/codes/easy-fs-fuse/src/main.rs b/codes/easy-fs-fuse/src/main.rs new file mode 100644 index 00000000..22540a50 --- /dev/null +++ b/codes/easy-fs-fuse/src/main.rs @@ -0,0 +1,258 @@ +use easy_fs::{ + BlockDevice, + EasyFileSystem, + DiskInodeType, +}; +use std::fs::{File, OpenOptions, read_dir}; +use std::io::{Read, Write, Seek, SeekFrom}; +use std::sync::Mutex; +use std::sync::Arc; +use clap::{Arg, App}; + +const BLOCK_SZ: usize = 512; + +struct BlockFile(Mutex<File>); + +impl BlockDevice for BlockFile { + fn read_block(&self, block_id: usize, buf: &mut [u8]) { + let mut file = self.0.lock().unwrap(); + file.seek(SeekFrom::Start((block_id * BLOCK_SZ) as u64)) + .expect("Error when seeking!"); + assert_eq!(file.read(buf).unwrap(), BLOCK_SZ, "Not a complete block!"); + } + + fn write_block(&self, block_id: usize, buf: &[u8]) { + let mut file = self.0.lock().unwrap(); + file.seek(SeekFrom::Start((block_id * BLOCK_SZ) as u64)) + .expect("Error when seeking!"); + assert_eq!(file.write(buf).unwrap(), BLOCK_SZ, "Not a complete block!"); + } +} + +fn main() { + //efs_test(); + easy_fs_pack().expect("Error when packing easy-fs!"); +} + +fn easy_fs_pack() -> std::io::Result<()> { + // clap::matches 用于æ•èŽ·ç”¨æˆ·è¾“å…¥çš„å‚æ•° + // 在makefileä¸ï¼Œå‘½ä»¤ä¸º + // @cd ../easy-fs-fuse && cargo run --release \ + // -- -s ../user/src/bin/ \ + // -t ../user/target/riscv64gc-unknown-none-elf/release/ + // å› æ¤å¾—åˆ°çš„å‚æ•°å°±æ˜¯ä¸¤ä¸ªè·¯å¾„ + let matches = App::new("EasyFileSystem packer") + .arg(Arg::with_name("source") + .short("s") // 对应输入的 -s + .long("source")//对应输入 --source + .takes_value(true) + .help("Executable source dir(with backslash)") + ) + .arg(Arg::with_name("target") + .short("t") + .long("target") + .takes_value(true) + .help("Executable target dir(with backslash)") + ) + .get_matches(); + let src_path = matches.value_of("source").unwrap(); + let target_path = matches.value_of("target").unwrap(); + println!("src_path = {}\ntarget_path = {}", src_path, target_path); + + // 创建fs.img + let block_file = Arc::new(BlockFile(Mutex::new({ + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(format!("{}{}", target_path, "fs.img"))?; + f.set_len(8192 * 512).unwrap(); + f + }))); + + // 4MiB, at most 4095 files + // 512*8 = 4095 * file_inode + 1 * root_inode + let efs = EasyFileSystem::create( + block_file.clone(), + 8192,//åªæœ‰4MB。。。 + 1, + ); + let root_inode = Arc::new(EasyFileSystem::get_inode(&efs,0)); + + // 从host获å–应用å + let apps: Vec<_> = read_dir(src_path) + .unwrap() + .into_iter() + .map(|dir_entry| { + let mut name_with_ext = dir_entry.unwrap().file_name().into_string().unwrap(); + // 丢弃åŽç¼€ 从'.'到末尾(len-1) + name_with_ext.drain(name_with_ext.find('.').unwrap()..name_with_ext.len()); + name_with_ext + }) + .collect(); + for app in apps { + // load app data from host file system + let mut host_file = File::open(format!("{}{}", target_path, app)).unwrap(); + let mut all_data: Vec<u8> = Vec::new(); + host_file.read_to_end(&mut all_data).unwrap(); + // create a file in easy-fs + let inode = root_inode.create(app.as_str(), DiskInodeType::File).unwrap(); + // write data to easy-fs + inode.write_at(0, all_data.as_slice()); + } + // list apps + + for app in root_inode.ls() { + println!("{}", app.0); + } + Ok(()) +} + +macro_rules! color_text { + ($text:expr, $color:expr) => {{ + format_args!("\x1b[{}m{}\x1b[0m", $color, $text) + }}; +} + +#[test] +fn efs_test() -> std::io::Result<()> { + let block_file = Arc::new(BlockFile(Mutex::new({ + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open("target/fs.img")?; + f.set_len(8192 * 512).unwrap(); + f + }))); + EasyFileSystem::create( + block_file.clone(), + 4096, + 1, + ); + let efs = EasyFileSystem::open(block_file.clone()); + println!("freeblk = {}", efs.lock().free_blocks()); + println!("freeinode = {}", efs.lock().free_inodes()); + let root_inode = EasyFileSystem::get_inode(&efs,0); + root_inode.create("filea",DiskInodeType::File); + root_inode.create("fileb",DiskInodeType::File); + for name in root_inode.ls() { + println!("{}", name.0); + } + let (filea,_) = root_inode.find_path(vec!["filea"]).unwrap(); + let greet_str = "Hello, world!"; + filea.write_at(0, greet_str.as_bytes()); + // let mut buffer = [0u8; 512]; + let mut buffer = [0u8; 256]; + let len = filea.read_at(0, &mut buffer); + assert_eq!( + greet_str, + core::str::from_utf8(&buffer[..len]).unwrap(), + ); + + // TODO:目录功能测试 + // 0.1 æ ¹ç›®å½•ä¸‹æ–‡ä»¶åˆ é™¤ + + // 1.1 æ ¹ç›®å½•ä¸‹ç›®å½•åˆ›å»º + println!("0: rw in /dir ... start"); + let dira_inode_id = { + println!("0.0: create dir"); + let dira_inode = root_inode.create("dira",DiskInodeType::Directory).unwrap(); + // 1.2 æ ¹ç›®å½•ä¸‹ç›®å½•å†…æ–‡ä»¶åˆ›å»º/读写/åˆ é™¤ + println!("0.1: create file in dir"); + let filec = dira_inode.create("filec",DiskInodeType::File).unwrap(); + println!("0.2: write file. wlen={}", filec.write_at(0, greet_str.as_bytes())); + let len = filec.read_at(0, &mut buffer); + println!("0.3: read file. rlen = {}",len); + assert_eq!( + greet_str, + core::str::from_utf8(&buffer[..len]).unwrap(), + ); + dira_inode.get_id() + // 到这里,filecå’Œdira_inode被释放 + }; + println!("0.4: open dir"); + let dira_inode = Arc::new(EasyFileSystem::get_inode(&efs.clone(), dira_inode_id)); + println!("0.5: read from file"); + let (filec,_) = dira_inode.find_path(vec!["filec"]).unwrap(); + let mut buffer = [0u8; 233]; + let len = filec.read_at(0, &mut buffer); + assert_eq!( + greet_str, + core::str::from_utf8(&buffer[..len]).unwrap(), + ); + // æ‰“å°æ ¹ç›®å½•内容 + println!("list files in root:"); + let file_vec = root_inode.ls(); + + for i in 0..file_vec.len(){ + if file_vec[i].1 == DiskInodeType::File{ + print!("{} ", file_vec[i].0); + } else { + // TODO: 统一é…è‰²ï¼ + print!("{} ", color_text!(file_vec[i].0, 96)); + } + } + println!(""); + println!("0: rw in /dir ... pass"); + + // 1.3 æ ¹ç›®å½•ä¸‹ç›®å½•åˆ é™¤ + + + // 2.1 多级目录创建 + // 2.2 多级目录下文件创建/读写/åˆ é™¤ + // 2.3 å¤šçº§ç›®å½•åˆ é™¤ + + // 3.1 ç›®å½•åˆ‡æ¢æµ‹è¯•: cd ./.. + // 3.2 ç›®å½•åˆ‡æ¢æµ‹è¯•: ç»å¯¹è·¯å¾„ + // 3.3 ç›®å½•åˆ‡æ¢æµ‹è¯•: 相对路径 + + // 4 鲿£’性测试 + // 4.1 å°è¯•æ“作ä¸å˜åœ¨çš„æ–‡ä»¶/目录 + // 4.2 大å°è¶…出é™åˆ¶ + + + // 文件数æ®å—分é…回收测试(superblock/inodeçš„size是å¦åŠæ—¶å¢žå‡) + + // éšæœºå—符串读写测试 + println!("random str rw test ... start"); + let mut random_str_test = |len: usize| { + filea.clear(); + assert_eq!( + filea.read_at(0, &mut buffer), + 0, + ); + let mut str = String::new(); + use rand; + // random digit + for _ in 0..len { + str.push(char::from('0' as u8 + rand::random::<u8>() % 10)); + } + filea.write_at(0, str.as_bytes()); + let mut read_buffer = [0u8; 127]; + let mut offset = 0usize; + let mut read_str = String::new(); + loop { + let len = filea.read_at(offset, &mut read_buffer); + if len == 0 { + break; + } + offset += len; + read_str.push_str( + core::str::from_utf8(&read_buffer[..len]).unwrap() + ); + } + assert_eq!(str, read_str); + }; + + random_str_test(4 * BLOCK_SZ); + random_str_test(8 * BLOCK_SZ + BLOCK_SZ / 2); + random_str_test(100 * BLOCK_SZ); + random_str_test(70 * BLOCK_SZ + BLOCK_SZ / 7); + random_str_test((12 + 128) * BLOCK_SZ); + random_str_test(400 * BLOCK_SZ); + random_str_test(1000 * BLOCK_SZ); + random_str_test(2000 * BLOCK_SZ); + println!("random str rw test ... pass"); + Ok(()) +} \ No newline at end of file diff --git a/codes/easy-fs/.gitignore b/codes/easy-fs/.gitignore new file mode 100644 index 00000000..79f5db68 --- /dev/null +++ b/codes/easy-fs/.gitignore @@ -0,0 +1,3 @@ +.idea/ +target/ +Cargo.lock diff --git a/codes/easy-fs/Cargo.toml b/codes/easy-fs/Cargo.toml new file mode 100644 index 00000000..417de3f6 --- /dev/null +++ b/codes/easy-fs/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "easy-fs" +version = "0.1.0" +authors = ["Yifan Wu <shinbokuow@163.com>","Haochen Gong <1527198893@qq.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +spin = "0.7.0" +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } diff --git a/codes/easy-fs/src/bitmap.rs b/codes/easy-fs/src/bitmap.rs new file mode 100644 index 00000000..6485ac5f --- /dev/null +++ b/codes/easy-fs/src/bitmap.rs @@ -0,0 +1,73 @@ +use alloc::sync::Arc; +use super::{ + BlockDevice, + BLOCK_SZ, + get_block_cache, +}; + +type BitmapBlock = [u64; 64]; + +const BLOCK_BITS: usize = BLOCK_SZ * 8; + +pub struct Bitmap { + start_block_id: usize, + blocks: usize, //å—æ•° +} + +/// Return (block_pos, bits64_pos, inner_pos) +fn decomposition(mut bit: usize) -> (usize, usize, usize) { + let block_pos = bit / BLOCK_BITS; + bit = bit % BLOCK_BITS; + (block_pos, bit / 64, bit % 64) +} + +impl Bitmap { + pub fn new(start_block_id: usize, blocks: usize) -> Self { + Self { + start_block_id, + blocks, + } + } + + pub fn alloc(&self, block_device: &Arc<dyn BlockDevice>) -> Option<usize> { + for block_id in 0..self.blocks { + let pos = get_block_cache( + block_id + self.start_block_id as usize, + Arc::clone(block_device), + ).lock().modify(0, |bitmap_block: &mut BitmapBlock| { + if let Some((bits64_pos, inner_pos)) = bitmap_block + .iter() + .enumerate() + .find(|(_, bits64)| **bits64 != u64::MAX)//找一个有空闲的 + .map(|(bits64_pos, bits64)| { + (bits64_pos, bits64.trailing_ones() as usize) //找到最低的0ä½å¹¶ç½®1 + }) { + // modify cache + bitmap_block[bits64_pos] |= 1u64 << inner_pos; + Some(block_id * BLOCK_BITS + bits64_pos * 64 + inner_pos as usize) + } else { + None + } + }); + if pos.is_some() { + return pos; + } + } + None + } + + pub fn dealloc(&self, block_device: &Arc<dyn BlockDevice>, bit: usize) { + let (block_pos, bits64_pos, inner_pos) = decomposition(bit); + get_block_cache( + block_pos + self.start_block_id, + Arc::clone(block_device) + ).lock().modify(0, |bitmap_block: &mut BitmapBlock| { + assert!(bitmap_block[bits64_pos] & (1u64 << inner_pos) > 0); + bitmap_block[bits64_pos] -= 1u64 << inner_pos; + }); + } + + pub fn maximum(&self) -> usize { + self.blocks * BLOCK_BITS + } +} \ No newline at end of file diff --git a/codes/easy-fs/src/block_cache.rs b/codes/easy-fs/src/block_cache.rs new file mode 100644 index 00000000..37bafe1f --- /dev/null +++ b/codes/easy-fs/src/block_cache.rs @@ -0,0 +1,134 @@ +use super::{ + BLOCK_SZ, + BlockDevice, +}; +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use lazy_static::*; +use spin::Mutex; +use riscv::register::time; + +pub struct BlockCache { + cache: [u8; BLOCK_SZ], + block_id: usize, + block_device: Arc<dyn BlockDevice>, + modified: bool, + time_stamp: usize, +} + +impl BlockCache { + /// Load a new BlockCache from disk. + pub fn new( + block_id: usize, + block_device: Arc<dyn BlockDevice> + ) -> Self { + let mut cache = [0u8; BLOCK_SZ]; + block_device.read_block(block_id, &mut cache); + // TODO: 时间戳 + //let mut time_stamp = time::read(); + let mut time_stamp = 0; + Self { + cache, + block_id, + block_device, + modified: false, + time_stamp, + } + } + + fn addr_of_offset(&self, offset: usize) -> usize { + &self.cache[offset] as *const _ as usize + } + + pub fn get_ref<T>(&self, offset: usize) -> &T where T: Sized { + let type_size = core::mem::size_of::<T>(); + assert!(offset + type_size <= BLOCK_SZ); + let addr = self.addr_of_offset(offset); + unsafe { &*(addr as *const T) } + } + + pub fn get_mut<T>(&mut self, offset: usize) -> &mut T where T: Sized { + let type_size = core::mem::size_of::<T>(); + assert!(offset + type_size <= BLOCK_SZ); + self.modified = true; + let addr = self.addr_of_offset(offset); + unsafe { &mut *(addr as *mut T) } + } + + pub fn read<T, V>(&self, offset: usize, f: impl FnOnce(&T) -> V) -> V { + f(self.get_ref(offset)) + } + + pub fn modify<T, V>(&mut self, offset:usize, f: impl FnOnce(&mut T) -> V) -> V { + f(self.get_mut(offset)) + } + + pub fn sync(&mut self) { + if self.modified { + self.modified = false; + self.block_device.write_block(self.block_id, &self.cache); + } + } +} + +impl Drop for BlockCache { + fn drop(&mut self) { + self.sync() + } +} + +const BLOCK_CACHE_SIZE: usize = 16; + +pub struct BlockCacheManager { + queue: VecDeque<(usize, Arc<Mutex<BlockCache>>)>, +} + +impl BlockCacheManager { + pub fn new() -> Self { + Self { queue: VecDeque::new() } + } + + pub fn get_block_cache( + &mut self, + block_id: usize, + block_device: Arc<dyn BlockDevice>, + ) -> Arc<Mutex<BlockCache>> { + if let Some(pair) = self.queue + .iter() + .find(|pair| pair.0 == block_id) { + Arc::clone(&pair.1) + } else { + // substitute + if self.queue.len() == BLOCK_CACHE_SIZE { + // from front to tail + if let Some((idx, _)) = self.queue + .iter() + .enumerate() + .find(|(_, pair)| Arc::strong_count(&pair.1) == 1) { + self.queue.drain(idx..=idx); + } else { + panic!("Run out of BlockCache!"); + } + } + // load block into mem and push back + let block_cache = Arc::new(Mutex::new( + BlockCache::new(block_id, Arc::clone(&block_device)) + )); + self.queue.push_back((block_id, Arc::clone(&block_cache))); + block_cache + } + } +} + +lazy_static! { + pub static ref BLOCK_CACHE_MANAGER: Mutex<BlockCacheManager> = Mutex::new( + BlockCacheManager::new() + ); +} + +pub fn get_block_cache( + block_id: usize, + block_device: Arc<dyn BlockDevice> +) -> Arc<Mutex<BlockCache>> { + BLOCK_CACHE_MANAGER.lock().get_block_cache(block_id, block_device) +} \ No newline at end of file diff --git a/codes/easy-fs/src/block_dev.rs b/codes/easy-fs/src/block_dev.rs new file mode 100644 index 00000000..7a282751 --- /dev/null +++ b/codes/easy-fs/src/block_dev.rs @@ -0,0 +1,6 @@ +use core::any::Any; + +pub trait BlockDevice : Send + Sync + Any { + fn read_block(&self, block_id: usize, buf: &mut [u8]); + fn write_block(&self, block_id: usize, buf: &[u8]); +} diff --git a/codes/easy-fs/src/efs.rs b/codes/easy-fs/src/efs.rs new file mode 100644 index 00000000..8176078e --- /dev/null +++ b/codes/easy-fs/src/efs.rs @@ -0,0 +1,237 @@ +use alloc::sync::Arc; +use spin::Mutex; +use super::{ + BlockDevice, + Bitmap, + SuperBlock, + DiskInode, + DiskInodeType, + Inode, + get_block_cache, + DIRENT_SZ, + DirEntry, +}; +use crate::BLOCK_SZ; +use alloc::vec::Vec; + +pub struct EasyFileSystem { + pub block_device: Arc<dyn BlockDevice>, + pub inode_bitmap: Bitmap, + pub data_bitmap: Bitmap, + inode_area_start_block: u32, + data_area_start_block: u32, +} + +type DataBlock = [u8; BLOCK_SZ]; + +impl EasyFileSystem { + pub fn create( + block_device: Arc<dyn BlockDevice>, + total_blocks: u32, + inode_bitmap_blocks: u32, + ) -> Arc<Mutex<Self>> { + // calculate block size of areas & create bitmaps + let inode_bitmap = Bitmap::new(1, inode_bitmap_blocks as usize); + let inode_num = inode_bitmap.maximum(); + // å˜æ”¾inodeæ•°ç»„çš„å—æ•°ï¼ˆå‘ä¸Šå¯¹é½ + let inode_area_blocks = + ((inode_num * core::mem::size_of::<DiskInode>() + BLOCK_SZ - 1) / BLOCK_SZ) as u32; + let inode_total_blocks = inode_bitmap_blocks + inode_area_blocks; + let data_total_blocks = total_blocks - 1 - inode_total_blocks; + // æ¯ä¸ªä½å›¾å—对应512*8=4096个数æ®å— + // å› æ¤ç†æƒ³æƒ…况为,4096 * data_bitmap_blocks + data_bitmap_blocks = data_total_blocks + // 需è¦å‘ä¸Šå–æ•´ï¼Œå¦åˆ™æœ‰çš„æ•°æ®å—没有对应的ä½å›¾ï¼Œä¼šè¢«æµªè´¹ï¼ + let data_bitmap_blocks = (data_total_blocks + 4096) / 4097; + let data_area_blocks = data_total_blocks - data_bitmap_blocks; + let data_bitmap = Bitmap::new( + (1 + inode_bitmap_blocks + inode_area_blocks) as usize, + data_bitmap_blocks as usize, + ); + let mut efs = Self { + block_device: Arc::clone(&block_device), + inode_bitmap, + data_bitmap, + inode_area_start_block: 1 + inode_bitmap_blocks, + data_area_start_block: 1 + inode_total_blocks + data_bitmap_blocks, + }; + // clear all blocks + for i in 0..total_blocks { + get_block_cache( + i as usize, + Arc::clone(&block_device) + ) + .lock() + .modify(0, |data_block: &mut DataBlock| { + for byte in data_block.iter_mut() { *byte = 0; } + }); + } + // initialize SuperBlock + get_block_cache(0, Arc::clone(&block_device)) + .lock() + .modify(0, |super_block: &mut SuperBlock| { + super_block.initialize( + total_blocks, + inode_bitmap_blocks, + inode_area_blocks, + data_bitmap_blocks, + data_area_blocks, + inode_num as u32, + data_area_blocks, + ); + }); + // write back immediately + // create a inode for root node "/" + assert_eq!(efs.alloc_inode(), 0); + let (root_inode_block_id, root_inode_offset) = efs.get_disk_inode_pos(0); + get_block_cache( + root_inode_block_id as usize, + Arc::clone(&block_device) + ) + .lock() + .modify(root_inode_offset, |disk_inode: &mut DiskInode| { + disk_inode.initialize(DiskInodeType::Directory); + // DEBUG: åˆ›å»ºæ ¹èŠ‚ç‚¹çš„..å’Œ. + let new_size = 2 * DIRENT_SZ; + // åˆ†é…æ•°æ®å— + let mut v: Vec<u32> = Vec::new(); + for _ in 0..1 { + v.push(efs.alloc_data()); + } + disk_inode.increase_size(new_size as u32, v, &block_device); + // rootçš„../.凿Œ‡å‘自身 + let dirent_self = DirEntry::new(".", DiskInodeType::Directory,0); + let dirent_parent = DirEntry::new("..", DiskInodeType::Directory,0); + disk_inode.write_at( + 0, + dirent_self.as_bytes(), + &block_device + ); + disk_inode.write_at( + DIRENT_SZ, + dirent_parent.as_bytes(), + &block_device + ); + /////// + }); + Arc::new(Mutex::new(efs)) + } + + pub fn open(block_device: Arc<dyn BlockDevice>) -> Arc<Mutex<Self>> { + // read SuperBlock + get_block_cache(0, Arc::clone(&block_device)) + .lock() + .read(0, |super_block: &SuperBlock| { + assert!(super_block.is_valid(), "Error loading EFS! magic = {}", super_block.magic); + let inode_total_blocks = + super_block.inode_bitmap_blocks + super_block.inode_area_blocks; + let efs = Self { + block_device, + inode_bitmap: Bitmap::new( + 1, + super_block.inode_bitmap_blocks as usize + ), + data_bitmap: Bitmap::new( + (1 + inode_total_blocks) as usize, + super_block.data_bitmap_blocks as usize, + ), + inode_area_start_block: 1 + super_block.inode_bitmap_blocks, + data_area_start_block: 1 + inode_total_blocks + super_block.data_bitmap_blocks, + }; + Arc::new(Mutex::new(efs)) + }) + } + + pub fn get_inode( efs: &Arc<Mutex<Self>>, inode_id :u32) -> Inode { + let block_device = Arc::clone(&efs.lock().block_device); + Inode::new( + inode_id, + Arc::clone(efs), + block_device, + ) + } + + pub fn get_disk_inode_pos(&self, inode_id: u32) -> (u32, usize) { + let inode_size = core::mem::size_of::<DiskInode>(); + let inodes_per_block = (BLOCK_SZ / inode_size) as u32; + let block_id = self.inode_area_start_block + inode_id / inodes_per_block; + (block_id, (inode_id % inodes_per_block) as usize * inode_size) + } + + pub fn get_data_block_id(&self, data_block_id: u32) -> u32 { + self.data_area_start_block + data_block_id + } + + // Return ID in the inode area + pub fn alloc_inode(&mut self) -> u32 { + // DEGUB: ä¿®æ”¹è¶…çº§å— + get_block_cache(0, Arc::clone(&self.block_device)) + .lock() + .modify(0, |super_block: &mut SuperBlock| { + super_block.dec_inode(); + }); + self.inode_bitmap.alloc(&self.block_device).unwrap() as u32 + } + + /// Return a block ID not ID in the data area ! + pub fn alloc_data(&mut self) -> u32 { + // DEGUB: ä¿®æ”¹è¶…çº§å— + get_block_cache(0, Arc::clone(&self.block_device)) + .lock() + .modify(0, |super_block: &mut SuperBlock| { + super_block.dec_block(); + }); + self.data_bitmap.alloc(&self.block_device).unwrap() as u32 + self.data_area_start_block + } + + pub fn dealloc_data(&mut self, block_id: u32) { + // DEGUB: ä¿®æ”¹è¶…çº§å— + get_block_cache(0, Arc::clone(&self.block_device)) + .lock() + .modify(0, |super_block: &mut SuperBlock| { + super_block.add_block(); + }); + get_block_cache( + block_id as usize, + Arc::clone(&self.block_device) + ) + .lock() + .modify(0, |data_block: &mut DataBlock| { + data_block.iter_mut().for_each(|p| { *p = 0; }) + }); + self.data_bitmap.dealloc( + &self.block_device, + (block_id - self.data_area_start_block) as usize + ) + } + + pub fn dealloc_inode(&mut self, inode_id: u32) { + // 回收inode + // å› ä¸ºåˆå§‹åŒ–inodeçš„æ—¶å€™ä¼šæ¸…é›¶ï¼Œæ‰€ä»¥è¿™é‡Œæ²¡å¿…è¦æ¸…é›¶ + // DEGUB: ä¿®æ”¹è¶…çº§å— + get_block_cache(0, Arc::clone(&self.block_device)) + .lock() + .modify(0, |super_block: &mut SuperBlock| { + super_block.add_inode(); + }); + self.inode_bitmap.dealloc( + &self.block_device, + inode_id as usize + ); + } + + pub fn free_blocks(&self) -> u32{ + get_block_cache(0, Arc::clone(&self.block_device)) + .lock() + .read(0, |super_block: &SuperBlock| { + super_block.get_free_blocks() + }) + } + + pub fn free_inodes(&self) -> u32{ + get_block_cache(0, Arc::clone(&self.block_device)) + .lock() + .read(0, |super_block: &SuperBlock| { + super_block.get_free_inodes() + }) + } +} \ No newline at end of file diff --git a/codes/easy-fs/src/layout.rs b/codes/easy-fs/src/layout.rs new file mode 100644 index 00000000..814acdfe --- /dev/null +++ b/codes/easy-fs/src/layout.rs @@ -0,0 +1,610 @@ +use core::fmt::{Debug, Formatter, Result}; +use super::{ + BLOCK_SZ, + BlockDevice, + get_block_cache, +}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use riscv::interrupt::free; + +const EFS_MAGIC: u32 = 0x3b800001; +const INODE_DIRECT_COUNT: usize = 28; +pub const NAME_LENGTH_LIMIT: usize = 26;//27 +const INODE_INDIRECT1_COUNT: usize = BLOCK_SZ / 4; +const INODE_INDIRECT2_COUNT: usize = INODE_INDIRECT1_COUNT * INODE_INDIRECT1_COUNT; +const DIRECT_BOUND: usize = INODE_DIRECT_COUNT; +const INDIRECT1_BOUND: usize = DIRECT_BOUND + INODE_INDIRECT1_COUNT; +#[allow(unused)] +const INDIRECT2_BOUND: usize = INDIRECT1_BOUND + INODE_INDIRECT2_COUNT; + +#[repr(C)] +pub struct SuperBlock { + // å¯ç”¨ç©ºé—´ï¼š512B = 128 * u32 + pub magic: u32, + pub total_blocks: u32, + // å„个区域的长度 + pub inode_bitmap_blocks: u32, + pub inode_area_blocks: u32, + pub data_bitmap_blocks: u32, + pub data_area_blocks: u32, + // ä½™é‡ + pub free_inodes: u32, + pub free_blocks: u32, +} + +impl Debug for SuperBlock { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("SuperBlock") + .field("total_blocks", &self.total_blocks) + .field("inode_bitmap_blocks", &self.inode_bitmap_blocks) + .field("inode_area_blocks", &self.inode_area_blocks) + .field("data_bitmap_blocks", &self.data_bitmap_blocks) + .field("data_area_blocks", &self.data_area_blocks) + .finish() + } +} + +impl SuperBlock { + pub fn initialize( + &mut self, + total_blocks: u32, + inode_bitmap_blocks: u32, + inode_area_blocks: u32, + data_bitmap_blocks: u32, + data_area_blocks: u32, + free_inodes: u32, + free_blocks: u32, + ) { + *self = Self { + magic: EFS_MAGIC, + total_blocks, + inode_bitmap_blocks, + inode_area_blocks, + data_bitmap_blocks, + data_area_blocks, + free_inodes, + free_blocks, + } + } + pub fn is_valid(&self) -> bool { + self.magic == EFS_MAGIC + } + + pub fn add_inode(&mut self) { + self.free_inodes += 1; + } + + pub fn dec_inode(&mut self) { + self.free_inodes -= 1; + } + + pub fn add_block(&mut self) { + self.free_blocks += 1; + } + + pub fn dec_block(&mut self) { + self.free_blocks -= 1; + } + + pub fn get_free_inodes(&self) -> u32{ + self.free_inodes + } + + pub fn get_free_blocks(&self) -> u32{ + self.free_blocks + } + +} + +#[derive(PartialEq,Copy,Clone)] +pub enum DiskInodeType { + File, + Directory, +} + +type IndirectBlock = [u32; BLOCK_SZ / 4]; +type DataBlock = [u8; BLOCK_SZ]; + +#[repr(C)] +pub struct DiskInode { + // 最大16540个数æ®å— + // + pub size: u32, // Byte + pub direct: [u32; INODE_DIRECT_COUNT], //28*512 = 14KB + pub indirect1: u32, // 128*512 = 64KB + pub indirect2: u32, // 128*128*512 = 8MB + pub type_: DiskInodeType, +} + +impl DiskInode { + /// indirect1 and indirect2 block are allocated only when they are needed. + pub fn initialize(&mut self, type_: DiskInodeType) { + self.size = 0; + self.direct.iter_mut().for_each(|v| *v = 0); + self.indirect1 = 0; + self.indirect2 = 0; + self.type_ = type_; + } + pub fn is_dir(&self) -> bool { + self.type_ == DiskInodeType::Directory + } + #[allow(unused)] + pub fn is_file(&self) -> bool { + self.type_ == DiskInodeType::File + } + /// Return block number correspond to size. + pub fn data_blocks(&self) -> u32 { + Self::_data_blocks(self.size) + } + fn _data_blocks(size: u32) -> u32 { + (size + BLOCK_SZ as u32 - 1) / BLOCK_SZ as u32 + } + /// Return number of blocks needed include indirect1/2. + pub fn total_blocks(size: u32) -> u32 { + let data_blocks = Self::_data_blocks(size) as usize; + let mut total = data_blocks as usize; + // indirect1 + if data_blocks > INODE_DIRECT_COUNT { + total += 1; + } + // indirect2 + if data_blocks > INDIRECT1_BOUND { + total += 1; + // sub indirect1 (å‘ä¸Šå–æ•´) + total += (data_blocks - INDIRECT1_BOUND + INODE_INDIRECT1_COUNT - 1) / INODE_INDIRECT1_COUNT; + } + total as u32 + } + // 计算扩容至new_size需è¦å¤šå°‘ç´¢å¼•å— + pub fn blocks_num_needed(&self, new_size: u32) -> u32 { + assert!(new_size >= self.size); + Self::total_blocks(new_size) - Self::total_blocks(self.size) + } + pub fn get_block_id(&self, inner_id: u32, block_device: &Arc<dyn BlockDevice>) -> u32 { + let inner_id = inner_id as usize; + if inner_id < INODE_DIRECT_COUNT { + self.direct[inner_id] + } else if inner_id < INDIRECT1_BOUND { + get_block_cache(self.indirect1 as usize, Arc::clone(block_device)) + .lock() + .read(0, |indirect_block: &IndirectBlock| { + indirect_block[inner_id - INODE_DIRECT_COUNT] + }) + } else { + let last = inner_id - INDIRECT1_BOUND; + let indirect1 = get_block_cache( + self.indirect2 as usize, + Arc::clone(block_device) + ) + .lock() + .read(0, |indirect2: &IndirectBlock| { + indirect2[last / INODE_INDIRECT1_COUNT] + }); + get_block_cache( + indirect1 as usize, + Arc::clone(block_device) + ) + .lock() + .read(0, |indirect1: &IndirectBlock| { + indirect1[last % INODE_INDIRECT1_COUNT] + }) + } + } + + pub fn decrease_size(&mut self, new_size: u32, block_device: &Arc<dyn BlockDevice>) -> Option<Vec<u32>>{ + // æŸ¥çœ‹æ˜¯å¦æœ‰å¯ä»¥å›žæ”¶çš„å— + // 有则将å—å·è¿”回 + let mut old_blocks = self.data_blocks(); + self.size = new_size; + let mut current_blocks = self.data_blocks(); + if current_blocks == old_blocks { + return None; + } + let mut blk_vec:Vec<u32> = Vec::new(); + + // 采用和分é…ä¸€æ ·çš„é¡ºåºï¼Œä»Žå‰å¾€åŽï¼Œè¿™æ ·æ•ˆçŽ‡é«˜ + // 回收direct + let mut ltimes = old_blocks.min(INODE_DIRECT_COUNT as u32); + while current_blocks < ltimes { + blk_vec.push(self.direct[current_blocks as usize]); + // self.direct[blocks_needed as usize] = 0; // æ²¡å¿…è¦æ¸…é›¶ + current_blocks += 1; + } + + // 回收indirect1 + if old_blocks > INODE_DIRECT_COUNT as u32 { + // 计算åç§»é‡ + current_blocks -= INODE_DIRECT_COUNT as u32; + old_blocks -= INODE_DIRECT_COUNT as u32; + }else{ + return Some(blk_vec); + } + if current_blocks == 0 { // å›žæ”¶ä¸€çº§é—´æŽ¥å— + blk_vec.push(self.indirect1); + } + get_block_cache( + self.indirect1 as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + ltimes = old_blocks.min(INODE_INDIRECT1_COUNT as u32); + while current_blocks < ltimes { + blk_vec.push(indirect1[current_blocks as usize]); + current_blocks += 1; + } + }); + + // 回收indirect2 + if old_blocks > INODE_INDIRECT1_COUNT as u32 { + old_blocks -= INODE_INDIRECT1_COUNT as u32; + current_blocks -= INODE_INDIRECT1_COUNT as u32; + } else { + return Some(blk_vec); + } + if current_blocks == 0 { // 二级间接å—需è¦å›žæ”¶ + blk_vec.push(self.indirect2); + } + // from (a0, b0) to (a1, b1) + let mut a0 = current_blocks as usize / INODE_INDIRECT1_COUNT; + let mut b0 = current_blocks as usize % INODE_INDIRECT1_COUNT; + let a1 = old_blocks as usize / INODE_INDIRECT1_COUNT; + let b1 = old_blocks as usize % INODE_INDIRECT1_COUNT; + get_block_cache( + self.indirect2 as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0, |indirect2: &mut IndirectBlock| { + while (a0 < a1) || (a0 == a1 && b0 < b1) { + // TODO + if b0 == 0 { // 一级间接å—需è¦å›žæ”¶ + blk_vec.push(indirect2[a0]); + } + get_block_cache( + indirect2[a0] as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0,|indirect1:&mut IndirectBlock|{ + //ä¿®æ”¹ä¸€çº§é—´æŽ¥å— + let ltimes = b1.min(INODE_INDIRECT1_COUNT); + while b0 < ltimes{ + blk_vec.push(indirect1[b0]); + b0 += 1; + } + }); + // TODO + if b0 == INODE_INDIRECT1_COUNT{ + b0 = 0; + } + a0 += 1; + } + }); + Some(blk_vec) + } + + // new_blocks: ä¿å˜æœ¬æ¬¡å®¹é‡æ‰©å……所需è¦å—ç¼–å·çš„å‘é‡ + // ?: new_blocks 究竟是什么结构,里é¢åŒ…å«äº†ä¸ºindirect分é…çš„å—? + // ç»“è®ºï¼šæ ¹æ®blocks_num_needed得到需è¦çš„å—æ•°ä»¥ç¡®å®š + pub fn increase_size( + &mut self, + new_size: u32, + new_blocks: Vec<u32>, + block_device: &Arc<dyn BlockDevice>, + ) { + let mut current_blocks = self.data_blocks(); + self.size = new_size; + let mut total_blocks = self.data_blocks(); + let mut new_blocks = new_blocks.into_iter(); + // fill direct + while current_blocks < total_blocks.min(INODE_DIRECT_COUNT as u32) { + self.direct[current_blocks as usize] = new_blocks.next().unwrap(); + current_blocks += 1; + } + // alloc indirect1 + if total_blocks > INODE_DIRECT_COUNT as u32{ + if current_blocks == INODE_DIRECT_COUNT as u32 { + // 说明indirect1还未被分é…ï¼Œå› æ¤åˆ†é…ä¸€ä¸ªå— + self.indirect1 = new_blocks.next().unwrap(); + } + current_blocks -= INODE_DIRECT_COUNT as u32; + total_blocks -= INODE_DIRECT_COUNT as u32; + } else { + return; + } + // fill indirect1 + get_block_cache( + self.indirect1 as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + while current_blocks < total_blocks.min(INODE_INDIRECT1_COUNT as u32) { + indirect1[current_blocks as usize] = new_blocks.next().unwrap(); + current_blocks += 1; + } + }); + // alloc indirect2 + if total_blocks > INODE_INDIRECT1_COUNT as u32 { + if current_blocks == INODE_INDIRECT1_COUNT as u32 { + self.indirect2 = new_blocks.next().unwrap(); + } + current_blocks -= INODE_INDIRECT1_COUNT as u32; + total_blocks -= INODE_INDIRECT1_COUNT as u32; + } else { + return; + } + // fill indirect2 from (a0, b0) -> (a1, b1) + let mut a0 = current_blocks as usize / INODE_INDIRECT1_COUNT; + let mut b0 = current_blocks as usize % INODE_INDIRECT1_COUNT; + let a1 = total_blocks as usize / INODE_INDIRECT1_COUNT; + let b1 = total_blocks as usize % INODE_INDIRECT1_COUNT; + // alloc low-level indirect1 + get_block_cache( + self.indirect2 as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0, |indirect2: &mut IndirectBlock| { + while (a0 < a1) || (a0 == a1 && b0 < b1) { + if b0 == 0 { + // 先分é…ä¸€ä¸ªä¸€çº§ç´¢å¼•å— + indirect2[a0] = new_blocks.next().unwrap(); + } + // fill current + get_block_cache( + indirect2[a0] as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + //å¡«å†™ä¸€çº§ç´¢å¼•å— + indirect1[b0] = new_blocks.next().unwrap(); + }); + // move to next + b0 += 1; + if b0 == INODE_INDIRECT1_COUNT { + b0 = 0; + a0 += 1; + } + } + }); + } + + /// Clear size to zero and return blocks that should be deallocated. + /// + /// We will clear the block contents to zero later. + pub fn clear_size(&mut self, block_device: &Arc<dyn BlockDevice>) -> Vec<u32> { + let mut v: Vec<u32> = Vec::new(); + let mut data_blocks = self.data_blocks() as usize; + self.size = 0; + + //统计当å‰å›žæ”¶çš„å—æ•° + let mut current_blocks = 0usize; + // direct + while current_blocks < data_blocks.min(INODE_DIRECT_COUNT) { + v.push(self.direct[current_blocks]); + self.direct[current_blocks] = 0; + current_blocks += 1; + } + // indirect1 block + if data_blocks > INODE_DIRECT_COUNT { + v.push(self.indirect1); + data_blocks -= INODE_DIRECT_COUNT; + current_blocks = 0; + } else { + return v; + } + // indirect1 + get_block_cache( + self.indirect1 as usize, + Arc::clone(block_device), + ) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + while current_blocks < data_blocks.min(INODE_INDIRECT1_COUNT) { + v.push(indirect1[current_blocks]); + //indirect1[current_blocks] = 0; + current_blocks += 1; + } + }); + self.indirect1 = 0; + // indirect2 block + if data_blocks > INODE_INDIRECT1_COUNT { + v.push(self.indirect2); + data_blocks -= INODE_INDIRECT1_COUNT; + } else { + return v; + } + // indirect2 + assert!(data_blocks <= INODE_INDIRECT2_COUNT); + let a1 = data_blocks / INODE_INDIRECT1_COUNT; + let b1 = data_blocks % INODE_INDIRECT1_COUNT; + get_block_cache( + self.indirect2 as usize, + Arc::clone(block_device), + ) + .lock() + .modify(0, |indirect2: &mut IndirectBlock| { + // full indirect1 blocks + for i in 0..a1 { + v.push(indirect2[i]); + get_block_cache( + indirect2[i] as usize, + Arc::clone(block_device), + ) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + // å‰å‡ ä¸ªä¸€çº§ç´¢å¼•éƒ½æ˜¯å æ»¡çš„ + // å› æ¤æ¯ä¸ªéƒ½è¦å›žæ”¶INODE_INDIRECT1_COUNTå— + for j in 0..INODE_INDIRECT1_COUNT { + v.push(indirect1[j]); + //indirect1[j] = 0; + } + }); + //indirect2[i] = 0; + } + // last indirect1 block + if b1 > 0 { + // è¿™ç§æƒ…况下,实际是第a1+1ä¸ªä¸€çº§ç´¢å¼•å— + v.push(indirect2[a1]); + get_block_cache( + indirect2[a1] as usize, + Arc::clone(block_device), + ) + .lock() + .modify(0, |indirect1: &mut IndirectBlock| { + for j in 0..b1 { + v.push(indirect1[j]); + //indirect1[j] = 0; + } + }); + //indirect2[a1] = 0; + } + }); + self.indirect2 = 0; + v + } + pub fn read_at( + &self, + offset: usize, + buf: &mut [u8], + block_device: &Arc<dyn BlockDevice>, + ) -> usize { + let mut start = offset; + //println!("size = {}",self.size); + let end = (offset + buf.len()).min(self.size as usize); + if start >= end { + return 0; + } + let mut start_block = start / BLOCK_SZ; + let mut read_size = 0usize; + loop { + // calculate end of current block + // 指文件从头开始到当å‰å—末尾的大å°ï¼ˆå®žé™…上也是å‘ä¸Šå¯¹é½ + // 但是ä¸åŒäºŽå‰é¢ï¼Œè¿™é‡Œæ˜¯å¯¹é½çš„æ˜¯ç´¢å¼• + // 例如å‰é¢ï¼Œsize = 512, åˆ™åº”è¯¥ç®—åœ¨ä¸€ä¸ªå— + // 而这里,å—内地å€èŒƒå›´ä¸º0-511,512å±žäºŽä¸‹ä¸€ä¸ªå— + let mut end_current_block = (start / BLOCK_SZ + 1) * BLOCK_SZ; + end_current_block = end_current_block.min(end); + + // read and update read size + let block_read_size = end_current_block - start; + let dst = &mut buf[read_size..read_size + block_read_size]; + get_block_cache( + self.get_block_id(start_block as u32, block_device) as usize, + Arc::clone(block_device), + ) + .lock() + .read(0, |data_block: &DataBlock| { + let src = &data_block[start % BLOCK_SZ..start % BLOCK_SZ + block_read_size]; + dst.copy_from_slice(src); + }); + read_size += block_read_size; + // move to next block + if end_current_block == end { break; } + start_block += 1; + start = end_current_block; + } + read_size + } + /// File size must be adjusted before! + /// Users should call increase_size for that + pub fn write_at( + &mut self, + offset: usize, + buf: &[u8], + block_device: &Arc<dyn BlockDevice>, + ) -> usize { + let mut start = offset; + let end = (offset + buf.len()).min(self.size as usize); + assert!(start <= end); + let mut start_block = start / BLOCK_SZ; + let mut write_size = 0usize; + loop { + // calculate end of current block + let mut end_current_block = (start / BLOCK_SZ + 1) * BLOCK_SZ; + end_current_block = end_current_block.min(end); + // write and update write size + let block_write_size = end_current_block - start; + get_block_cache( + self.get_block_id(start_block as u32, block_device) as usize, + Arc::clone(block_device) + ) + .lock() + .modify(0, |data_block: &mut DataBlock| { + let src = &buf[write_size..write_size + block_write_size]; + let dst = &mut data_block[start % BLOCK_SZ..start % BLOCK_SZ + block_write_size]; + dst.copy_from_slice(src); + }); + write_size += block_write_size; + // move to next block + if end_current_block == end { break; } + start_block += 1; + start = end_current_block; + } + write_size + } +} + +#[repr(C)] +pub struct DirEntry { + // +1 is for '\0' + // 大å°ä¸º32B + // 规定:如果当å‰ç›®å½•项为空,则name[0] = 0 + // è¿™æ ·æ•ˆçŽ‡æ¯”è¾ƒé«˜ + name: [u8; NAME_LENGTH_LIMIT + 1], + type_: DiskInodeType, + inode_number: u32, +} + +pub const DIRENT_SZ: usize = 32; + +impl DirEntry { + pub fn empty() -> Self { + Self { + name: [0u8; NAME_LENGTH_LIMIT + 1], + type_: DiskInodeType::File, + inode_number: 0, + } + } + pub fn new(name: &str, type_: DiskInodeType, inode_number: u32) -> Self { + let mut bytes = [0u8; NAME_LENGTH_LIMIT + 1]; + &mut bytes[..name.len()].copy_from_slice(name.as_bytes()); + Self { + name: bytes, + type_, + inode_number, + } + } + pub fn as_bytes(&self) -> &[u8] { + unsafe { + core::slice::from_raw_parts( + self as *const _ as usize as *const u8, + DIRENT_SZ, + ) + } + } + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + unsafe { + core::slice::from_raw_parts_mut( + self as *mut _ as usize as *mut u8, + DIRENT_SZ, + ) + } + } + pub fn name(&self) -> &str { + let len = (0usize..).find(|i| self.name[*i] == 0).unwrap(); + core::str::from_utf8(&self.name[..len]).unwrap() + } + + pub fn type_(&self) -> DiskInodeType { + self.type_ + } + + pub fn inode_number(&self) -> u32 { + self.inode_number + } +} \ No newline at end of file diff --git a/codes/easy-fs/src/lib.rs b/codes/easy-fs/src/lib.rs new file mode 100644 index 00000000..e365d9e0 --- /dev/null +++ b/codes/easy-fs/src/lib.rs @@ -0,0 +1,19 @@ +#![no_std] +extern crate alloc; + +mod block_dev; +mod layout; +mod efs; +mod bitmap; +mod vfs; +mod block_cache; + +pub const BLOCK_SZ: usize = 512; +pub use block_dev::BlockDevice; +pub use efs::EasyFileSystem; +pub use vfs::Inode; +pub use layout::DiskInodeType; +pub use layout::NAME_LENGTH_LIMIT; +use layout::*; +use bitmap::Bitmap; +use block_cache::get_block_cache; \ No newline at end of file diff --git a/codes/easy-fs/src/vfs.rs b/codes/easy-fs/src/vfs.rs new file mode 100644 index 00000000..cbbd6b35 --- /dev/null +++ b/codes/easy-fs/src/vfs.rs @@ -0,0 +1,430 @@ +use super::{ + BlockDevice, + DiskInode, + DiskInodeType, + DirEntry, + EasyFileSystem, + DIRENT_SZ, + get_block_cache, +}; +use alloc::sync::Arc; +use alloc::string::String; +use alloc::vec::Vec; +use spin::{Mutex, MutexGuard}; + +#[derive(Clone)] +pub struct Inode { + inode_id: u32, + block_id: usize, + block_offset: usize, //记录inode在ç£ç›˜ä¸Šçš„ä½ç½® + fs: Arc<Mutex<EasyFileSystem>>, + block_device: Arc<dyn BlockDevice>, +} + +impl Inode { + pub fn new( + inode_id: u32, + fs: Arc<Mutex<EasyFileSystem>>, + block_device: Arc<dyn BlockDevice>, + ) -> Self { + let (block_id, block_offset) = fs.lock().get_disk_inode_pos(inode_id); + Self { + inode_id, + block_id: block_id as usize, + block_offset, + fs, + block_device, + } + } + + pub fn get_id(&self) -> u32{ + self.inode_id + } + + pub fn get_fs(&self) -> Arc<Mutex<EasyFileSystem>>{ + self.fs.clone() + } + + fn read_disk_inode<V>(&self, f: impl FnOnce(&DiskInode) -> V) -> V { + get_block_cache( + self.block_id, + Arc::clone(&self.block_device) + ).lock().read(self.block_offset, f) + } + + fn modify_disk_inode<V>(&self, f: impl FnOnce(&mut DiskInode) -> V) -> V { + get_block_cache( + self.block_id, + Arc::clone(&self.block_device) + ).lock().modify(self.block_offset, f) + } + + fn find_inode_id( + &self, + name: &str, + disk_inode: &DiskInode, + ) -> Option<(u32,u32)> { + // 返回inode_id和文件目录项在目录的åç§»é‡ + assert!(disk_inode.is_dir()); + let file_count = (disk_inode.size as usize) / DIRENT_SZ; + let mut dirent = DirEntry::empty(); + for i in 0..file_count { + assert_eq!( + disk_inode.read_at( + DIRENT_SZ * i, + dirent.as_bytes_mut(), + &self.block_device, + ), + DIRENT_SZ, + ); + if dirent.name() == name { + return Some((dirent.inode_number() as u32, i as u32)); + } + } + None + } + + fn find(&self, name: &str) -> Option<(Arc<Inode>,u32)> { + // æœç´¢å½“å‰ç›®å½•下的文件/目录 + let _ = self.fs.lock(); + self.read_disk_inode(|disk_inode| { + // find_inode_idä¼šæ£€æŸ¥æ˜¯å¦æ˜¯ç›®å½• + self.find_inode_id(name, disk_inode)// 获å–Inodeç¼–å· + .map(|(inode_id,offset)| { //转æ¢ä¸ºInode + ( Arc::new(Self::new( + inode_id, + self.fs.clone(), + self.block_device.clone(), + )), + offset + ) + }) + }) + } + + pub fn find_path(&self, path: Vec<&str>) -> Option<(Arc<Inode>, u32)> { + // 返回inode和文件的dirent在目录ä¸çš„åç§» + // æ ¹æ®è·¯å¾„æœç´¢æ–‡ä»¶/目录 + // 调用findé€çº§æœç´¢ + // shell应当ä¿è¯path有内容!!!这里ä¸å¯¹len进行检查以æé«˜æ•ˆçŽ‡ã€‚ + let len = path.len(); + let mut curr_inode:Arc<Inode> = Arc::new(self.clone()); + let mut curr_offset:u32 = 0; + for i in 0 .. len { + if let Some((inode,offset)) = curr_inode.find(path[i]){ + curr_inode = inode; + curr_offset = offset; + }else{ + return None; + } + } + Some((curr_inode.clone(), curr_offset)) + } + + /* + pub fn ch_dir(&self, path: Vec<&str>)->Option<Vec<Arc<Inode>>>{ + // 切æ¢å·¥ä½œç›®å½•时调用,返回路径目录的所有Inode + // QUES: 修改ç–ç•¥åŽï¼Œä¼¼ä¹Žæ²¡ç”¨äº†... + let mut curr_inode:Arc<Inode> = Arc::new(self.clone()); + let mut inode_vec = Vec::new(); + let len = path.len(); + if len == 0 { + return None; + } + for i in 0 .. len { + if let Some(inode) = curr_inode.find(path[i]){ + inode_vec.push(inode.clone()); + curr_inode = inode; + }else{ + return None; + } + } + Some(inode_vec) + } + */ + + fn remove_file(&self)->bool{ + // åˆ é™¤å½“å‰Inode对应的文件 + // 清空文件内容 + self.clear(); + // 释放inode + let mut fs = self.fs.lock(); + fs.dealloc_inode(self.inode_id); + true + } + + fn remove_dir(&self)->bool{ + // é€’å½’åˆ é™¤å½“å‰Inode对应的目录 + // DEBUG + self.read_disk_inode(|disk_inode| { + assert!(disk_inode.is_dir()); + let file_count = (disk_inode.size as usize) / DIRENT_SZ; + let mut dirent = DirEntry::empty(); + // åˆ é™¤ç›®å½•ä¸‹çš„æ¯ä¸€é¡¹ + for i in 0..file_count { + assert_eq!( + disk_inode.read_at( + DIRENT_SZ * i, + dirent.as_bytes_mut(), + &self.block_device, + ), + DIRENT_SZ, + ); + let temp_inode = Inode::new( + dirent.inode_number(), + self.fs.clone(), + self.block_device.clone() + ); + let type_:DiskInodeType = temp_inode.read_disk_inode( + |disk_inode|{ + disk_inode.type_ + } + ); + let result:bool; + // æ ¹æ®ç±»åˆ«åˆ 除 + if type_ == DiskInodeType::File{ + result = temp_inode.remove_file(); + } else if type_ == DiskInodeType::Directory { + result = temp_inode.remove_dir(); + } else { + return false; + } + if result == false{ + return false; + } + } + // 清除所有目录项 + self.clear(); + // 释放inode + let mut fs = self.fs.lock(); + fs.dealloc_inode(self.inode_id); + true + }) + } + + pub fn remove(&self, mut path: Vec<&str>, type_: DiskInodeType)->bool{ + // åˆ é™¤æ–‡ä»¶/目录 + let result:bool; + let name = path.pop().unwrap(); + // 获å–上级目录 + if let Some((par_inode, offset)) = self.find_path(path){ + // æœç´¢ç›®æ ‡æ–‡ä»¶/目录 + if let Some((tar_inode,_)) = par_inode.find(name){ + if type_ == DiskInodeType::File{ + result = tar_inode.remove_file(); + } else if type_ == DiskInodeType::Directory{ + result = tar_inode.remove_dir(); + } else { + return false; + } + if result == false { + return false; + } + } else{ + return false; + } + // DEBUG: ä¿®æ”¹ç›®å½•é¡¹ï¼Œè°ƒæ•´ç›®å½•å¤§å° + // 对fs上é”,é¿å…ä¿®æ”¹é€”ä¸æœ‰è¿›ç¨‹å¯¹å…¶æ“作 + let mut fs = self.fs.lock(); + // ä¿®æ”¹ç›®å½•é¡¹ï¼Œè°ƒæ•´ç›®å½•å¤§å° + self.modify_disk_inode(|curr_inode| { + // append file in the dirent + let file_count = (curr_inode.size as usize) / DIRENT_SZ; + let new_size = (file_count - 1) * DIRENT_SZ; + // 移动目录项 + let mut dirent = DirEntry::empty(); + // 读出最åŽä¸€ä¸ªç›®å½•项 + curr_inode.read_at( + new_size, + dirent.as_bytes_mut(), + &self.block_device, + ); + // å†™å…¥è¢«åˆ é™¤çš„ä½ç½® + curr_inode.write_at( + offset as usize, + dirent.as_bytes(), + &self.block_device, + ); + self.decrease_size(new_size as u32, curr_inode, &mut fs); + }); + return result; + }else{ + false + } + } + + + fn decrease_size( + &self, + new_size: u32, + disk_inode: &mut DiskInode, + fs: &mut MutexGuard<EasyFileSystem>, + ){ + // TODO: åˆ é™¤æ–‡ä»¶åŽæ£€æµ‹æ˜¯å¦éœ€è¦å›žæ”¶å— + // TODO: 修改文件åŽä¹Ÿè¦æ£€æµ‹æ˜¯å¦éœ€è¦å›žæ”¶å— + // å› ä¸ºæ— æ°”æ³¡åŽ‹ç¼©åˆ é™¤çš„è®¾è®¡ï¼Œç›®å½•çš„å¤§å°å¾ˆå¥½åˆ¤æ– + if new_size > disk_inode.size { + return; + } + // disk_inode释放ä¸éœ€è¦çš„å— + if let Some(blk_vec) = disk_inode.decrease_size(new_size, &self.block_device){ + // æŽ§åˆ¶æ–‡ä»¶ç³»ç»Ÿå›žæ”¶å— + for block_id in blk_vec { + fs.dealloc_data(block_id); + } + } + } + + fn increase_size( + &self, + new_size: u32, + disk_inode: &mut DiskInode, + fs: &mut MutexGuard<EasyFileSystem>, + ) { + if new_size < disk_inode.size { + //println!("new_size < disk_inode.size"); + return; + } + let blocks_needed = disk_inode.blocks_num_needed(new_size); + let mut v: Vec<u32> = Vec::new(); + for _ in 0..blocks_needed { + v.push(fs.alloc_data()); + } + disk_inode.increase_size(new_size, v, &self.block_device); + } + + pub fn create(&self, name: &str , type_: DiskInodeType) -> Option<Arc<Inode>> { + let mut fs = self.fs.lock(); + if self.modify_disk_inode(|curr_inode| { + // assert it is a directory + assert!(curr_inode.is_dir()); + // has the file been created? + // 文件和目录也ä¸èƒ½åŒåï¼ + self.find_inode_id(name, curr_inode) + }).is_some() { + return None; + } + // create a new file + // alloc a inode with an indirect block + let new_inode_id = fs.alloc_inode(); + // initialize inode + let (new_inode_block_id, new_inode_block_offset) + = fs.get_disk_inode_pos(new_inode_id); + get_block_cache( + new_inode_block_id as usize, + Arc::clone(&self.block_device) + ).lock().modify(new_inode_block_offset, |new_dskinode: &mut DiskInode| { + new_dskinode.initialize(type_.clone()); + // TODO: 为新目录分é…一个å—并简历.å’Œ..两个目录项 + // DEBUG + if type_ == DiskInodeType::Directory { + let new_size = 2 * DIRENT_SZ; + //println!("0.0._"); + // get_inode/new会获å–é” + // let new_inode = EasyFileSystem::get_inode(&self.fs.clone(), new_inode_id); + let (block_id, block_offset) = fs.get_disk_inode_pos(new_inode_id); + let new_inode = Self{ + inode_id: new_inode_id, + block_id: block_id as usize, + block_offset, + fs: self.fs.clone(), + block_device: self.block_device.clone(), + }; + // increase_size自身ä¸ä¼šèŽ·å–é”ï¼Œå› æ¤ä¸ä¼šæ»é” + //println!("0.0.0"); + new_inode.increase_size(new_size as u32, new_dskinode, &mut fs); + let dirent_self = DirEntry::new(".", DiskInodeType::Directory,new_inode_id); + let dirent_parent = DirEntry::new("..", DiskInodeType::Directory,self.get_id()); + //println!("0.0.1"); + new_dskinode.write_at( + 0, + dirent_self.as_bytes(), + &self.block_device + ); + //println!("0.0.2"); + new_dskinode.write_at( + DIRENT_SZ, + dirent_parent.as_bytes(), + &self.block_device + ); + } + }); + + // 对当å‰ç›®å½•进行修改 + self.modify_disk_inode(|curr_inode| { + // append file in the dirent + let file_count = (curr_inode.size as usize) / DIRENT_SZ; + let new_size = (file_count + 1) * DIRENT_SZ; + // increase size + self.increase_size(new_size as u32, curr_inode, &mut fs); + // write dirent + let dirent = DirEntry::new(name, type_,new_inode_id); + curr_inode.write_at( + file_count * DIRENT_SZ, + dirent.as_bytes(), + &self.block_device, + ); + }); + + // release efs lock manually because we will acquire it again in Inode::new + drop(fs); + // return inode + Some(Arc::new(Self::new( + new_inode_id, + self.fs.clone(), + self.block_device.clone(), + ))) + } + + pub fn ls(&self) -> Vec<(String, DiskInodeType)> { + let _ = self.fs.lock(); + self.read_disk_inode(|disk_inode| { + let file_count = (disk_inode.size as usize) / DIRENT_SZ; + let mut v: Vec<(String, DiskInodeType)> = Vec::new(); + //let mut tv: Vec<DiskInodeType> = Vec::new(); + for i in 0..file_count { + let mut dirent = DirEntry::empty(); + assert_eq!( + disk_inode.read_at( + i * DIRENT_SZ, + dirent.as_bytes_mut(), + &self.block_device, + ), + DIRENT_SZ, + ); + // TODO: 获å–类型 + v.push( (String::from(dirent.name()), dirent.type_()) ); + } + v.sort_by(|a, b| a.0.cmp(&b.0)); + v + }) + } + + pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize { + let _ = self.fs.lock(); + self.read_disk_inode(|disk_inode| { + disk_inode.read_at(offset, buf, &self.block_device) + }) + } + + pub fn write_at(&self, offset: usize, buf: &[u8]) -> usize { + let mut fs = self.fs.lock(); + self.modify_disk_inode(|disk_inode| { + //println!("buflen = {}",buf.len()); + self.increase_size((offset + buf.len()) as u32, disk_inode, &mut fs); + disk_inode.write_at(offset, buf, &self.block_device) + }) + } + + pub fn clear(&self) { + let mut fs = self.fs.lock(); + self.modify_disk_inode(|disk_inode| { + let size = disk_inode.size; + let data_blocks_dealloc = disk_inode.clear_size(&self.block_device); + assert!(data_blocks_dealloc.len() == DiskInode::total_blocks(size) as usize); + for data_block in data_blocks_dealloc.into_iter() { + fs.dealloc_data(data_block); + } + }); + } +} diff --git a/codes/os/.cargo/config b/codes/os/.cargo/config new file mode 100644 index 00000000..4275fcad --- /dev/null +++ b/codes/os/.cargo/config @@ -0,0 +1,7 @@ +[build] +target = "riscv64gc-unknown-none-elf" + +[target.riscv64gc-unknown-none-elf] +rustflags = [ + "-Clink-arg=-Tsrc/linker.ld", "-Cforce-frame-pointers=yes" +] diff --git a/codes/os/Cargo.toml b/codes/os/Cargo.toml new file mode 100644 index 00000000..bded6409 --- /dev/null +++ b/codes/os/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "os" +version = "0.1.0" +authors = ["Yifan Wu <shinbokuow@163.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +buddy_system_allocator = "0.6" +spin = "0.7.0" +bitflags = "1.2.1" +xmas-elf = "0.7.0" +virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers" } +k210-pac = { git = "https://github.com/wyfcyx/k210-pac" } +k210-hal = { git = "https://github.com/wyfcyx/k210-hal" } +k210-soc = { git = "https://github.com/wyfcyx/k210-soc" } +easy-fs = { path = "../easy-fs" } + +[features] +board_qemu = [] +board_k210 = [] \ No newline at end of file diff --git a/codes/os/Makefile b/codes/os/Makefile new file mode 100644 index 00000000..96b542f4 --- /dev/null +++ b/codes/os/Makefile @@ -0,0 +1,104 @@ +# Building +TARGET := riscv64gc-unknown-none-elf +MODE := release +KERNEL_ELF := target/$(TARGET)/$(MODE)/os +KERNEL_BIN := $(KERNEL_ELF).bin +DISASM_TMP := target/$(TARGET)/$(MODE)/asm +FS_IMG := ../user/target/$(TARGET)/$(MODE)/fs.img +SDCARD := /dev/sdb +APPS := ../user/src/bin/* + +# BOARD +BOARD ?= qemu +SBI ?= rustsbi +BOOTLOADER := ../bootloader/$(SBI)-$(BOARD).bin +K210_BOOTLOADER_SIZE := 131072 + +# KERNEL ENTRY +ifeq ($(BOARD), qemu) + KERNEL_ENTRY_PA := 0x80200000 +else ifeq ($(BOARD), k210) + KERNEL_ENTRY_PA := 0x80020000 +endif + +# Run K210 +K210-SERIALPORT = /dev/ttyUSB0 +K210-BURNER = ../tools/kflash.py/kflash.py + +# Binutils +OBJDUMP := rust-objdump --arch-name=riscv64 +OBJCOPY := rust-objcopy --binary-architecture=riscv64 + +# Disassembly +DISASM ?= -x + +build: env $(KERNEL_BIN) $(FS_IMG) + +env: + (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET) + cargo install cargo-binutils + rustup component add rust-src + rustup component add llvm-tools-preview + +# dev/zero永远输出0 +sdcard: $(FS_IMG) + @echo "Are you sure write to $(SDCARD) ? [y/N] " && read ans && [ $${ans:-N} = y ] + @sudo dd if=/dev/zero of=$(SDCARD) bs=1048576 count=16 + @sudo dd if=$(FS_IMG) of=$(SDCARD) + +$(KERNEL_BIN): kernel + @$(OBJCOPY) $(KERNEL_ELF) --strip-all -O binary $@ + +$(FS_IMG): $(APPS) + @cd ../user && make build + @cd ../easy-fs-fuse && cargo run --release -- -s ../user/src/bin/ -t ../user/target/riscv64gc-unknown-none-elf/release/ + +$(APPS): + +kernel: + @echo Platform: $(BOARD) + @cp src/linker-$(BOARD).ld src/linker.ld + @cargo build --release --features "board_$(BOARD)" + @rm src/linker.ld + +clean: + @cargo clean + +disasm: kernel + @$(OBJDUMP) $(DISASM) $(KERNEL_ELF) | less + +disasm-vim: kernel + @$(OBJDUMP) $(DISASM) $(KERNEL_ELF) > $(DISASM_TMP) + @vim $(DISASM_TMP) + @rm $(DISASM_TMP) + +run: run-inner + + + +run-inner: build +ifeq ($(BOARD),qemu) + @qemu-system-riscv64 \ + -machine virt \ + -nographic \ + -bios $(BOOTLOADER) \ + -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ + -drive file=$(FS_IMG),if=none,format=raw,id=x0 \ + -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +else + (which $(K210-BURNER)) || (cd .. && git clone https://github.com/sipeed/kflash.py.git && mv kflash.py tools) + @cp $(BOOTLOADER) $(BOOTLOADER).copy + @dd if=$(KERNEL_BIN) of=$(BOOTLOADER).copy bs=$(K210_BOOTLOADER_SIZE) seek=1 + @mv $(BOOTLOADER).copy $(KERNEL_BIN) + @sudo chmod 777 $(K210-SERIALPORT) + python3 $(K210-BURNER) -p $(K210-SERIALPORT) -b 1500000 $(KERNEL_BIN) + python3 -m serial.tools.miniterm --eol LF --dtr 0 --rts 0 --filter direct $(K210-SERIALPORT) 115200 +endif + +debug: build + @tmux new-session -d \ + "qemu-system-riscv64 -machine virt -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \ + tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ + tmux -2 attach-session -d + +.PHONY: build env kernel clean disasm disasm-vim run-inner diff --git a/codes/os/build.rs b/codes/os/build.rs new file mode 100644 index 00000000..5529b4fe --- /dev/null +++ b/codes/os/build.rs @@ -0,0 +1,6 @@ +static TARGET_PATH: &str = "../user/target/riscv64gc-unknown-none-elf/release/"; + +fn main() { + println!("cargo:rerun-if-changed=../user/src/"); + println!("cargo:rerun-if-changed={}", TARGET_PATH); +} diff --git a/codes/os/src/config.rs b/codes/os/src/config.rs new file mode 100644 index 00000000..dce95ec4 --- /dev/null +++ b/codes/os/src/config.rs @@ -0,0 +1,42 @@ +#[allow(unused)] + +pub const USER_STACK_SIZE: usize = 4096 * 2; +pub const KERNEL_STACK_SIZE: usize = 4096 * 2; +pub const KERNEL_HEAP_SIZE: usize = 0x20_0000; +pub const MEMORY_END: usize = 0x80800000; +pub const PAGE_SIZE: usize = 0x1000; +pub const PAGE_SIZE_BITS: usize = 0xc; + +pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; +pub const TRAP_CONTEXT: usize = TRAMPOLINE - PAGE_SIZE; + +#[cfg(feature = "board_k210")] +pub const CLOCK_FREQ: usize = 403000000 / 62; + +#[cfg(feature = "board_qemu")] +pub const CLOCK_FREQ: usize = 12500000; + +#[cfg(feature = "board_qemu")] +pub const MMIO: &[(usize, usize)] = &[ + (0x10001000, 0x1000), +]; + +#[cfg(feature = "board_k210")] +pub const MMIO: &[(usize, usize)] = &[ + // we don't need clint in S priv when running + // we only need claim/complete for target0 after initializing + (0x0C00_0000, 0x3000), /* PLIC */ + (0x0C20_0000, 0x1000), /* PLIC */ + (0x3800_0000, 0x1000), /* UARTHS */ + (0x3800_1000, 0x1000), /* GPIOHS */ + (0x5020_0000, 0x1000), /* GPIO */ + (0x5024_0000, 0x1000), /* SPI_SLAVE */ + (0x502B_0000, 0x1000), /* FPIOA */ + (0x502D_0000, 0x1000), /* TIMER0 */ + (0x502E_0000, 0x1000), /* TIMER1 */ + (0x502F_0000, 0x1000), /* TIMER2 */ + (0x5044_0000, 0x1000), /* SYSCTL */ + (0x5200_0000, 0x1000), /* SPI0 */ + (0x5300_0000, 0x1000), /* SPI1 */ + (0x5400_0000, 0x1000), /* SPI2 */ +]; \ No newline at end of file diff --git a/codes/os/src/console.rs b/codes/os/src/console.rs new file mode 100644 index 00000000..2bd55930 --- /dev/null +++ b/codes/os/src/console.rs @@ -0,0 +1,33 @@ +use core::fmt::{self, Write}; +use crate::sbi::console_putchar; + +struct Stdout; + +impl Write for Stdout { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + console_putchar(c as usize); + } + Ok(()) + } +} + +pub fn print(args: fmt::Arguments) { + Stdout.write_fmt(args).unwrap(); +} + +#[macro_export] +macro_rules! print { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::console::print(format_args!($fmt $(, $($arg)+)?)); + } +} + +#[macro_export] +macro_rules! println { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)); + } +} + + diff --git a/codes/os/src/drivers/block/mod.rs b/codes/os/src/drivers/block/mod.rs new file mode 100644 index 00000000..f79b8b81 --- /dev/null +++ b/codes/os/src/drivers/block/mod.rs @@ -0,0 +1,30 @@ +mod virtio_blk; +mod sdcard; + +use lazy_static::*; +use alloc::sync::Arc; +use easy_fs::BlockDevice; + +#[cfg(feature = "board_qemu")] +type BlockDeviceImpl = virtio_blk::VirtIOBlock; + +#[cfg(feature = "board_k210")] +type BlockDeviceImpl = sdcard::SDCardWrapper; + +lazy_static! { + pub static ref BLOCK_DEVICE: Arc<dyn BlockDevice> = Arc::new(BlockDeviceImpl::new()); +} + +#[allow(unused)] +pub fn block_device_test() { + let block_device = BLOCK_DEVICE.clone(); + let mut write_buffer = [0u8; 512]; + let mut read_buffer = [0u8; 512]; + for i in 0..512 { + for byte in write_buffer.iter_mut() { *byte = i as u8; } + block_device.write_block(i as usize, &write_buffer); + block_device.read_block(i as usize, &mut read_buffer); + assert_eq!(write_buffer, read_buffer); + } + println!("block device test passed!"); +} \ No newline at end of file diff --git a/codes/os/src/drivers/block/sdcard.rs b/codes/os/src/drivers/block/sdcard.rs new file mode 100644 index 00000000..17ef1631 --- /dev/null +++ b/codes/os/src/drivers/block/sdcard.rs @@ -0,0 +1,755 @@ +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(unused)] + +use k210_pac::{Peripherals, SPI0}; +use k210_hal::prelude::*; +use k210_soc::{ + //dmac::{dma_channel, DMAC, DMACExt}, + gpio, + gpiohs, + spi::{aitm, frame_format, tmod, work_mode, SPI, SPIExt, SPIImpl}, + fpioa::{self, io}, + sysctl, + sleep::usleep, +}; +use spin::Mutex; +use lazy_static::*; +use super::BlockDevice; +use core::convert::TryInto; + +pub struct SDCard<SPI> { + spi: SPI, + spi_cs: u32, + cs_gpionum: u8, + //dmac: &'a DMAC, + //channel: dma_channel, +} + +/* + * Start Data tokens: + * Tokens (necessary because at nop/idle (and CS active) only 0xff is + * on the data/command line) + */ +/** Data token start byte, Start Single Block Read */ +pub const SD_START_DATA_SINGLE_BLOCK_READ: u8 = 0xFE; +/** Data token start byte, Start Multiple Block Read */ +pub const SD_START_DATA_MULTIPLE_BLOCK_READ: u8 = 0xFE; +/** Data token start byte, Start Single Block Write */ +pub const SD_START_DATA_SINGLE_BLOCK_WRITE: u8 = 0xFE; +/** Data token start byte, Start Multiple Block Write */ +pub const SD_START_DATA_MULTIPLE_BLOCK_WRITE: u8 = 0xFC; + +pub const SEC_LEN: usize = 512; + +/** SD commands */ +#[repr(u8)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[allow(unused)] +pub enum CMD { + /** Software reset */ + CMD0 = 0, + /** Check voltage range (SDC V2) */ + CMD8 = 8, + /** Read CSD register */ + CMD9 = 9, + /** Read CID register */ + CMD10 = 10, + /** Stop to read data */ + CMD12 = 12, + /** Change R/W block size */ + CMD16 = 16, + /** Read block */ + CMD17 = 17, + /** Read multiple blocks */ + CMD18 = 18, + /** Number of blocks to erase (SDC) */ + ACMD23 = 23, + /** Write a block */ + CMD24 = 24, + /** Write multiple blocks */ + CMD25 = 25, + /** Initiate initialization process (SDC) */ + ACMD41 = 41, + /** Leading command for ACMD* */ + CMD55 = 55, + /** Read OCR */ + CMD58 = 58, + /** Enable/disable CRC check */ + CMD59 = 59, +} + +#[allow(unused)] +#[derive(Debug, Copy, Clone)] +pub enum InitError { + CMDFailed(CMD, u8), + CardCapacityStatusNotSet([u8; 4]), + CannotGetCardInfo, +} + +/** + * Card Specific Data: CSD Register + */ +#[derive(Debug, Copy, Clone)] +pub struct SDCardCSD { + pub CSDStruct: u8, /* CSD structure */ + pub SysSpecVersion: u8, /* System specification version */ + pub Reserved1: u8, /* Reserved */ + pub TAAC: u8, /* Data read access-time 1 */ + pub NSAC: u8, /* Data read access-time 2 in CLK cycles */ + pub MaxBusClkFrec: u8, /* Max. bus clock frequency */ + pub CardComdClasses: u16, /* Card command classes */ + pub RdBlockLen: u8, /* Max. read data block length */ + pub PartBlockRead: u8, /* Partial blocks for read allowed */ + pub WrBlockMisalign: u8, /* Write block misalignment */ + pub RdBlockMisalign: u8, /* Read block misalignment */ + pub DSRImpl: u8, /* DSR implemented */ + pub Reserved2: u8, /* Reserved */ + pub DeviceSize: u32, /* Device Size */ + //MaxRdCurrentVDDMin: u8, /* Max. read current @ VDD min */ + //MaxRdCurrentVDDMax: u8, /* Max. read current @ VDD max */ + //MaxWrCurrentVDDMin: u8, /* Max. write current @ VDD min */ + //MaxWrCurrentVDDMax: u8, /* Max. write current @ VDD max */ + //DeviceSizeMul: u8, /* Device size multiplier */ + pub EraseGrSize: u8, /* Erase group size */ + pub EraseGrMul: u8, /* Erase group size multiplier */ + pub WrProtectGrSize: u8, /* Write protect group size */ + pub WrProtectGrEnable: u8, /* Write protect group enable */ + pub ManDeflECC: u8, /* Manufacturer default ECC */ + pub WrSpeedFact: u8, /* Write speed factor */ + pub MaxWrBlockLen: u8, /* Max. write data block length */ + pub WriteBlockPaPartial: u8, /* Partial blocks for write allowed */ + pub Reserved3: u8, /* Reserded */ + pub ContentProtectAppli: u8, /* Content protection application */ + pub FileFormatGroup: u8, /* File format group */ + pub CopyFlag: u8, /* Copy flag (OTP) */ + pub PermWrProtect: u8, /* Permanent write protection */ + pub TempWrProtect: u8, /* Temporary write protection */ + pub FileFormat: u8, /* File Format */ + pub ECC: u8, /* ECC code */ + pub CSD_CRC: u8, /* CSD CRC */ + pub Reserved4: u8, /* always 1*/ +} + +/** + * Card Identification Data: CID Register + */ +#[derive(Debug, Copy, Clone)] +pub struct SDCardCID { + pub ManufacturerID: u8, /* ManufacturerID */ + pub OEM_AppliID: u16, /* OEM/Application ID */ + pub ProdName1: u32, /* Product Name part1 */ + pub ProdName2: u8, /* Product Name part2*/ + pub ProdRev: u8, /* Product Revision */ + pub ProdSN: u32, /* Product Serial Number */ + pub Reserved1: u8, /* Reserved1 */ + pub ManufactDate: u16, /* Manufacturing Date */ + pub CID_CRC: u8, /* CID CRC */ + pub Reserved2: u8, /* always 1 */ +} + +/** + * Card information + */ +#[derive(Debug, Copy, Clone)] +pub struct SDCardInfo { + pub SD_csd: SDCardCSD, + pub SD_cid: SDCardCID, + pub CardCapacity: u64, /* Card Capacity */ + pub CardBlockSize: u64, /* Card Block Size */ +} + +impl</*'a,*/ X: SPI> SDCard</*'a,*/ X> { + pub fn new(spi: X, spi_cs: u32, cs_gpionum: u8/*, dmac: &'a DMAC, channel: dma_channel*/) -> Self { + Self { + spi, + spi_cs, + cs_gpionum, + /* + dmac, + channel, + */ + } + } + + fn CS_HIGH(&self) { + gpiohs::set_pin(self.cs_gpionum, true); + } + + fn CS_LOW(&self) { + gpiohs::set_pin(self.cs_gpionum, false); + } + + fn HIGH_SPEED_ENABLE(&self) { + self.spi.set_clk_rate(10000000); + } + + fn lowlevel_init(&self) { + gpiohs::set_direction(self.cs_gpionum, gpio::direction::OUTPUT); + self.spi.set_clk_rate(200000); + } + + fn write_data(&self, data: &[u8]) { + self.spi.configure( + work_mode::MODE0, + frame_format::STANDARD, + 8, /* data bits */ + 0, /* endian */ + 0, /*instruction length*/ + 0, /*address length*/ + 0, /*wait cycles*/ + aitm::STANDARD, + tmod::TRANS, + ); + self.spi.send_data(self.spi_cs, data); + } + + /* + fn write_data_dma(&self, data: &[u32]) { + self.spi.configure( + work_mode::MODE0, + frame_format::STANDARD, + 8, /* data bits */ + 0, /* endian */ + 0, /*instruction length*/ + 0, /*address length*/ + 0, /*wait cycles*/ + aitm::STANDARD, + tmod::TRANS, + ); + self.spi + .send_data_dma(self.dmac, self.channel, self.spi_cs, data); + } + */ + + fn read_data(&self, data: &mut [u8]) { + self.spi.configure( + work_mode::MODE0, + frame_format::STANDARD, + 8, /* data bits */ + 0, /* endian */ + 0, /*instruction length*/ + 0, /*address length*/ + 0, /*wait cycles*/ + aitm::STANDARD, + tmod::RECV, + ); + self.spi.recv_data(self.spi_cs, data); + } + + /* + fn read_data_dma(&self, data: &mut [u32]) { + self.spi.configure( + work_mode::MODE0, + frame_format::STANDARD, + 8, /* data bits */ + 0, /* endian */ + 0, /*instruction length*/ + 0, /*address length*/ + 0, /*wait cycles*/ + aitm::STANDARD, + tmod::RECV, + ); + self.spi + .recv_data_dma(self.dmac, self.channel, self.spi_cs, data); + } + */ + + /* + * Send 5 bytes command to the SD card. + * @param cmd: The user expected command to send to SD card. + * @param arg: The command argument. + * @param crc: The CRC. + * @retval None + */ + fn send_cmd(&self, cmd: CMD, arg: u32, crc: u8) { + /* SD chip select low */ + self.CS_LOW(); + /* Send the Cmd bytes */ + self.write_data(&[ + /* Construct byte 1 */ + ((cmd as u8) | 0x40), + /* Construct byte 2 */ + (arg >> 24) as u8, + /* Construct byte 3 */ + ((arg >> 16) & 0xff) as u8, + /* Construct byte 4 */ + ((arg >> 8) & 0xff) as u8, + /* Construct byte 5 */ + (arg & 0xff) as u8, + /* Construct CRC: byte 6 */ + crc, + ]); + } + + /* Send end-command sequence to SD card */ + fn end_cmd(&self) { + /* SD chip select high */ + self.CS_HIGH(); + /* Send the cmd byte */ + self.write_data(&[0xff]); + } + + /* + * Returns the SD response. + * @param None + * @retval The SD Response: + * - 0xFF: Sequence failed + * - 0: Sequence succeed + */ + fn get_response(&self) -> u8 { + let result = &mut [0u8]; + let mut timeout = 0x0FFF; + /* Check if response is got or a timeout is happen */ + while timeout != 0 { + self.read_data(result); + /* Right response got */ + if result[0] != 0xFF { + return result[0]; + } + timeout -= 1; + } + /* After time out */ + return 0xFF; + } + + /* + * Get SD card data response. + * @param None + * @retval The SD status: Read data response xxx0<status>1 + * - status 010: Data accecpted + * - status 101: Data rejected due to a crc error + * - status 110: Data rejected due to a Write error. + * - status 111: Data rejected due to other error. + */ + fn get_dataresponse(&self) -> u8 { + let response = &mut [0u8]; + /* Read resonse */ + self.read_data(response); + /* Mask unused bits */ + response[0] &= 0x1F; + if response[0] != 0x05 { + return 0xFF; + } + /* Wait null data */ + self.read_data(response); + while response[0] == 0 { + self.read_data(response); + } + /* Return response */ + return 0; + } + + /* + * Read the CSD card register + * Reading the contents of the CSD register in SPI mode is a simple + * read-block transaction. + * @param SD_csd: pointer on an SCD register structure + * @retval The SD Response: + * - `Err()`: Sequence failed + * - `Ok(info)`: Sequence succeed + */ + fn get_csdregister(&self) -> Result<SDCardCSD, ()> { + let mut csd_tab = [0u8; 18]; + /* Send CMD9 (CSD register) */ + self.send_cmd(CMD::CMD9, 0, 0); + /* Wait for response in the R1 format (0x00 is no errors) */ + if self.get_response() != 0x00 { + self.end_cmd(); + return Err(()); + } + if self.get_response() != SD_START_DATA_SINGLE_BLOCK_READ { + self.end_cmd(); + return Err(()); + } + /* Store CSD register value on csd_tab */ + /* Get CRC bytes (not really needed by us, but required by SD) */ + self.read_data(&mut csd_tab); + self.end_cmd(); + /* see also: https://cdn-shop.adafruit.com/datasheets/TS16GUSDHC6.pdf */ + return Ok(SDCardCSD { + /* Byte 0 */ + CSDStruct: (csd_tab[0] & 0xC0) >> 6, + SysSpecVersion: (csd_tab[0] & 0x3C) >> 2, + Reserved1: csd_tab[0] & 0x03, + /* Byte 1 */ + TAAC: csd_tab[1], + /* Byte 2 */ + NSAC: csd_tab[2], + /* Byte 3 */ + MaxBusClkFrec: csd_tab[3], + /* Byte 4, 5 */ + CardComdClasses: (u16::from(csd_tab[4]) << 4) | ((u16::from(csd_tab[5]) & 0xF0) >> 4), + /* Byte 5 */ + RdBlockLen: csd_tab[5] & 0x0F, + /* Byte 6 */ + PartBlockRead: (csd_tab[6] & 0x80) >> 7, + WrBlockMisalign: (csd_tab[6] & 0x40) >> 6, + RdBlockMisalign: (csd_tab[6] & 0x20) >> 5, + DSRImpl: (csd_tab[6] & 0x10) >> 4, + Reserved2: 0, + // DeviceSize: (csd_tab[6] & 0x03) << 10, + /* Byte 7, 8, 9 */ + DeviceSize: ((u32::from(csd_tab[7]) & 0x3F) << 16) + | (u32::from(csd_tab[8]) << 8) + | u32::from(csd_tab[9]), + /* Byte 10 */ + EraseGrSize: (csd_tab[10] & 0x40) >> 6, + /* Byte 10, 11 */ + EraseGrMul: ((csd_tab[10] & 0x3F) << 1) | ((csd_tab[11] & 0x80) >> 7), + /* Byte 11 */ + WrProtectGrSize: (csd_tab[11] & 0x7F), + /* Byte 12 */ + WrProtectGrEnable: (csd_tab[12] & 0x80) >> 7, + ManDeflECC: (csd_tab[12] & 0x60) >> 5, + WrSpeedFact: (csd_tab[12] & 0x1C) >> 2, + /* Byte 12,13 */ + MaxWrBlockLen: ((csd_tab[12] & 0x03) << 2) | ((csd_tab[13] & 0xC0) >> 6), + /* Byte 13 */ + WriteBlockPaPartial: (csd_tab[13] & 0x20) >> 5, + Reserved3: 0, + ContentProtectAppli: (csd_tab[13] & 0x01), + /* Byte 14 */ + FileFormatGroup: (csd_tab[14] & 0x80) >> 7, + CopyFlag: (csd_tab[14] & 0x40) >> 6, + PermWrProtect: (csd_tab[14] & 0x20) >> 5, + TempWrProtect: (csd_tab[14] & 0x10) >> 4, + FileFormat: (csd_tab[14] & 0x0C) >> 2, + ECC: (csd_tab[14] & 0x03), + /* Byte 15 */ + CSD_CRC: (csd_tab[15] & 0xFE) >> 1, + Reserved4: 1, + /* Return the reponse */ + }); + } + + /* + * Read the CID card register. + * Reading the contents of the CID register in SPI mode is a simple + * read-block transaction. + * @param SD_cid: pointer on an CID register structure + * @retval The SD Response: + * - `Err()`: Sequence failed + * - `Ok(info)`: Sequence succeed + */ + fn get_cidregister(&self) -> Result<SDCardCID, ()> { + let mut cid_tab = [0u8; 18]; + /* Send CMD10 (CID register) */ + self.send_cmd(CMD::CMD10, 0, 0); + /* Wait for response in the R1 format (0x00 is no errors) */ + if self.get_response() != 0x00 { + self.end_cmd(); + return Err(()); + } + if self.get_response() != SD_START_DATA_SINGLE_BLOCK_READ { + self.end_cmd(); + return Err(()); + } + /* Store CID register value on cid_tab */ + /* Get CRC bytes (not really needed by us, but required by SD) */ + self.read_data(&mut cid_tab); + self.end_cmd(); + return Ok(SDCardCID { + /* Byte 0 */ + ManufacturerID: cid_tab[0], + /* Byte 1, 2 */ + OEM_AppliID: (u16::from(cid_tab[1]) << 8) | u16::from(cid_tab[2]), + /* Byte 3, 4, 5, 6 */ + ProdName1: (u32::from(cid_tab[3]) << 24) + | (u32::from(cid_tab[4]) << 16) + | (u32::from(cid_tab[5]) << 8) + | u32::from(cid_tab[6]), + /* Byte 7 */ + ProdName2: cid_tab[7], + /* Byte 8 */ + ProdRev: cid_tab[8], + /* Byte 9, 10, 11, 12 */ + ProdSN: (u32::from(cid_tab[9]) << 24) + | (u32::from(cid_tab[10]) << 16) + | (u32::from(cid_tab[11]) << 8) + | u32::from(cid_tab[12]), + /* Byte 13, 14 */ + Reserved1: (cid_tab[13] & 0xF0) >> 4, + ManufactDate: ((u16::from(cid_tab[13]) & 0x0F) << 8) | u16::from(cid_tab[14]), + /* Byte 15 */ + CID_CRC: (cid_tab[15] & 0xFE) >> 1, + Reserved2: 1, + }); + } + + /* + * Returns information about specific card. + * @param cardinfo: pointer to a SD_CardInfo structure that contains all SD + * card information. + * @retval The SD Response: + * - `Err(())`: Sequence failed + * - `Ok(info)`: Sequence succeed + */ + fn get_cardinfo(&self) -> Result<SDCardInfo, ()> { + let mut info = SDCardInfo { + SD_csd: self.get_csdregister()?, + SD_cid: self.get_cidregister()?, + CardCapacity: 0, + CardBlockSize: 0, + }; + info.CardBlockSize = 1 << u64::from(info.SD_csd.RdBlockLen); + info.CardCapacity = (u64::from(info.SD_csd.DeviceSize) + 1) * 1024 * info.CardBlockSize; + + Ok(info) + } + + /* + * Initializes the SD/SD communication in SPI mode. + * @param None + * @retval The SD Response info if succeeeded, otherwise Err + */ + pub fn init(&self) -> Result<SDCardInfo, InitError> { + /* Initialize SD_SPI */ + self.lowlevel_init(); + /* SD chip select high */ + self.CS_HIGH(); + /* NOTE: this reset doesn't always seem to work if the SD access was broken off in the + * middle of an operation: CMDFailed(CMD0, 127). */ + + /* Send dummy byte 0xFF, 10 times with CS high */ + /* Rise CS and MOSI for 80 clocks cycles */ + /* Send dummy byte 0xFF */ + self.write_data(&[0xff; 10]); + /*------------Put SD in SPI mode--------------*/ + /* SD initialized and set to SPI mode properly */ + + /* Send software reset */ + self.send_cmd(CMD::CMD0, 0, 0x95); + let result = self.get_response(); + self.end_cmd(); + if result != 0x01 { + return Err(InitError::CMDFailed(CMD::CMD0, result)); + } + + /* Check voltage range */ + self.send_cmd(CMD::CMD8, 0x01AA, 0x87); + /* 0x01 or 0x05 */ + let result = self.get_response(); + let mut frame = [0u8; 4]; + self.read_data(&mut frame); + self.end_cmd(); + if result != 0x01 { + return Err(InitError::CMDFailed(CMD::CMD8, result)); + } + let mut index = 255; + while index != 0 { + /* <ACMD> */ + self.send_cmd(CMD::CMD55, 0, 0); + let result = self.get_response(); + self.end_cmd(); + if result != 0x01 { + return Err(InitError::CMDFailed(CMD::CMD55, result)); + } + /* Initiate SDC initialization process */ + self.send_cmd(CMD::ACMD41, 0x40000000, 0); + let result = self.get_response(); + self.end_cmd(); + if result == 0x00 { + break; + } + index -= 1; + } + if index == 0 { + return Err(InitError::CMDFailed(CMD::ACMD41, result)); + } + index = 255; + let mut frame = [0u8; 4]; + while index != 0 { + /* Read OCR */ + self.send_cmd(CMD::CMD58, 0, 1); + let result = self.get_response(); + self.read_data(&mut frame); + self.end_cmd(); + if result == 0 { + break; + } + index -= 1; + } + if index == 0 { + return Err(InitError::CMDFailed(CMD::CMD58, result)); + } + if (frame[0] & 0x40) == 0 { + return Err(InitError::CardCapacityStatusNotSet(frame)); + } + self.HIGH_SPEED_ENABLE(); + self.get_cardinfo() + .map_err(|_| InitError::CannotGetCardInfo) + } + + /* + * Reads a block of data from the SD. + * @param data_buf: slice that receives the data read from the SD. + * @param sector: SD's internal address to read from. + * @retval The SD Response: + * - `Err(())`: Sequence failed + * - `Ok(())`: Sequence succeed + */ + pub fn read_sector(&self, data_buf: &mut [u8], sector: u32) -> Result<(), ()> { + assert!(data_buf.len() >= SEC_LEN && (data_buf.len() % SEC_LEN) == 0); + /* Send CMD17 to read one block, or CMD18 for multiple */ + let flag = if data_buf.len() == SEC_LEN { + self.send_cmd(CMD::CMD17, sector, 0); + false + } else { + self.send_cmd(CMD::CMD18, sector, 0); + true + }; + /* Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */ + if self.get_response() != 0x00 { + self.end_cmd(); + return Err(()); + } + let mut error = false; + //let mut dma_chunk = [0u32; SEC_LEN]; + let mut tmp_chunk= [0u8; SEC_LEN]; + for chunk in data_buf.chunks_mut(SEC_LEN) { + if self.get_response() != SD_START_DATA_SINGLE_BLOCK_READ { + error = true; + break; + } + /* Read the SD block data : read NumByteToRead data */ + //self.read_data_dma(&mut dma_chunk); + //*å¯ä¼˜åŒ– + self.read_data(&mut tmp_chunk); + /* Place the data received as u32 units from DMA into the u8 target buffer */ + for (a, b) in chunk.iter_mut().zip(/*dma_chunk*/tmp_chunk.iter()) { + //*a = (b & 0xff) as u8; + *a = *b; + } + /* Get CRC bytes (not really needed by us, but required by SD) */ + let mut frame = [0u8; 2]; + self.read_data(&mut frame); + } + self.end_cmd(); + if flag { + self.send_cmd(CMD::CMD12, 0, 0); + self.get_response(); + self.end_cmd(); + self.end_cmd(); + } + /* It is an error if not everything requested was read */ + if error { + Err(()) + } else { + Ok(()) + } + } + + /* + * Writes a block to the SD + * @param data_buf: slice containing the data to be written to the SD. + * @param sector: address to write on. + * @retval The SD Response: + * - `Err(())`: Sequence failed + * - `Ok(())`: Sequence succeed + */ + pub fn write_sector(&self, data_buf: &[u8], sector: u32) -> Result<(), ()> { + assert!(data_buf.len() >= SEC_LEN && (data_buf.len() % SEC_LEN) == 0); + let mut frame = [0xff, 0x00]; + if data_buf.len() == SEC_LEN { + frame[1] = SD_START_DATA_SINGLE_BLOCK_WRITE; + self.send_cmd(CMD::CMD24, sector, 0); + } else { + frame[1] = SD_START_DATA_MULTIPLE_BLOCK_WRITE; + self.send_cmd( + CMD::ACMD23, + (data_buf.len() / SEC_LEN).try_into().unwrap(), + 0, + ); + self.get_response(); + self.end_cmd(); + self.send_cmd(CMD::CMD25, sector, 0); + } + /* Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */ + if self.get_response() != 0x00 { + self.end_cmd(); + return Err(()); + } + //let mut dma_chunk = [0u32; SEC_LEN]; + let mut tmp_chunk = [0u8; SEC_LEN]; + for chunk in data_buf.chunks(SEC_LEN) { + /* Send the data token to signify the start of the data */ + self.write_data(&frame); + /* Write the block data to SD : write count data by block */ + for (a, &b) in /*dma_chunk*/tmp_chunk.iter_mut().zip(chunk.iter()) { + //*a = b.into(); + *a = b; + } + //self.write_data_dma(&mut dma_chunk); + self.write_data(&mut tmp_chunk); + /* Put dummy CRC bytes */ + self.write_data(&[0xff, 0xff]); + /* Read data response */ + if self.get_dataresponse() != 0x00 { + self.end_cmd(); + return Err(()); + } + } + self.end_cmd(); + self.end_cmd(); + Ok(()) + } +} + +/** GPIOHS GPIO number to use for controlling the SD card CS pin */ +const SD_CS_GPIONUM: u8 = 7; +/** CS value passed to SPI controller, this is a dummy value as SPI0_CS3 is not mapping to anything + * in the FPIOA */ +const SD_CS: u32 = 3; + +/** Connect pins to internal functions */ +fn io_init() { + fpioa::set_function(io::SPI0_SCLK, fpioa::function::SPI0_SCLK); + fpioa::set_function(io::SPI0_MOSI, fpioa::function::SPI0_D0); + fpioa::set_function(io::SPI0_MISO, fpioa::function::SPI0_D1); + fpioa::set_function(io::SPI0_CS0, fpioa::function::gpiohs(SD_CS_GPIONUM)); + fpioa::set_io_pull(io::SPI0_CS0, fpioa::pull::DOWN); // GPIO output=pull down +} + +lazy_static! { + static ref PERIPHERALS: Mutex<Peripherals> = Mutex::new(Peripherals::take().unwrap()); +} + +fn init_sdcard() -> SDCard<SPIImpl<SPI0>> { + // wait previous output + usleep(100000); + let peripherals = unsafe { Peripherals::steal() }; + sysctl::pll_set_freq(sysctl::pll::PLL0, 800_000_000).unwrap(); + sysctl::pll_set_freq(sysctl::pll::PLL1, 300_000_000).unwrap(); + sysctl::pll_set_freq(sysctl::pll::PLL2, 45_158_400).unwrap(); + let clocks = k210_hal::clock::Clocks::new(); + peripherals.UARTHS.configure(115_200.bps(), &clocks); + io_init(); + + let spi = peripherals.SPI0.constrain(); + let sd = SDCard::new(spi, SD_CS, SD_CS_GPIONUM); + let info = sd.init().unwrap(); + let num_sectors = info.CardCapacity / 512; + assert!(num_sectors > 0); + + println!("init sdcard!"); + sd +} + + +pub struct SDCardWrapper(Mutex<SDCard<SPIImpl<SPI0>>>); + +impl SDCardWrapper { + pub fn new() -> Self { + Self(Mutex::new(init_sdcard())) + } +} + +impl BlockDevice for SDCardWrapper { + fn read_block(&self, block_id: usize, buf: &mut [u8]) { + self.0.lock().read_sector(buf,block_id as u32).unwrap(); + } + fn write_block(&self, block_id: usize, buf: &[u8]) { + self.0.lock().write_sector(buf,block_id as u32).unwrap(); + } +} \ No newline at end of file diff --git a/codes/os/src/drivers/block/virtio_blk.rs b/codes/os/src/drivers/block/virtio_blk.rs new file mode 100644 index 00000000..fde3428c --- /dev/null +++ b/codes/os/src/drivers/block/virtio_blk.rs @@ -0,0 +1,76 @@ + +use virtio_drivers::{VirtIOBlk, VirtIOHeader}; +use crate::mm::{ + PhysAddr, + VirtAddr, + frame_alloc, + frame_dealloc, + PhysPageNum, + FrameTracker, + StepByOne, + PageTable, + kernel_token, +}; +use super::BlockDevice; +use spin::Mutex; +use alloc::vec::Vec; +use lazy_static::*; + +#[allow(unused)] +const VIRTIO0: usize = 0x10001000; + +pub struct VirtIOBlock(Mutex<VirtIOBlk<'static>>); + +lazy_static! { + static ref QUEUE_FRAMES: Mutex<Vec<FrameTracker>> = Mutex::new(Vec::new()); +} + +impl BlockDevice for VirtIOBlock { + fn read_block(&self, block_id: usize, buf: &mut [u8]) { + self.0.lock().read_block(block_id, buf).expect("Error when reading VirtIOBlk"); + } + fn write_block(&self, block_id: usize, buf: &[u8]) { + self.0.lock().write_block(block_id, buf).expect("Error when writing VirtIOBlk"); + } +} + +impl VirtIOBlock { + #[allow(unused)] + pub fn new() -> Self { + Self(Mutex::new(VirtIOBlk::new( + unsafe { &mut *(VIRTIO0 as *mut VirtIOHeader) } + ).unwrap())) + } +} + +#[no_mangle] +pub extern "C" fn virtio_dma_alloc(pages: usize) -> PhysAddr { + let mut ppn_base = PhysPageNum(0); + for i in 0..pages { + let frame = frame_alloc().unwrap(); + if i == 0 { ppn_base = frame.ppn; } + assert_eq!(frame.ppn.0, ppn_base.0 + i); + QUEUE_FRAMES.lock().push(frame); + } + ppn_base.into() +} + +#[no_mangle] +pub extern "C" fn virtio_dma_dealloc(pa: PhysAddr, pages: usize) -> i32 { + let mut ppn_base: PhysPageNum = pa.into(); + for _ in 0..pages { + frame_dealloc(ppn_base); + ppn_base.step(); + } + 0 +} + +#[no_mangle] +pub extern "C" fn virtio_phys_to_virt(paddr: PhysAddr) -> VirtAddr { + VirtAddr(paddr.0) +} + +#[no_mangle] +pub extern "C" fn virtio_virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + PageTable::from_token(kernel_token()).translate_va(vaddr).unwrap() +} diff --git a/codes/os/src/drivers/mod.rs b/codes/os/src/drivers/mod.rs new file mode 100644 index 00000000..54c0a2cb --- /dev/null +++ b/codes/os/src/drivers/mod.rs @@ -0,0 +1,3 @@ +mod block; + +pub use block::BLOCK_DEVICE; \ No newline at end of file diff --git a/codes/os/src/entry.asm b/codes/os/src/entry.asm new file mode 100644 index 00000000..9d2ff713 --- /dev/null +++ b/codes/os/src/entry.asm @@ -0,0 +1,12 @@ + .section .text.entry + .globl _start +_start: + la sp, boot_stack_top + call rust_main + + .section .bss.stack + .globl boot_stack +boot_stack: + .space 4096 * 16 + .globl boot_stack_top +boot_stack_top: \ No newline at end of file diff --git a/codes/os/src/fs/inode.rs b/codes/os/src/fs/inode.rs new file mode 100644 index 00000000..1c86f500 --- /dev/null +++ b/codes/os/src/fs/inode.rs @@ -0,0 +1,242 @@ +use easy_fs::{ + EasyFileSystem, + Inode, + DiskInodeType, + NAME_LENGTH_LIMIT, +}; +use crate::drivers::BLOCK_DEVICE; +use crate::color_text; +use alloc::sync::Arc; +use lazy_static::*; +use bitflags::*; +use alloc::vec::Vec; +use alloc::vec; +use spin::Mutex; +use super::File; +use crate::mm::UserBuffer; + +// æ¤inode实际被当作文件 +pub struct OSInode { + readable: bool, + writable: bool, + inner: Mutex<OSInodeInner>, +} + +pub struct OSDirEntry { + inode_id: u32, + offset: usize,//dirent在目录ä¸çš„åç§»é‡ +} + +pub struct OSInodeInner { + offset: usize, // 当å‰è¯»å†™çš„ä½ç½® + inode: Arc<Inode>, // inode引用 +} + +impl OSInode { + pub fn new( + readable: bool, + writable: bool, + inode: Arc<Inode>, + ) -> Self { + Self { + readable, + writable, + inner: Mutex::new(OSInodeInner { + offset: 0, + inode, + }), + } + } + pub fn read_all(&self) -> Vec<u8> { + let mut inner = self.inner.lock(); + let mut buffer = [0u8; 512]; + let mut v: Vec<u8> = Vec::new(); + loop { + let len = inner.inode.read_at(inner.offset, &mut buffer); + if len == 0 { + break; + } + inner.offset += len; + v.extend_from_slice(&buffer[..len]); + } + v + } +} + +lazy_static! { + // 通过ROOT_INODEå¯ä»¥å®žçް坹efsçš„æ“作 + pub static ref ROOT_INODE: Arc<Inode> = { + // æ¤å¤„载入文件系统 + let efs = EasyFileSystem::open(BLOCK_DEVICE.clone()); + Arc::new(EasyFileSystem::get_inode(&efs, 0)) + }; +} + +lazy_static! { + // ç›®å½•æ ˆ + pub static ref DIR_STACK: Vec<Arc<Inode>> = vec![ROOT_INODE.clone()]; +} + +pub fn list_apps() { + println!("/**** APPS ****"); + for app in ROOT_INODE.ls() { + println!("{}", app.0); + } + println!("**************/") +} + +pub fn list_files(inode_id: u32){ + let curr_inode = EasyFileSystem::get_inode(&ROOT_INODE.get_fs(), inode_id); + let file_vec = curr_inode.ls(); + for i in 0 .. file_vec.len() { + if file_vec[i].1 == DiskInodeType::File{ + print!("{} ", file_vec[i].0); + } else { + // TODO: 统一é…è‰²ï¼ + print!("{} ", color_text!(file_vec[i].0, 96)); + } + } +} + +bitflags! { + pub struct OpenFlags: u32 { + const RDONLY = 0; + const WRONLY = 1 << 0; + const RDWR = 1 << 1; + const CREATE = 1 << 9; + const TRUNC = 1 << 10; + } +} + +impl OpenFlags { + /// Do not check validity for simplicity + /// Return (readable, writable) + pub fn read_write(&self) -> (bool, bool) { + if self.is_empty() { + (true, false) + } else if self.contains(Self::WRONLY) { + (false, true) + } else { + (true, true) + } + } +} + +pub fn open(inode_id: u32, path: &str, flags: OpenFlags, type_: DiskInodeType) -> Option<Arc<OSInode>> { + // DEBUG: 相对路径 + let cur_inode = { + if inode_id == 0 { + ROOT_INODE.clone() + }else{ + Arc::new(EasyFileSystem::get_inode( + &ROOT_INODE.get_fs(), + inode_id + )) + } + }; + let mut pathv:Vec<&str> = path.split('/').collect(); + // shell应当ä¿è¯æ¤å¤„输入的pathä¸ä¸ºç©º + let (readable, writable) = flags.read_write(); + if flags.contains(OpenFlags::CREATE) { + if let Some((inode,_)) = cur_inode.find_path(pathv.clone()) { + // clear size + inode.clear(); + Some(Arc::new(OSInode::new( + readable, + writable, + inode, + ))) + } else { + // create file + let name = pathv.pop().unwrap(); + if let Some((temp_inode,_)) = cur_inode.find_path(pathv.clone()){ + temp_inode.create( name, type_) + .map(|inode| { + Arc::new(OSInode::new( + readable, + writable, + inode, + )) + }) + }else{ + None + } + } + } else { + cur_inode.find_path(pathv) + .map(|(inode, _)| { + if flags.contains(OpenFlags::TRUNC) { + inode.clear(); + } + Arc::new(OSInode::new( + readable, + writable, + inode + )) + }) + } +} + + +pub fn ch_dir(inode_id: u32, path: &str) -> i32{ + // 切æ¢å·¥ä½œè·¯å¾„ + // åˆ‡æ¢æˆåŠŸï¼Œè¿”å›žinode_id,å¦åˆ™è¿”回-1 + let pathv:Vec<&str> = path.split('/').collect(); + let cur_inode = EasyFileSystem::get_inode( + &ROOT_INODE.get_fs(), + inode_id + ); + if let Some((tar_inode,_)) = cur_inode.find_path(pathv){ + // ! 当inode_id > 2^16 时,有溢出的å¯èƒ½ï¼ˆç›®å‰ä¸ä¼šå‘生。。 + tar_inode.get_id() as i32 + }else{ + -1 + } +} + +// TODO: 䏿€¥ +/* +pub fn read_dir(inode_id: u32) -> Option<Arc<OSDirEntry>> { + // 从目录ä¸è¯»å–下一个目录项 +}*/ + + +pub fn remove(inode_id: u32, path: &str, type_: DiskInodeType)->bool{ + // type_确认è¦åˆ é™¤çš„æ–‡ä»¶ç±»åž‹ï¼Œå¦‚æžœæ˜¯ç›®å½•ï¼Œé€’å½’åˆ é™¤ + let curr_inode = EasyFileSystem::get_inode( + &ROOT_INODE.get_fs().clone(), + inode_id + ); + let pathv:Vec<&str> = path.split('/').collect(); + curr_inode.remove(pathv,type_) +} + +impl File for OSInode { + fn readable(&self) -> bool { self.readable } + fn writable(&self) -> bool { self.writable } + fn read(&self, mut buf: UserBuffer) -> usize { + let mut inner = self.inner.lock(); + let mut total_read_size = 0usize; + for slice in buf.buffers.iter_mut() { + // bufferå˜æ”¾çš„å…ƒç´ æ˜¯[u8]è€Œä¸æ˜¯u8 + let read_size = inner.inode.read_at(inner.offset, *slice); + if read_size == 0 { + break; + } + inner.offset += read_size; + total_read_size += read_size; + } + total_read_size + } + fn write(&self, buf: UserBuffer) -> usize { + let mut inner = self.inner.lock(); + let mut total_write_size = 0usize; + for slice in buf.buffers.iter() { + let write_size = inner.inode.write_at(inner.offset, *slice); + assert_eq!(write_size, slice.len()); + inner.offset += write_size; + total_write_size += write_size; + } + total_write_size + } +} \ No newline at end of file diff --git a/codes/os/src/fs/mod.rs b/codes/os/src/fs/mod.rs new file mode 100644 index 00000000..6b092b25 --- /dev/null +++ b/codes/os/src/fs/mod.rs @@ -0,0 +1,17 @@ +mod pipe; +mod stdio; +mod inode; +mod util; + +use crate::mm::UserBuffer; + +pub trait File : Send + Sync { + fn readable(&self) -> bool; + fn writable(&self) -> bool; + fn read(&self, buf: UserBuffer) -> usize; + fn write(&self, buf: UserBuffer) -> usize; +} + +pub use pipe::{Pipe, make_pipe}; +pub use stdio::{Stdin, Stdout}; +pub use inode::{OSInode, open, OpenFlags, list_apps}; \ No newline at end of file diff --git a/codes/os/src/fs/pipe.rs b/codes/os/src/fs/pipe.rs new file mode 100644 index 00000000..ab319237 --- /dev/null +++ b/codes/os/src/fs/pipe.rs @@ -0,0 +1,168 @@ +use super::File; +use alloc::sync::{Arc, Weak}; +use spin::Mutex; +use crate::mm::{ + UserBuffer, +}; +use crate::task::suspend_current_and_run_next; + +pub struct Pipe { + readable: bool, + writable: bool, + buffer: Arc<Mutex<PipeRingBuffer>>, +} + +impl Pipe { + pub fn read_end_with_buffer(buffer: Arc<Mutex<PipeRingBuffer>>) -> Self { + Self { + readable: true, + writable: false, + buffer, + } + } + pub fn write_end_with_buffer(buffer: Arc<Mutex<PipeRingBuffer>>) -> Self { + Self { + readable: false, + writable: true, + buffer, + } + } +} + +const RING_BUFFER_SIZE: usize = 32; + +#[derive(Copy, Clone, PartialEq)] +enum RingBufferStatus { + FULL, + EMPTY, + NORMAL, +} + +pub struct PipeRingBuffer { + arr: [u8; RING_BUFFER_SIZE], + head: usize, + tail: usize, + status: RingBufferStatus, + write_end: Option<Weak<Pipe>>, +} + +impl PipeRingBuffer { + pub fn new() -> Self { + Self { + arr: [0; RING_BUFFER_SIZE], + head: 0, + tail: 0, + status: RingBufferStatus::EMPTY, + write_end: None, + } + } + pub fn set_write_end(&mut self, write_end: &Arc<Pipe>) { + self.write_end = Some(Arc::downgrade(write_end)); + } + pub fn write_byte(&mut self, byte: u8) { + self.status = RingBufferStatus::NORMAL; + self.arr[self.tail] = byte; + self.tail = (self.tail + 1) % RING_BUFFER_SIZE; + if self.tail == self.head { + self.status = RingBufferStatus::FULL; + } + } + pub fn read_byte(&mut self) -> u8 { + self.status = RingBufferStatus::NORMAL; + let c = self.arr[self.head]; + self.head = (self.head + 1) % RING_BUFFER_SIZE; + if self.head == self.tail { + self.status = RingBufferStatus::EMPTY; + } + c + } + pub fn available_read(&self) -> usize { + if self.status == RingBufferStatus::EMPTY { + 0 + } else { + if self.tail > self.head { + self.tail - self.head + } else { + self.tail + RING_BUFFER_SIZE - self.head + } + } + } + pub fn available_write(&self) -> usize { + if self.status == RingBufferStatus::FULL { + 0 + } else { + RING_BUFFER_SIZE - self.available_read() + } + } + pub fn all_write_ends_closed(&self) -> bool { + self.write_end.as_ref().unwrap().upgrade().is_none() + } +} + +/// Return (read_end, write_end) +pub fn make_pipe() -> (Arc<Pipe>, Arc<Pipe>) { + let buffer = Arc::new(Mutex::new(PipeRingBuffer::new())); + // bufferä»…å‰©ä¸¤ä¸ªå¼ºå¼•ç”¨ï¼Œè¿™æ ·è¯»å†™ç«¯å…³é—åŽå°±ä¼šè¢«é‡Šæ”¾ + let read_end = Arc::new( + Pipe::read_end_with_buffer(buffer.clone()) + ); + let write_end = Arc::new( + Pipe::write_end_with_buffer(buffer.clone()) + ); + buffer.lock().set_write_end(&write_end); + (read_end, write_end) +} + +impl File for Pipe { + fn readable(&self) -> bool { self.readable } + fn writable(&self) -> bool { self.writable } + fn read(&self, buf: UserBuffer) -> usize { + assert_eq!(self.readable(), true); + let mut buf_iter = buf.into_iter(); + let mut read_size = 0usize; + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_read = ring_buffer.available_read(); + if loop_read == 0 { + if ring_buffer.all_write_ends_closed() { + return read_size; //returnåŽå°±ring_buffer释放了,é”自然释放 + } + drop(ring_buffer); + suspend_current_and_run_next(); + continue; + } + // read at most loop_read bytes + for _ in 0..loop_read { + if let Some(byte_ref) = buf_iter.next() { + unsafe { *byte_ref = ring_buffer.read_byte(); } + read_size += 1; + } else { + return read_size; + } + } + } + } + fn write(&self, buf: UserBuffer) -> usize { + assert_eq!(self.writable(), true); + let mut buf_iter = buf.into_iter(); + let mut write_size = 0usize; + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_write = ring_buffer.available_write(); + if loop_write == 0 { + drop(ring_buffer); + suspend_current_and_run_next(); + continue; + } + // write at most loop_write bytes + for _ in 0..loop_write { + if let Some(byte_ref) = buf_iter.next() { + ring_buffer.write_byte(unsafe { *byte_ref }); + write_size += 1; + } else { + return write_size; + } + } + } + } +} \ No newline at end of file diff --git a/codes/os/src/fs/stdio.rs b/codes/os/src/fs/stdio.rs new file mode 100644 index 00000000..e8df7950 --- /dev/null +++ b/codes/os/src/fs/stdio.rs @@ -0,0 +1,47 @@ +use super::File; +use crate::mm::{UserBuffer}; +use crate::sbi::console_getchar; +use crate::task::suspend_current_and_run_next; + +pub struct Stdin; + +pub struct Stdout; + +impl File for Stdin { + fn readable(&self) -> bool { true } + fn writable(&self) -> bool { false } + fn read(&self, mut user_buf: UserBuffer) -> usize { + assert_eq!(user_buf.len(), 1); + // busy loop + let mut c: usize; + loop { + c = console_getchar(); + if c == 0 { + suspend_current_and_run_next(); + continue; + } else { + break; + } + } + let ch = c as u8; + unsafe { user_buf.buffers[0].as_mut_ptr().write_volatile(ch); } + 1 + } + fn write(&self, _user_buf: UserBuffer) -> usize { + panic!("Cannot write to stdin!"); + } +} + +impl File for Stdout { + fn readable(&self) -> bool { false } + fn writable(&self) -> bool { true } + fn read(&self, _user_buf: UserBuffer) -> usize{ + panic!("Cannot read from stdout!"); + } + fn write(&self, user_buf: UserBuffer) -> usize { + for buffer in user_buf.buffers.iter() { + print!("{}", core::str::from_utf8(*buffer).unwrap()); + } + user_buf.len() + } +} \ No newline at end of file diff --git a/codes/os/src/fs/util.rs b/codes/os/src/fs/util.rs new file mode 100644 index 00000000..5cb1d76a --- /dev/null +++ b/codes/os/src/fs/util.rs @@ -0,0 +1,5 @@ +use alloc::vec::Vec; + +pub fn str2vec(str: &str) -> Vec<&str>{ + str.split('/').collect() +} \ No newline at end of file diff --git a/codes/os/src/lang_items.rs b/codes/os/src/lang_items.rs new file mode 100644 index 00000000..99c34cd9 --- /dev/null +++ b/codes/os/src/lang_items.rs @@ -0,0 +1,19 @@ +use core::panic::PanicInfo; +use crate::sbi::shutdown; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + if let Some(location) = info.location() { + println!("[kernel] Panicked at {}:{} {}", location.file(), location.line(), info.message().unwrap()); + } else { + println!("[kernel] Panicked: {}", info.message().unwrap()); + } + shutdown() +} + +#[macro_export] +macro_rules! color_text { + ($text:expr, $color:expr) => {{ + format_args!("\x1b[{}m{}\x1b[0m", $color, $text) + }}; +} \ No newline at end of file diff --git a/codes/os/src/linker-k210.ld b/codes/os/src/linker-k210.ld new file mode 100644 index 00000000..4f9d2171 --- /dev/null +++ b/codes/os/src/linker-k210.ld @@ -0,0 +1,50 @@ +OUTPUT_ARCH(riscv) +ENTRY(_start) +BASE_ADDRESS = 0x80020000; + +SECTIONS +{ + . = BASE_ADDRESS; + skernel = .; + + stext = .; + .text : { + *(.text.entry) + . = ALIGN(4K); + strampoline = .; + *(.text.trampoline); + . = ALIGN(4K); + *(.text .text.*) + } + + . = ALIGN(4K); + etext = .; + srodata = .; + .rodata : { + *(.rodata .rodata.*) + } + + . = ALIGN(4K); + erodata = .; + sdata = .; + .data : { + *(.data .data.*) + } + + . = ALIGN(4K); + edata = .; + sbss_with_stack = .; + .bss : { + *(.bss.stack) + sbss = .; + *(.bss .bss.*) + } + + . = ALIGN(4K); + ebss = .; + ekernel = .; + + /DISCARD/ : { + *(.eh_frame) + } +} \ No newline at end of file diff --git a/codes/os/src/linker-qemu.ld b/codes/os/src/linker-qemu.ld new file mode 100644 index 00000000..6b06e916 --- /dev/null +++ b/codes/os/src/linker-qemu.ld @@ -0,0 +1,50 @@ +OUTPUT_ARCH(riscv) +ENTRY(_start) +BASE_ADDRESS = 0x80200000; + +SECTIONS +{ + . = BASE_ADDRESS; + skernel = .; + + stext = .; + .text : { + *(.text.entry) + . = ALIGN(4K); + strampoline = .; + *(.text.trampoline); + . = ALIGN(4K); + *(.text .text.*) + } + + . = ALIGN(4K); + etext = .; + srodata = .; + .rodata : { + *(.rodata .rodata.*) + } + + . = ALIGN(4K); + erodata = .; + sdata = .; + .data : { + *(.data .data.*) + } + + . = ALIGN(4K); + edata = .; + sbss_with_stack = .; + .bss : { + *(.bss.stack) + sbss = .; + *(.bss .bss.*) + } + + . = ALIGN(4K); + ebss = .; + ekernel = .; + + /DISCARD/ : { + *(.eh_frame) + } +} \ No newline at end of file diff --git a/codes/os/src/main.rs b/codes/os/src/main.rs new file mode 100644 index 00000000..e9a34bb8 --- /dev/null +++ b/codes/os/src/main.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] +#![feature(global_asm)] +#![feature(llvm_asm)] +#![feature(panic_info_message)] +#![feature(const_in_array_repeat_expressions)] +#![feature(alloc_error_handler)] + +extern crate alloc; + +#[macro_use] +extern crate bitflags; + +#[macro_use] +mod console; +mod lang_items; +mod sbi; +mod syscall; +mod trap; +mod config; +mod task; +mod timer; +mod mm; +mod fs; +mod drivers; + +global_asm!(include_str!("entry.asm")); + +fn clear_bss() { + extern "C" { + fn sbss(); + fn ebss(); + } + (sbss as usize..ebss as usize).for_each(|a| { + unsafe { (a as *mut u8).write_volatile(0) } + }); +} + +#[no_mangle] +pub fn rust_main() -> ! { + clear_bss(); + println!("[kernel] Hello, world!"); + mm::init(); + mm::remap_test(); + trap::init(); + trap::enable_timer_interrupt(); + timer::set_next_trigger(); + fs::list_apps(); + task::add_initproc(); + task::run_tasks(); + panic!("Unreachable in rust_main!"); +} \ No newline at end of file diff --git a/codes/os/src/mm/address.rs b/codes/os/src/mm/address.rs new file mode 100644 index 00000000..d5828ed2 --- /dev/null +++ b/codes/os/src/mm/address.rs @@ -0,0 +1,209 @@ +use crate::config::{PAGE_SIZE, PAGE_SIZE_BITS}; +use super::PageTableEntry; +use core::fmt::{self, Debug, Formatter}; + +/// Definitions +#[repr(C)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct PhysAddr(pub usize); + +#[repr(C)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct VirtAddr(pub usize); + +#[repr(C)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct PhysPageNum(pub usize); + +#[repr(C)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct VirtPageNum(pub usize); + +/// Debugging + +impl Debug for VirtAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("VA:{:#x}", self.0)) + } +} +impl Debug for VirtPageNum { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("VPN:{:#x}", self.0)) + } +} +impl Debug for PhysAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("PA:{:#x}", self.0)) + } +} +impl Debug for PhysPageNum { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("PPN:{:#x}", self.0)) + } +} + +/// T: {PhysAddr, VirtAddr, PhysPageNum, VirtPageNum} +/// T -> usize: T.0 +/// usize -> T: usize.into() + +impl From<usize> for PhysAddr { + fn from(v: usize) -> Self { Self(v) } +} +impl From<usize> for PhysPageNum { + fn from(v: usize) -> Self { Self(v) } +} +impl From<usize> for VirtAddr { + fn from(v: usize) -> Self { Self(v) } +} +impl From<usize> for VirtPageNum { + fn from(v: usize) -> Self { Self(v) } +} +impl From<PhysAddr> for usize { + fn from(v: PhysAddr) -> Self { v.0 } +} +impl From<PhysPageNum> for usize { + fn from(v: PhysPageNum) -> Self { v.0 } +} +impl From<VirtAddr> for usize { + fn from(v: VirtAddr) -> Self { v.0 } +} +impl From<VirtPageNum> for usize { + fn from(v: VirtPageNum) -> Self { v.0 } +} + +impl VirtAddr { + pub fn floor(&self) -> VirtPageNum { VirtPageNum(self.0 / PAGE_SIZE) } + pub fn ceil(&self) -> VirtPageNum { VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) } + pub fn page_offset(&self) -> usize { self.0 & (PAGE_SIZE - 1) } + pub fn aligned(&self) -> bool { self.page_offset() == 0 } +} +impl From<VirtAddr> for VirtPageNum { + fn from(v: VirtAddr) -> Self { + assert_eq!(v.page_offset(), 0); + v.floor() + } +} +impl From<VirtPageNum> for VirtAddr { + fn from(v: VirtPageNum) -> Self { Self(v.0 << PAGE_SIZE_BITS) } +} +impl PhysAddr { + pub fn floor(&self) -> PhysPageNum { PhysPageNum(self.0 / PAGE_SIZE) } + pub fn ceil(&self) -> PhysPageNum { PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) } + pub fn page_offset(&self) -> usize { self.0 & (PAGE_SIZE - 1) } + pub fn aligned(&self) -> bool { self.page_offset() == 0 } +} +impl From<PhysAddr> for PhysPageNum { + fn from(v: PhysAddr) -> Self { + assert_eq!(v.page_offset(), 0); + v.floor() + } +} +impl From<PhysPageNum> for PhysAddr { + fn from(v: PhysPageNum) -> Self { Self(v.0 << PAGE_SIZE_BITS) } +} + +impl VirtPageNum { + pub fn indexes(&self) -> [usize; 3] { + let mut vpn = self.0; + let mut idx = [0usize; 3]; + for i in (0..3).rev() { + idx[i] = vpn & 511; + vpn >>= 9; + } + idx + } +} + +impl PhysAddr { + pub fn get_ref<T>(&self) -> &'static T { + unsafe { + (self.0 as *const T).as_ref().unwrap() + } + } + pub fn get_mut<T>(&self) -> &'static mut T { + unsafe { + (self.0 as *mut T).as_mut().unwrap() + } + } +} +impl PhysPageNum { + pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] { + let pa: PhysAddr = self.clone().into(); + unsafe { + core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) + } + } + pub fn get_bytes_array(&self) -> &'static mut [u8] { + let pa: PhysAddr = self.clone().into(); + unsafe { + core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) + } + } + pub fn get_mut<T>(&self) -> &'static mut T { + let pa: PhysAddr = self.clone().into(); + pa.get_mut() + } +} + +pub trait StepByOne { + fn step(&mut self); +} +impl StepByOne for VirtPageNum { + fn step(&mut self) { + self.0 += 1; + } +} +impl StepByOne for PhysPageNum { + fn step(&mut self) { + self.0 += 1; + } +} + +#[derive(Copy, Clone)] +pub struct SimpleRange<T> where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { + l: T, + r: T, +} +impl<T> SimpleRange<T> where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { + pub fn new(start: T, end: T) -> Self { + assert!(start <= end, "start {:?} > end {:?}!", start, end); + Self { l: start, r: end } + } + pub fn get_start(&self) -> T { self.l } + pub fn get_end(&self) -> T { self.r } +} +impl<T> IntoIterator for SimpleRange<T> where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { + type Item = T; + type IntoIter = SimpleRangeIterator<T>; + fn into_iter(self) -> Self::IntoIter { + SimpleRangeIterator::new(self.l, self.r) + } +} +pub struct SimpleRangeIterator<T> where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { + current: T, + end: T, +} +impl<T> SimpleRangeIterator<T> where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { + pub fn new(l: T, r: T) -> Self { + Self { current: l, end: r, } + } +} +impl<T> Iterator for SimpleRangeIterator<T> where + T: StepByOne + Copy + PartialEq + PartialOrd + Debug, { + type Item = T; + fn next(&mut self) -> Option<Self::Item> { + if self.current == self.end { + None + } else { + let t = self.current; + self.current.step(); + Some(t) + } + } +} +pub type VPNRange = SimpleRange<VirtPageNum>; \ No newline at end of file diff --git a/codes/os/src/mm/frame_allocator.rs b/codes/os/src/mm/frame_allocator.rs new file mode 100644 index 00000000..2cb6427b --- /dev/null +++ b/codes/os/src/mm/frame_allocator.rs @@ -0,0 +1,133 @@ +use super::{PhysAddr, PhysPageNum}; +use alloc::vec::Vec; +use spin::Mutex; +use crate::config::MEMORY_END; +use lazy_static::*; +use core::fmt::{self, Debug, Formatter}; + +pub struct FrameTracker { + pub ppn: PhysPageNum, +} + +impl FrameTracker { + pub fn new(ppn: PhysPageNum) -> Self { + // page cleaning + let bytes_array = ppn.get_bytes_array(); + for i in bytes_array { + *i = 0; + } + Self { ppn } + } +} + +impl Debug for FrameTracker { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("FrameTracker:PPN={:#x}", self.ppn.0)) + } +} + +impl Drop for FrameTracker { + fn drop(&mut self) { + frame_dealloc(self.ppn); + } +} + +trait FrameAllocator { + fn new() -> Self; + fn alloc(&mut self) -> Option<PhysPageNum>; + fn dealloc(&mut self, ppn: PhysPageNum); +} + +pub struct StackFrameAllocator { + current: usize, + end: usize, + recycled: Vec<usize>, +} + +impl StackFrameAllocator { + pub fn init(&mut self, l: PhysPageNum, r: PhysPageNum) { + self.current = l.0; + self.end = r.0; + println!("last {} Physical Frames.", self.end - self.current); + } +} +impl FrameAllocator for StackFrameAllocator { + fn new() -> Self { + Self { + current: 0, + end: 0, + recycled: Vec::new(), + } + } + fn alloc(&mut self) -> Option<PhysPageNum> { + if let Some(ppn) = self.recycled.pop() { + Some(ppn.into()) + } else { + if self.current == self.end { + None + } else { + self.current += 1; + Some((self.current - 1).into()) + } + } + } + fn dealloc(&mut self, ppn: PhysPageNum) { + let ppn = ppn.0; + // validity check + if ppn >= self.current || self.recycled + .iter() + .find(|&v| {*v == ppn}) + .is_some() { + panic!("Frame ppn={:#x} has not been allocated!", ppn); + } + // recycle + self.recycled.push(ppn); + } +} + +type FrameAllocatorImpl = StackFrameAllocator; + +lazy_static! { + pub static ref FRAME_ALLOCATOR: Mutex<FrameAllocatorImpl> = + Mutex::new(FrameAllocatorImpl::new()); +} + +pub fn init_frame_allocator() { + extern "C" { + fn ekernel(); + } + FRAME_ALLOCATOR + .lock() + .init(PhysAddr::from(ekernel as usize).ceil(), PhysAddr::from(MEMORY_END).floor()); +} + +pub fn frame_alloc() -> Option<FrameTracker> { + FRAME_ALLOCATOR + .lock() + .alloc() + .map(|ppn| FrameTracker::new(ppn)) +} + +pub fn frame_dealloc(ppn: PhysPageNum) { + FRAME_ALLOCATOR + .lock() + .dealloc(ppn); +} + +#[allow(unused)] +pub fn frame_allocator_test() { + let mut v: Vec<FrameTracker> = Vec::new(); + for i in 0..5 { + let frame = frame_alloc().unwrap(); + println!("{:?}", frame); + v.push(frame); + } + v.clear(); + for i in 0..5 { + let frame = frame_alloc().unwrap(); + println!("{:?}", frame); + v.push(frame); + } + drop(v); + println!("frame_allocator_test passed!"); +} \ No newline at end of file diff --git a/codes/os/src/mm/heap_allocator.rs b/codes/os/src/mm/heap_allocator.rs new file mode 100644 index 00000000..2c7468f2 --- /dev/null +++ b/codes/os/src/mm/heap_allocator.rs @@ -0,0 +1,45 @@ +use buddy_system_allocator::LockedHeap; +use crate::config::KERNEL_HEAP_SIZE; + +#[global_allocator] +static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty(); + +#[alloc_error_handler] +pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! { + panic!("Heap allocation error, layout = {:?}", layout); +} + +static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE]; + +pub fn init_heap() { + unsafe { + HEAP_ALLOCATOR + .lock() + .init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE); + } +} + +#[allow(unused)] +pub fn heap_test() { + use alloc::boxed::Box; + use alloc::vec::Vec; + extern "C" { + fn sbss(); + fn ebss(); + } + let bss_range = sbss as usize..ebss as usize; + let a = Box::new(5); + assert_eq!(*a, 5); + assert!(bss_range.contains(&(a.as_ref() as *const _ as usize))); + drop(a); + let mut v: Vec<usize> = Vec::new(); + for i in 0..500 { + v.push(i); + } + for i in 0..500 { + assert_eq!(v[i], i); + } + assert!(bss_range.contains(&(v.as_ptr() as usize))); + drop(v); + println!("heap_test passed!"); +} diff --git a/codes/os/src/mm/memory_set.rs b/codes/os/src/mm/memory_set.rs new file mode 100644 index 00000000..262f0852 --- /dev/null +++ b/codes/os/src/mm/memory_set.rs @@ -0,0 +1,359 @@ +use super::{PageTable, PageTableEntry, PTEFlags}; +use super::{VirtPageNum, VirtAddr, PhysPageNum, PhysAddr}; +use super::{FrameTracker, frame_alloc}; +use super::{VPNRange, StepByOne}; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use riscv::register::satp; +use alloc::sync::Arc; +use lazy_static::*; +use spin::Mutex; +use crate::config::{ + MEMORY_END, + PAGE_SIZE, + TRAMPOLINE, + TRAP_CONTEXT, + USER_STACK_SIZE, + MMIO, +}; + +extern "C" { + fn stext(); + fn etext(); + fn srodata(); + fn erodata(); + fn sdata(); + fn edata(); + fn sbss_with_stack(); + fn ebss(); + fn ekernel(); + fn strampoline(); +} + +lazy_static! { + pub static ref KERNEL_SPACE: Arc<Mutex<MemorySet>> = Arc::new(Mutex::new( + MemorySet::new_kernel() + )); +} + +pub fn kernel_token() -> usize { + KERNEL_SPACE.lock().token() +} + +pub struct MemorySet { + page_table: PageTable, + areas: Vec<MapArea>, +} + +impl MemorySet { + pub fn new_bare() -> Self { + Self { + page_table: PageTable::new(), + areas: Vec::new(), + } + } + pub fn token(&self) -> usize { + self.page_table.token() + } + /// Assume that no conflicts. + pub fn insert_framed_area(&mut self, start_va: VirtAddr, end_va: VirtAddr, permission: MapPermission) { + self.push(MapArea::new( + start_va, + end_va, + MapType::Framed, + permission, + ), None); + } + pub fn remove_area_with_start_vpn(&mut self, start_vpn: VirtPageNum) { + if let Some((idx, area)) = self.areas.iter_mut().enumerate() + .find(|(_, area)| area.vpn_range.get_start() == start_vpn) { + area.unmap(&mut self.page_table); + self.areas.remove(idx); + } + } + fn push(&mut self, mut map_area: MapArea, data: Option<&[u8]>) { + map_area.map(&mut self.page_table); + if let Some(data) = data { + map_area.copy_data(&mut self.page_table, data); + } + self.areas.push(map_area); + } + /// Mention that trampoline is not collected by areas. + fn map_trampoline(&mut self) { + println!("*** trampoline = {:X}",TRAMPOLINE); + self.page_table.map( + VirtAddr::from(TRAMPOLINE).into(), + PhysAddr::from(strampoline as usize).into(), + PTEFlags::R | PTEFlags::X, + ); + } + /// Without kernel stacks. + pub fn new_kernel() -> Self { + let mut memory_set = Self::new_bare(); + // map trampoline + memory_set.map_trampoline(); + // map kernel sections + println!(".text [{:#x}, {:#x})", stext as usize, etext as usize); + println!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize); + println!(".data [{:#x}, {:#x})", sdata as usize, edata as usize); + println!(".bss [{:#x}, {:#x})", sbss_with_stack as usize, ebss as usize); + println!("mapping .text section"); + memory_set.push(MapArea::new( + (stext as usize).into(), + (etext as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::X, + ), None); + println!("mapping .rodata section"); + memory_set.push(MapArea::new( + (srodata as usize).into(), + (erodata as usize).into(), + MapType::Identical, + MapPermission::R, + ), None); + println!("mapping .data section"); + memory_set.push(MapArea::new( + (sdata as usize).into(), + (edata as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), None); + println!("mapping .bss section"); + memory_set.push(MapArea::new( + (sbss_with_stack as usize).into(), + (ebss as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), None); + println!("mapping physical memory"); + memory_set.push(MapArea::new( + (ekernel as usize).into(), + MEMORY_END.into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), None); + println!("mapping memory-mapped registers"); + for pair in MMIO { + memory_set.push(MapArea::new( + (*pair).0.into(), + ((*pair).0 + (*pair).1).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), None); + } + memory_set + } + /// Include sections in elf and trampoline and TrapContext and user stack, + /// also returns user_sp and entry point. + pub fn from_elf(elf_data: &[u8]) -> (Self, usize, usize) { + let mut memory_set = Self::new_bare(); + // map trampoline + memory_set.map_trampoline(); + // map program headers of elf, with U flag + let elf = xmas_elf::ElfFile::new(elf_data).unwrap(); + let elf_header = elf.header; + let magic = elf_header.pt1.magic; + assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!"); + let ph_count = elf_header.pt2.ph_count(); + let mut max_end_vpn = VirtPageNum(0); + for i in 0..ph_count { + let ph = elf.program_header(i).unwrap(); + if ph.get_type().unwrap() == xmas_elf::program::Type::Load { + let start_va: VirtAddr = (ph.virtual_addr() as usize).into(); + let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into(); + let mut map_perm = MapPermission::U; + let ph_flags = ph.flags(); + if ph_flags.is_read() { map_perm |= MapPermission::R; } + if ph_flags.is_write() { map_perm |= MapPermission::W; } + if ph_flags.is_execute() { map_perm |= MapPermission::X; } + let map_area = MapArea::new( + start_va, + end_va, + MapType::Framed, + map_perm, + ); + max_end_vpn = map_area.vpn_range.get_end(); + memory_set.push( + map_area, + Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]) + ); + } + } + // map user stack with U flags + let max_end_va: VirtAddr = max_end_vpn.into(); + let mut user_stack_bottom: usize = max_end_va.into(); + // guard page + user_stack_bottom += PAGE_SIZE; + let user_stack_top = user_stack_bottom + USER_STACK_SIZE; + memory_set.push(MapArea::new( + user_stack_bottom.into(), + user_stack_top.into(), + MapType::Framed, + MapPermission::R | MapPermission::W | MapPermission::U, + ), None); + // map TrapContext + memory_set.push(MapArea::new( + TRAP_CONTEXT.into(), + TRAMPOLINE.into(), + MapType::Framed, + MapPermission::R | MapPermission::W, + ), None); + (memory_set, user_stack_top, elf.header.pt2.entry_point() as usize) + } + pub fn from_existed_user(user_space: &MemorySet) -> MemorySet { + let mut memory_set = Self::new_bare(); + // map trampoline + memory_set.map_trampoline(); + // copy data sections/trap_context/user_stack + for area in user_space.areas.iter() { + let new_area = MapArea::from_another(area); + memory_set.push(new_area, None); + // copy data from another space + for vpn in area.vpn_range { + let src_ppn = user_space.translate(vpn).unwrap().ppn(); + let dst_ppn = memory_set.translate(vpn).unwrap().ppn(); + dst_ppn.get_bytes_array().copy_from_slice(src_ppn.get_bytes_array()); + } + } + memory_set + } + pub fn activate(&self) { + let satp = self.page_table.token(); + unsafe { + satp::write(satp); + llvm_asm!("sfence.vma" :::: "volatile"); + } + } + pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> { + self.page_table.translate(vpn) + } + pub fn recycle_data_pages(&mut self) { + //*self = Self::new_bare(); + self.areas.clear(); + } +} + +pub struct MapArea { + vpn_range: VPNRange, + data_frames: BTreeMap<VirtPageNum, FrameTracker>, + map_type: MapType, + map_perm: MapPermission, +} + +impl MapArea { + pub fn new( + start_va: VirtAddr, + end_va: VirtAddr, + map_type: MapType, + map_perm: MapPermission + ) -> Self { + let start_vpn: VirtPageNum = start_va.floor(); + let end_vpn: VirtPageNum = end_va.ceil(); + Self { + vpn_range: VPNRange::new(start_vpn, end_vpn), + data_frames: BTreeMap::new(), + map_type, + map_perm, + } + } + pub fn from_another(another: &MapArea) -> Self { + Self { + vpn_range: VPNRange::new(another.vpn_range.get_start(), another.vpn_range.get_end()), + data_frames: BTreeMap::new(), + map_type: another.map_type, + map_perm: another.map_perm, + } + } + pub fn map_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) { + let ppn: PhysPageNum; + match self.map_type { + MapType::Identical => { + ppn = PhysPageNum(vpn.0); + } + MapType::Framed => { + let frame = frame_alloc().unwrap(); + ppn = frame.ppn; + self.data_frames.insert(vpn, frame); + } + } + let pte_flags = PTEFlags::from_bits(self.map_perm.bits).unwrap(); + page_table.map(vpn, ppn, pte_flags); + } + pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) { + match self.map_type { + MapType::Framed => { + self.data_frames.remove(&vpn); + } + _ => {} + } + page_table.unmap(vpn); + } + pub fn map(&mut self, page_table: &mut PageTable) { + for vpn in self.vpn_range { + self.map_one(page_table, vpn); + } + } + pub fn unmap(&mut self, page_table: &mut PageTable) { + for vpn in self.vpn_range { + self.unmap_one(page_table, vpn); + } + } + /// data: start-aligned but maybe with shorter length + /// assume that all frames were cleared before + pub fn copy_data(&mut self, page_table: &mut PageTable, data: &[u8]) { + assert_eq!(self.map_type, MapType::Framed); + let mut start: usize = 0; + let mut current_vpn = self.vpn_range.get_start(); + let len = data.len(); + loop { + let src = &data[start..len.min(start + PAGE_SIZE)]; + let dst = &mut page_table + .translate(current_vpn) + .unwrap() + .ppn() + .get_bytes_array()[..src.len()]; + dst.copy_from_slice(src); + start += PAGE_SIZE; + if start >= len { + break; + } + current_vpn.step(); + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum MapType { + Identical, + Framed, +} + +bitflags! { + pub struct MapPermission: u8 { + const R = 1 << 1; + const W = 1 << 2; + const X = 1 << 3; + const U = 1 << 4; + } +} + +#[allow(unused)] +pub fn remap_test() { + let mut kernel_space = KERNEL_SPACE.lock(); + let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into(); + let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into(); + let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into(); + assert_eq!( + kernel_space.page_table.translate(mid_text.floor()).unwrap().writable(), + false + ); + assert_eq!( + kernel_space.page_table.translate(mid_rodata.floor()).unwrap().writable(), + false, + ); + assert_eq!( + kernel_space.page_table.translate(mid_data.floor()).unwrap().executable(), + false, + ); + println!("remap_test passed!"); +} \ No newline at end of file diff --git a/codes/os/src/mm/mod.rs b/codes/os/src/mm/mod.rs new file mode 100644 index 00000000..dbd47fe7 --- /dev/null +++ b/codes/os/src/mm/mod.rs @@ -0,0 +1,28 @@ +mod heap_allocator; +mod address; +mod frame_allocator; +mod page_table; +mod memory_set; + +use page_table::PTEFlags; +use address::VPNRange; +pub use address::{PhysAddr, VirtAddr, PhysPageNum, VirtPageNum, StepByOne}; +pub use frame_allocator::{FrameTracker, frame_alloc, frame_dealloc,}; +pub use page_table::{ + PageTable, + PageTableEntry, + translated_byte_buffer, + translated_str, + translated_ref, + translated_refmut, + UserBuffer, + UserBufferIterator, +}; +pub use memory_set::{MemorySet, KERNEL_SPACE, MapPermission, kernel_token}; +pub use memory_set::remap_test; + +pub fn init() { + heap_allocator::init_heap(); + frame_allocator::init_frame_allocator(); + KERNEL_SPACE.lock().activate(); +} \ No newline at end of file diff --git a/codes/os/src/mm/page_table.rs b/codes/os/src/mm/page_table.rs new file mode 100644 index 00000000..b1a227fa --- /dev/null +++ b/codes/os/src/mm/page_table.rs @@ -0,0 +1,255 @@ +use super::{ + frame_alloc, + PhysPageNum, + FrameTracker, + VirtPageNum, + VirtAddr, + PhysAddr, + StepByOne +}; +use alloc::vec::Vec; +use alloc::vec; +use alloc::string::String; +use bitflags::*; + +bitflags! { + pub struct PTEFlags: u8 { + const V = 1 << 0; + const R = 1 << 1; + const W = 1 << 2; + const X = 1 << 3; + const U = 1 << 4; + const G = 1 << 5; + const A = 1 << 6; + const D = 1 << 7; + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PageTableEntry { + pub bits: usize, +} + +impl PageTableEntry { + pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self { + PageTableEntry { + bits: ppn.0 << 10 | flags.bits as usize, + } + } + pub fn empty() -> Self { + PageTableEntry { + bits: 0, + } + } + pub fn ppn(&self) -> PhysPageNum { + (self.bits >> 10 & ((1usize << 44) - 1)).into() + } + pub fn flags(&self) -> PTEFlags { + PTEFlags::from_bits(self.bits as u8).unwrap() + } + pub fn is_valid(&self) -> bool { + (self.flags() & PTEFlags::V) != PTEFlags::empty() + } + pub fn readable(&self) -> bool { + (self.flags() & PTEFlags::R) != PTEFlags::empty() + } + pub fn writable(&self) -> bool { + (self.flags() & PTEFlags::W) != PTEFlags::empty() + } + pub fn executable(&self) -> bool { + (self.flags() & PTEFlags::X) != PTEFlags::empty() + } +} + +pub struct PageTable { + root_ppn: PhysPageNum, + frames: Vec<FrameTracker>, +} + +/// Assume that it won't oom when creating/mapping. +impl PageTable { + pub fn new() -> Self { + let frame = frame_alloc().unwrap(); + PageTable { + root_ppn: frame.ppn, + frames: vec![frame], + } + } + /// Temporarily used to get arguments from user space. + pub fn from_token(satp: usize) -> Self { + Self { + root_ppn: PhysPageNum::from(satp & ((1usize << 44) - 1)), + frames: Vec::new(), + } + } + fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> { + let idxs = vpn.indexes(); + let mut ppn = self.root_ppn; + let mut result: Option<&mut PageTableEntry> = None; + for i in 0..3 { + let pte = &mut ppn.get_pte_array()[idxs[i]]; + if i == 2 { + result = Some(pte); + break; + } + if !pte.is_valid() { + let frame = frame_alloc().unwrap(); + *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); + self.frames.push(frame); + } + ppn = pte.ppn(); + } + result + } + fn find_pte(&self, vpn: VirtPageNum) -> Option<&PageTableEntry> { + let idxs = vpn.indexes(); + let mut ppn = self.root_ppn; + let mut result: Option<&PageTableEntry> = None; + for i in 0..3 { + let pte = &ppn.get_pte_array()[idxs[i]]; + if i == 2 { + result = Some(pte); + break; + } + if !pte.is_valid() { + return None; + } + ppn = pte.ppn(); + } + result + } + #[allow(unused)] + pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) { + let pte = self.find_pte_create(vpn).unwrap(); + assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn); + *pte = PageTableEntry::new(ppn, flags | PTEFlags::V); + } + #[allow(unused)] + pub fn unmap(&mut self, vpn: VirtPageNum) { + let pte = self.find_pte_create(vpn).unwrap(); + assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn); + *pte = PageTableEntry::empty(); + } + pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> { + self.find_pte(vpn) + .map(|pte| {pte.clone()}) + } + pub fn translate_va(&self, va: VirtAddr) -> Option<PhysAddr> { + self.find_pte(va.clone().floor()) + .map(|pte| { + let aligned_pa: PhysAddr = pte.ppn().into(); + let offset = va.page_offset(); + let aligned_pa_usize: usize = aligned_pa.into(); + (aligned_pa_usize + offset).into() + }) + } + pub fn token(&self) -> usize { + 8usize << 60 | self.root_ppn.0 + } +} + +pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&'static mut [u8]> { + let page_table = PageTable::from_token(token); + let mut start = ptr as usize; + let end = start + len; + let mut v = Vec::new(); + while start < end { + let start_va = VirtAddr::from(start); + let mut vpn = start_va.floor(); + let ppn = page_table + .translate(vpn) + .unwrap() + .ppn(); + vpn.step(); + let mut end_va: VirtAddr = vpn.into(); + end_va = end_va.min(VirtAddr::from(end)); + if end_va.page_offset() == 0 { + v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..]); + } else { + v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..end_va.page_offset()]); + } + start = end_va.into(); + } + v +} + +/// Load a string from other address spaces into kernel space without an end `\0`. +pub fn translated_str(token: usize, ptr: *const u8) -> String { + let page_table = PageTable::from_token(token); + let mut string = String::new(); + let mut va = ptr as usize; + loop { + let ch: u8 = *(page_table.translate_va(VirtAddr::from(va)).unwrap().get_mut()); + if ch == 0 { + break; + } + string.push(ch as char); + va += 1; + } + string +} + +pub fn translated_ref<T>(token: usize, ptr: *const T) -> &'static T { + let page_table = PageTable::from_token(token); + page_table.translate_va(VirtAddr::from(ptr as usize)).unwrap().get_ref() +} + +pub fn translated_refmut<T>(token: usize, ptr: *mut T) -> &'static mut T { + let page_table = PageTable::from_token(token); + let va = ptr as usize; + page_table.translate_va(VirtAddr::from(va)).unwrap().get_mut() +} + +pub struct UserBuffer { + pub buffers: Vec<&'static mut [u8]>, +} + +impl UserBuffer { + pub fn new(buffers: Vec<&'static mut [u8]>) -> Self { + Self { buffers } + } + pub fn len(&self) -> usize { + let mut total: usize = 0; + for b in self.buffers.iter() { + total += b.len(); + } + total + } +} + +impl IntoIterator for UserBuffer { + type Item = *mut u8; + type IntoIter = UserBufferIterator; + fn into_iter(self) -> Self::IntoIter { + UserBufferIterator { + buffers: self.buffers, + current_buffer: 0, + current_idx: 0, + } + } +} + +pub struct UserBufferIterator { + buffers: Vec<&'static mut [u8]>, + current_buffer: usize, + current_idx: usize, +} + +impl Iterator for UserBufferIterator { + type Item = *mut u8; + fn next(&mut self) -> Option<Self::Item> { + if self.current_buffer >= self.buffers.len() { + None + } else { + let r = &mut self.buffers[self.current_buffer][self.current_idx] as *mut _; + if self.current_idx + 1 == self.buffers[self.current_buffer].len() { + self.current_idx = 0; + self.current_buffer += 1; + } else { + self.current_idx += 1; + } + Some(r) + } + } +} \ No newline at end of file diff --git a/codes/os/src/sbi.rs b/codes/os/src/sbi.rs new file mode 100644 index 00000000..36e15fcc --- /dev/null +++ b/codes/os/src/sbi.rs @@ -0,0 +1,43 @@ +#![allow(unused)] + +const SBI_SET_TIMER: usize = 0; +const SBI_CONSOLE_PUTCHAR: usize = 1; +const SBI_CONSOLE_GETCHAR: usize = 2; +const SBI_CLEAR_IPI: usize = 3; +const SBI_SEND_IPI: usize = 4; +const SBI_REMOTE_FENCE_I: usize = 5; +const SBI_REMOTE_SFENCE_VMA: usize = 6; +const SBI_REMOTE_SFENCE_VMA_ASID: usize = 7; +const SBI_SHUTDOWN: usize = 8; + +#[inline(always)] +fn sbi_call(which: usize, arg0: usize, arg1: usize, arg2: usize) -> usize { + let mut ret; + unsafe { + llvm_asm!("ecall" + : "={x10}" (ret) + : "{x10}" (arg0), "{x11}" (arg1), "{x12}" (arg2), "{x17}" (which) + : "memory" + : "volatile" + ); + } + ret +} + +pub fn set_timer(timer: usize) { + sbi_call(SBI_SET_TIMER, timer, 0, 0); +} + +pub fn console_putchar(c: usize) { + sbi_call(SBI_CONSOLE_PUTCHAR, c, 0, 0); +} + +pub fn console_getchar() -> usize { + sbi_call(SBI_CONSOLE_GETCHAR, 0, 0, 0) +} + +pub fn shutdown() -> ! { + sbi_call(SBI_SHUTDOWN, 0, 0, 0); + panic!("It should shutdown!"); +} + diff --git a/codes/os/src/syscall/fs.rs b/codes/os/src/syscall/fs.rs new file mode 100644 index 00000000..6599b405 --- /dev/null +++ b/codes/os/src/syscall/fs.rs @@ -0,0 +1,116 @@ +use crate::mm::{ + UserBuffer, + translated_byte_buffer, + translated_refmut, + translated_str, +}; +use crate::task::{current_user_token, current_task}; +use crate::fs::{make_pipe, OpenFlags, open}; +use alloc::sync::Arc; +use alloc::vec; +use easy_fs::DiskInodeType; + +pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize { + let token = current_user_token(); + let task = current_task().unwrap(); + let inner = task.acquire_inner_lock(); + if fd >= inner.fd_table.len() { + return -1; + } + if let Some(file) = &inner.fd_table[fd] { + if !file.writable() { + return -1; + } + let file = file.clone(); + // release Task lock manually to avoid deadlock + drop(inner); + file.write( + UserBuffer::new(translated_byte_buffer(token, buf, len)) + ) as isize + } else { + -1 + } +} + +pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize { + let token = current_user_token(); + let task = current_task().unwrap(); + let inner = task.acquire_inner_lock(); + if fd >= inner.fd_table.len() { + return -1; + } + if let Some(file) = &inner.fd_table[fd] { + let file = file.clone(); + if !file.readable() { + return -1; + } + // release Task lock manually to avoid deadlock + drop(inner); + file.read( + UserBuffer::new(translated_byte_buffer(token, buf, len)) + ) as isize + } else { + -1 + } +} + +pub fn sys_open(path: *const u8, flags: u32) -> isize { + let task = current_task().unwrap(); + let token = current_user_token(); + // è¿™é‡Œä¼ å…¥çš„åœ°å€ä¸ºç”¨æˆ·çš„虚地å€ï¼Œå› æ¤è¦ä½¿ç”¨ç”¨æˆ·çš„虚地å€è¿›è¡Œæ˜ å°„ + let path = translated_str(token, path); + if let Some(inode) = open( + 0, + path.as_str(), + OpenFlags::from_bits(flags).unwrap(), + DiskInodeType::File + ) { + let mut inner = task.acquire_inner_lock(); + let fd = inner.alloc_fd(); + inner.fd_table[fd] = Some(inode); + fd as isize + } else { + -1 + } +} + +pub fn sys_close(fd: usize) -> isize { + let task = current_task().unwrap(); + let mut inner = task.acquire_inner_lock(); + if fd >= inner.fd_table.len() { + return -1; + } + if inner.fd_table[fd].is_none() { + return -1; + } + inner.fd_table[fd].take(); + 0 +} + +pub fn sys_pipe(pipe: *mut usize) -> isize { + let task = current_task().unwrap(); + let token = current_user_token(); + let mut inner = task.acquire_inner_lock(); + let (pipe_read, pipe_write) = make_pipe(); + let read_fd = inner.alloc_fd(); + inner.fd_table[read_fd] = Some(pipe_read); + let write_fd = inner.alloc_fd(); + inner.fd_table[write_fd] = Some(pipe_write); + *translated_refmut(token, pipe) = read_fd; + *translated_refmut(token, unsafe { pipe.add(1) }) = write_fd; + 0 +} + +pub fn sys_dup(fd: usize) -> isize { + let task = current_task().unwrap(); + let mut inner = task.acquire_inner_lock(); + if fd >= inner.fd_table.len() { + return -1; + } + if inner.fd_table[fd].is_none() { + return -1; + } + let new_fd = inner.alloc_fd(); + inner.fd_table[new_fd] = Some(Arc::clone(inner.fd_table[fd].as_ref().unwrap())); + new_fd as isize +} \ No newline at end of file diff --git a/codes/os/src/syscall/mod.rs b/codes/os/src/syscall/mod.rs new file mode 100644 index 00000000..4683d055 --- /dev/null +++ b/codes/os/src/syscall/mod.rs @@ -0,0 +1,39 @@ +const SYSCALL_DUP: usize = 24; +const SYSCALL_OPEN: usize = 56; +const SYSCALL_CLOSE: usize = 57; +const SYSCALL_PIPE: usize = 59; +const SYSCALL_READ: usize = 63; +const SYSCALL_WRITE: usize = 64; +const SYSCALL_EXIT: usize = 93; +const SYSCALL_YIELD: usize = 124; +const SYSCALL_GET_TIME: usize = 169; +const SYSCALL_GETPID: usize = 172; +const SYSCALL_FORK: usize = 220; +const SYSCALL_EXEC: usize = 221; +const SYSCALL_WAITPID: usize = 260; + +mod fs; +mod process; + +use fs::*; +use process::*; + +pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize { + match syscall_id { + SYSCALL_DUP=> sys_dup(args[0]), + SYSCALL_OPEN => sys_open(args[0] as *const u8, args[1] as u32), + SYSCALL_CLOSE => sys_close(args[0]), + SYSCALL_PIPE => sys_pipe(args[0] as *mut usize), + SYSCALL_READ => sys_read(args[0], args[1] as *const u8, args[2]), + SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]), + SYSCALL_EXIT => sys_exit(args[0] as i32), + SYSCALL_YIELD => sys_yield(), + SYSCALL_GET_TIME => sys_get_time(), + SYSCALL_GETPID => sys_getpid(), + SYSCALL_FORK => sys_fork(), + SYSCALL_EXEC => sys_exec(args[0] as *const u8, args[1] as *const usize), + SYSCALL_WAITPID => sys_waitpid(args[0] as isize, args[1] as *mut i32), + _ => panic!("Unsupported syscall_id: {}", syscall_id), + } +} + diff --git a/codes/os/src/syscall/process.rs b/codes/os/src/syscall/process.rs new file mode 100644 index 00000000..36e420e2 --- /dev/null +++ b/codes/os/src/syscall/process.rs @@ -0,0 +1,117 @@ +use crate::task::{ + suspend_current_and_run_next, + exit_current_and_run_next, + current_task, + current_user_token, + add_task, +}; +use crate::timer::get_time_ms; +use crate::mm::{ + translated_str, + translated_refmut, + translated_ref, +}; +use crate::fs::{ + open, + OpenFlags, +}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use alloc::vec; +use alloc::string::String; +use easy_fs::DiskInodeType; + +pub fn sys_exit(exit_code: i32) -> ! { + exit_current_and_run_next(exit_code); + panic!("Unreachable in sys_exit!"); +} + +pub fn sys_yield() -> isize { + suspend_current_and_run_next(); + 0 +} + +pub fn sys_get_time() -> isize { + get_time_ms() as isize +} + +pub fn sys_getpid() -> isize { + current_task().unwrap().pid.0 as isize +} + +pub fn sys_fork() -> isize { + let current_task = current_task().unwrap(); + let new_task = current_task.fork(); + let new_pid = new_task.pid.0; + // modify trap context of new_task, because it returns immediately after switching + let trap_cx = new_task.acquire_inner_lock().get_trap_cx(); + // we do not have to move to next instruction since we have done it before + // for child process, fork returns 0 + trap_cx.x[10] = 0; + // add new task to scheduler + add_task(new_task); + new_pid as isize +} + +pub fn sys_exec(path: *const u8, mut args: *const usize) -> isize { + let token = current_user_token(); + let path = translated_str(token, path); + let mut args_vec: Vec<String> = Vec::new(); + loop { + let arg_str_ptr = *translated_ref(token, args); + if arg_str_ptr == 0 { + break; + } + args_vec.push(translated_str(token, arg_str_ptr as *const u8)); + unsafe { args = args.add(1); } + } + if let Some(app_inode) = open(0,path.as_str(), OpenFlags::RDONLY, DiskInodeType::File) { + let all_data = app_inode.read_all(); + let task = current_task().unwrap(); + let argc = args_vec.len(); + task.exec(all_data.as_slice(), args_vec); + // return argc because cx.x[10] will be covered with it later + argc as isize + } else { + -1 + } +} + +/// If there is not a child process whose pid is same as given, return -1. +/// Else if there is a child process but it is still running, return -2. +pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize { + let task = current_task().unwrap(); + // find a child process + + // ---- hold current PCB lock + let mut inner = task.acquire_inner_lock(); + if inner.children + .iter() + .find(|p| {pid == -1 || pid as usize == p.getpid()}) + .is_none() { + return -1; + // ---- release current PCB lock + } + let pair = inner.children + .iter() + .enumerate() + .find(|(_, p)| { + // ++++ temporarily hold child PCB lock + p.acquire_inner_lock().is_zombie() && (pid == -1 || pid as usize == p.getpid()) + // ++++ release child PCB lock + }); + if let Some((idx, _)) = pair { + let child = inner.children.remove(idx); + // confirm that child will be deallocated after being removed from children list + assert_eq!(Arc::strong_count(&child), 1); + let found_pid = child.getpid(); + // ++++ temporarily hold child lock + let exit_code = child.acquire_inner_lock().exit_code; + // ++++ release child PCB lock + *translated_refmut(inner.memory_set.token(), exit_code_ptr) = exit_code; + found_pid as isize + } else { + -2 + } + // ---- release current PCB lock automatically +} \ No newline at end of file diff --git a/codes/os/src/task/context.rs b/codes/os/src/task/context.rs new file mode 100644 index 00000000..a91f45db --- /dev/null +++ b/codes/os/src/task/context.rs @@ -0,0 +1,18 @@ +use crate::trap::trap_return; + +#[derive(Debug)] +#[repr(C)] +pub struct TaskContext { + ra: usize, + s: [usize; 12], +} + +impl TaskContext { + pub fn goto_trap_return() -> Self { + Self { + ra: trap_return as usize, + s: [0; 12], + } + } +} + diff --git a/codes/os/src/task/manager.rs b/codes/os/src/task/manager.rs new file mode 100644 index 00000000..ed223916 --- /dev/null +++ b/codes/os/src/task/manager.rs @@ -0,0 +1,34 @@ +use super::TaskControlBlock; +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use spin::Mutex; +use lazy_static::*; + +pub struct TaskManager { + ready_queue: VecDeque<Arc<TaskControlBlock>>, +} + +/// A simple FIFO scheduler. +impl TaskManager { + pub fn new() -> Self { + Self { ready_queue: VecDeque::new(), } + } + pub fn add(&mut self, task: Arc<TaskControlBlock>) { + self.ready_queue.push_back(task); + } + pub fn fetch(&mut self) -> Option<Arc<TaskControlBlock>> { + self.ready_queue.pop_front() + } +} + +lazy_static! { + pub static ref TASK_MANAGER: Mutex<TaskManager> = Mutex::new(TaskManager::new()); +} + +pub fn add_task(task: Arc<TaskControlBlock>) { + TASK_MANAGER.lock().add(task); +} + +pub fn fetch_task() -> Option<Arc<TaskControlBlock>> { + TASK_MANAGER.lock().fetch() +} \ No newline at end of file diff --git a/codes/os/src/task/mod.rs b/codes/os/src/task/mod.rs new file mode 100644 index 00000000..07ead764 --- /dev/null +++ b/codes/os/src/task/mod.rs @@ -0,0 +1,92 @@ +mod context; +mod switch; +mod task; +mod manager; +mod processor; +mod pid; + +use crate::fs::{open, OpenFlags}; +use easy_fs::DiskInodeType; +use switch::__switch; +use task::{TaskControlBlock, TaskStatus}; +use alloc::sync::Arc; +use alloc::vec; +use manager::fetch_task; +use lazy_static::*; +pub use context::TaskContext; + +pub use processor::{ + run_tasks, + current_task, + current_user_token, + current_trap_cx, + take_current_task, + schedule, +}; +pub use manager::add_task; +pub use pid::{PidHandle, pid_alloc, KernelStack}; + +pub fn suspend_current_and_run_next() { + // There must be an application running. + let task = take_current_task().unwrap(); + + // ---- hold current PCB lock + let mut task_inner = task.acquire_inner_lock(); + //task_inner.print_cx(); + let task_cx_ptr2 = task_inner.get_task_cx_ptr2(); + // Change status to Ready + task_inner.task_status = TaskStatus::Ready; + drop(task_inner); + // ---- release current PCB lock + + // push back to ready queue. + add_task(task); + + // jump to scheduling cycle + schedule(task_cx_ptr2); +} + +pub fn exit_current_and_run_next(exit_code: i32) { + // take from Processor + let task = take_current_task().unwrap(); + // **** hold current PCB lock + let mut inner = task.acquire_inner_lock(); + // Change status to Zombie + inner.task_status = TaskStatus::Zombie; + // Record exit code + inner.exit_code = exit_code; + // do not move to its parent but under initproc + + // ++++++ hold initproc PCB lock here + { + let mut initproc_inner = INITPROC.acquire_inner_lock(); + for child in inner.children.iter() { + child.acquire_inner_lock().parent = Some(Arc::downgrade(&INITPROC)); + initproc_inner.children.push(child.clone()); + } + } + // ++++++ release parent PCB lock here + + inner.children.clear(); + // deallocate user space + inner.memory_set.recycle_data_pages(); + drop(inner); + // **** release current PCB lock + // drop task manually to maintain rc correctly + drop(task); + // we do not have to save task context + let _unused: usize = 0; + schedule(&_unused as *const _); +} + +lazy_static! { + pub static ref INITPROC: Arc<TaskControlBlock> = Arc::new({ + let inode = open(0,"initproc", OpenFlags::RDONLY, DiskInodeType::File).unwrap(); + let v = inode.read_all(); + TaskControlBlock::new(v.as_slice()) + }); +} + +pub fn add_initproc() { + add_task(INITPROC.clone()); +} diff --git a/codes/os/src/task/pid.rs b/codes/os/src/task/pid.rs new file mode 100644 index 00000000..7cfdb83d --- /dev/null +++ b/codes/os/src/task/pid.rs @@ -0,0 +1,105 @@ +use alloc::vec::Vec; +use lazy_static::*; +use spin::Mutex; +use crate::mm::{KERNEL_SPACE, MapPermission, VirtAddr}; +use crate::config::{ + PAGE_SIZE, + TRAMPOLINE, + KERNEL_STACK_SIZE, +}; + +struct PidAllocator { + current: usize, + recycled: Vec<usize>, +} + +impl PidAllocator { + pub fn new() -> Self { + PidAllocator { + current: 0, + recycled: Vec::new(), + } + } + pub fn alloc(&mut self) -> PidHandle { + if let Some(pid) = self.recycled.pop() { + PidHandle(pid) + } else { + self.current += 1; + PidHandle(self.current - 1) + } + } + pub fn dealloc(&mut self, pid: usize) { + assert!(pid < self.current); + assert!( + self.recycled.iter().find(|ppid| **ppid == pid).is_none(), + "pid {} has been deallocated!", pid + ); + self.recycled.push(pid); + } +} + +lazy_static! { + static ref PID_ALLOCATOR : Mutex<PidAllocator> = Mutex::new(PidAllocator::new()); +} + +pub struct PidHandle(pub usize); + +impl Drop for PidHandle { + fn drop(&mut self) { + //println!("drop pid {}", self.0); + PID_ALLOCATOR.lock().dealloc(self.0); + } +} + +pub fn pid_alloc() -> PidHandle { + PID_ALLOCATOR.lock().alloc() +} + +/// Return (bottom, top) of a kernel stack in kernel space. +pub fn kernel_stack_position(app_id: usize) -> (usize, usize) { + let top = TRAMPOLINE - app_id * (KERNEL_STACK_SIZE + PAGE_SIZE); + let bottom = top - KERNEL_STACK_SIZE; + (bottom, top) +} + +pub struct KernelStack { + pid: usize, +} + +impl KernelStack { + pub fn new(pid_handle: &PidHandle) -> Self { + let pid = pid_handle.0; + let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(pid); + KERNEL_SPACE + .lock() + .insert_framed_area( + kernel_stack_bottom.into(), + kernel_stack_top.into(), + MapPermission::R | MapPermission::W, + ); + KernelStack { + pid: pid_handle.0, + } + } + pub fn push_on_top<T>(&self, value: T) -> *mut T where + T: Sized, { + let kernel_stack_top = self.get_top(); + let ptr_mut = (kernel_stack_top - core::mem::size_of::<T>()) as *mut T; + unsafe { *ptr_mut = value; } + ptr_mut + } + pub fn get_top(&self) -> usize { + let (_, kernel_stack_top) = kernel_stack_position(self.pid); + kernel_stack_top + } +} + +impl Drop for KernelStack { + fn drop(&mut self) { + let (kernel_stack_bottom, _) = kernel_stack_position(self.pid); + let kernel_stack_bottom_va: VirtAddr = kernel_stack_bottom.into(); + KERNEL_SPACE + .lock() + .remove_area_with_start_vpn(kernel_stack_bottom_va.into()); + } +} \ No newline at end of file diff --git a/codes/os/src/task/processor.rs b/codes/os/src/task/processor.rs new file mode 100644 index 00000000..9cdced4e --- /dev/null +++ b/codes/os/src/task/processor.rs @@ -0,0 +1,95 @@ +use super::TaskControlBlock; +use alloc::sync::Arc; +use core::{borrow::Borrow, cell::RefCell}; +use lazy_static::*; +use super::{fetch_task, TaskStatus}; +use super::__switch; +use crate::trap::TrapContext; + +pub struct Processor { + inner: RefCell<ProcessorInner>, +} + +unsafe impl Sync for Processor {} + +struct ProcessorInner { + current: Option<Arc<TaskControlBlock>>, + idle_task_cx_ptr: usize, +} + +impl Processor { + pub fn new() -> Self { + Self { + inner: RefCell::new(ProcessorInner { + current: None, + idle_task_cx_ptr: 0, + }), + } + } + fn get_idle_task_cx_ptr2(&self) -> *const usize { + let inner = self.inner.borrow(); + &inner.idle_task_cx_ptr as *const usize + } + pub fn run(&self) { + loop { + if let Some(task) = fetch_task() { + let idle_task_cx_ptr2 = self.get_idle_task_cx_ptr2(); + // acquire + let mut task_inner = task.acquire_inner_lock(); + let next_task_cx_ptr2 = task_inner.get_task_cx_ptr2(); + task_inner.task_status = TaskStatus::Running; + drop(task_inner); + // release + self.inner.borrow_mut().current = Some(task); + unsafe { + __switch( + idle_task_cx_ptr2, + next_task_cx_ptr2, + ); + } + } + } + } + pub fn take_current(&self) -> Option<Arc<TaskControlBlock>> { + self.inner.borrow_mut().current.take() + } + pub fn current(&self) -> Option<Arc<TaskControlBlock>> { + self.inner.borrow().current.as_ref().map(|task| Arc::clone(task)) + } +} + +lazy_static! { + pub static ref PROCESSOR: Processor = Processor::new(); +} + +pub fn run_tasks() { + PROCESSOR.run(); +} + +pub fn take_current_task() -> Option<Arc<TaskControlBlock>> { + PROCESSOR.take_current() +} + +pub fn current_task() -> Option<Arc<TaskControlBlock>> { + PROCESSOR.current() +} + +pub fn current_user_token() -> usize { + let task = current_task().unwrap(); + let token = task.acquire_inner_lock().get_user_token(); + token +} + +pub fn current_trap_cx() -> &'static mut TrapContext { + current_task().unwrap().acquire_inner_lock().get_trap_cx() +} + +pub fn schedule(switched_task_cx_ptr2: *const usize) { + let idle_task_cx_ptr2 = PROCESSOR.get_idle_task_cx_ptr2(); + unsafe { + __switch( + switched_task_cx_ptr2, + idle_task_cx_ptr2, + ); + } +} diff --git a/codes/os/src/task/switch.S b/codes/os/src/task/switch.S new file mode 100644 index 00000000..262511fe --- /dev/null +++ b/codes/os/src/task/switch.S @@ -0,0 +1,37 @@ +.altmacro +.macro SAVE_SN n + sd s\n, (\n+1)*8(sp) +.endm +.macro LOAD_SN n + ld s\n, (\n+1)*8(sp) +.endm + .section .text + .globl __switch +__switch: + # __switch( + # current_task_cx_ptr2: &*const TaskContext, + # next_task_cx_ptr2: &*const TaskContext + # ) + # push TaskContext to current sp and save its address to where a0 points to + addi sp, sp, -13*8 + sd sp, 0(a0) + # fill TaskContext with ra & s0-s11 + sd ra, 0(sp) + .set n, 0 + .rept 12 + SAVE_SN %n + .set n, n + 1 + .endr + # ready for loading TaskContext a1 points to + ld sp, 0(a1) + # load registers in the TaskContext + ld ra, 0(sp) + .set n, 0 + .rept 12 + LOAD_SN %n + .set n, n + 1 + .endr + # pop TaskContext + addi sp, sp, 13*8 + ret + diff --git a/codes/os/src/task/switch.rs b/codes/os/src/task/switch.rs new file mode 100644 index 00000000..867fcb1e --- /dev/null +++ b/codes/os/src/task/switch.rs @@ -0,0 +1,8 @@ +global_asm!(include_str!("switch.S")); + +extern "C" { + pub fn __switch( + current_task_cx_ptr2: *const usize, + next_task_cx_ptr2: *const usize + ); +} diff --git a/codes/os/src/task/task.rs b/codes/os/src/task/task.rs new file mode 100644 index 00000000..c583d5d6 --- /dev/null +++ b/codes/os/src/task/task.rs @@ -0,0 +1,245 @@ +use crate::{console::print, mm::{ + MemorySet, + PhysPageNum, + KERNEL_SPACE, + VirtAddr, + translated_refmut, +}}; +use crate::trap::{TrapContext, trap_handler}; +use crate::config::{TRAP_CONTEXT}; +use super::TaskContext; +use super::{PidHandle, pid_alloc, KernelStack}; +use alloc::sync::{Weak, Arc}; +use alloc::vec; +use alloc::vec::Vec; +use alloc::string::String; +use spin::{Mutex, MutexGuard}; +use crate::fs::{File, Stdin, Stdout}; + +pub struct TaskControlBlock { + // immutable + pub pid: PidHandle, + pub kernel_stack: KernelStack, + // mutable + inner: Mutex<TaskControlBlockInner>, +} + +pub struct TaskControlBlockInner { + pub trap_cx_ppn: PhysPageNum, + pub base_size: usize, + pub task_cx_ptr: usize, + pub task_status: TaskStatus, + pub memory_set: MemorySet, + pub parent: Option<Weak<TaskControlBlock>>, + pub children: Vec<Arc<TaskControlBlock>>, + pub exit_code: i32, + pub fd_table: Vec<Option<Arc<dyn File + Send + Sync>>>, +} + +impl TaskControlBlockInner { + pub fn get_task_cx_ptr2(&self) -> *const usize { + &self.task_cx_ptr as *const usize + } + pub fn get_trap_cx(&self) -> &'static mut TrapContext { + self.trap_cx_ppn.get_mut() + } + pub fn get_user_token(&self) -> usize { + self.memory_set.token() + } + fn get_status(&self) -> TaskStatus { + self.task_status + } + pub fn is_zombie(&self) -> bool { + self.get_status() == TaskStatus::Zombie + } + pub fn alloc_fd(&mut self) -> usize { + if let Some(fd) = (0..self.fd_table.len()) + .find(|fd| self.fd_table[*fd].is_none()) { + fd + } else { + self.fd_table.push(None); + self.fd_table.len() - 1 + } + } + pub fn print_cx(&self) { + unsafe{ + println!("task_cx = {:?}", *(self.task_cx_ptr as *const TaskContext) ); + } + } +} + + + +impl TaskControlBlock { + pub fn acquire_inner_lock(&self) -> MutexGuard<TaskControlBlockInner> { + self.inner.lock() + } + pub fn new(elf_data: &[u8]) -> Self { + // memory_set with elf program headers/trampoline/trap context/user stack + let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data); + let trap_cx_ppn = memory_set + .translate(VirtAddr::from(TRAP_CONTEXT).into()) + .unwrap() + .ppn(); + // alloc a pid and a kernel stack in kernel space + let pid_handle = pid_alloc(); + let kernel_stack = KernelStack::new(&pid_handle); + let kernel_stack_top = kernel_stack.get_top(); + // push a task context which goes to trap_return to the top of kernel stack + let task_cx_ptr = kernel_stack.push_on_top(TaskContext::goto_trap_return()); + let task_control_block = Self { + pid: pid_handle, + kernel_stack, + inner: Mutex::new(TaskControlBlockInner { + trap_cx_ppn, + base_size: user_sp, + task_cx_ptr: task_cx_ptr as usize, + task_status: TaskStatus::Ready, + memory_set, + parent: None, + children: Vec::new(), + exit_code: 0, + fd_table: vec![ + // 0 -> stdin + Some(Arc::new(Stdin)), + // 1 -> stdout + Some(Arc::new(Stdout)), + // 2 -> stderr + Some(Arc::new(Stdout)), + ], + }), + }; + // prepare TrapContext in user space + let trap_cx = task_control_block.acquire_inner_lock().get_trap_cx(); + *trap_cx = TrapContext::app_init_context( + entry_point, + user_sp, + KERNEL_SPACE.lock().token(), + kernel_stack_top, + trap_handler as usize, + ); + task_control_block + } + pub fn exec(&self, elf_data: &[u8], args: Vec<String>) { + // memory_set with elf program headers/trampoline/trap context/user stack + let (memory_set, mut user_sp, entry_point) = MemorySet::from_elf(elf_data); + let trap_cx_ppn = memory_set + .translate(VirtAddr::from(TRAP_CONTEXT).into()) + .unwrap() + .ppn(); + // push arguments on user stack + // spå‡å°[傿•°ä¸ªæ•°*usize]ï¼Œç”¨äºŽå˜æ”¾å‚æ•°åœ°å€ + user_sp -= (args.len() + 1) * core::mem::size_of::<usize>(); + let argv_base = user_sp; + + let mut argv: Vec<_> = (0..=args.len()) + .map(|arg| { + translated_refmut( + memory_set.token(), + (argv_base + arg * core::mem::size_of::<usize>()) as *mut usize + ) + }) + .collect(); + + // argv的类型实际为 Vec<&'static mut T> + // 所以这里直接往对应的地å€å†™æ•°æ® + *argv[args.len()] = 0; + for i in 0..args.len() { + // spç»§ç»å‘下增长,留空间给æ¯ä¸ªå‚æ•° + // +1æ˜¯å› ä¸ºéœ€è¦'\0' + user_sp -= args[i].len() + 1; + *argv[i] = user_sp; + let mut p = user_sp; + // å°†å—ç¬¦å†™å…¥æ ˆ [user_sp, user_sp + len] + for c in args[i].as_bytes() { + *translated_refmut(memory_set.token(), p as *mut u8) = *c; + p += 1; + } + *translated_refmut(memory_set.token(), p as *mut u8) = 0; + } + // make the user_sp aligned to 8B for k210 platform + user_sp -= user_sp % core::mem::size_of::<usize>(); + + // **** hold current PCB lock + let mut inner = self.acquire_inner_lock(); + // substitute memory_set + inner.memory_set = memory_set; + // update trap_cx ppn + inner.trap_cx_ppn = trap_cx_ppn; + // initialize trap_cx + let mut trap_cx = TrapContext::app_init_context( + entry_point, + user_sp, + KERNEL_SPACE.lock().token(), + self.kernel_stack.get_top(), + trap_handler as usize, + ); + trap_cx.x[10] = args.len(); + trap_cx.x[11] = argv_base; + *inner.get_trap_cx() = trap_cx; + // **** release current PCB lock + } + pub fn fork(self: &Arc<TaskControlBlock>) -> Arc<TaskControlBlock> { + // ---- hold parent PCB lock + let mut parent_inner = self.acquire_inner_lock(); + // copy user space(include trap context) + let memory_set = MemorySet::from_existed_user( + &parent_inner.memory_set + ); + let trap_cx_ppn = memory_set + .translate(VirtAddr::from(TRAP_CONTEXT).into()) + .unwrap() + .ppn(); + // alloc a pid and a kernel stack in kernel space + let pid_handle = pid_alloc(); + let kernel_stack = KernelStack::new(&pid_handle); + let kernel_stack_top = kernel_stack.get_top(); + // push a goto_trap_return task_cx on the top of kernel stack + let task_cx_ptr = kernel_stack.push_on_top(TaskContext::goto_trap_return()); + // copy fd table + let mut new_fd_table: Vec<Option<Arc<dyn File + Send + Sync>>> = Vec::new(); + for fd in parent_inner.fd_table.iter() { + if let Some(file) = fd { + new_fd_table.push(Some(file.clone())); + } else { + new_fd_table.push(None); + } + } + let task_control_block = Arc::new(TaskControlBlock { + pid: pid_handle, + kernel_stack, + inner: Mutex::new(TaskControlBlockInner { + trap_cx_ppn, + base_size: parent_inner.base_size, + task_cx_ptr: task_cx_ptr as usize, + task_status: TaskStatus::Ready, + memory_set, + parent: Some(Arc::downgrade(self)), + children: Vec::new(), + exit_code: 0, + fd_table: new_fd_table, + }), + }); + // add child + parent_inner.children.push(task_control_block.clone()); + // modify kernel_sp in trap_cx + // **** acquire child PCB lock + let trap_cx = task_control_block.acquire_inner_lock().get_trap_cx(); + // **** release child PCB lock + trap_cx.kernel_sp = kernel_stack_top; + // return + task_control_block + // ---- release parent PCB lock + } + pub fn getpid(&self) -> usize { + self.pid.0 + } + +} + +#[derive(Copy, Clone, PartialEq)] +pub enum TaskStatus { + Ready, + Running, + Zombie, +} \ No newline at end of file diff --git a/codes/os/src/timer.rs b/codes/os/src/timer.rs new file mode 100644 index 00000000..92d50e3a --- /dev/null +++ b/codes/os/src/timer.rs @@ -0,0 +1,18 @@ +use riscv::register::time; +use crate::sbi::set_timer; +use crate::config::CLOCK_FREQ; + +const TICKS_PER_SEC: usize = 100; +const MSEC_PER_SEC: usize = 1000; + +pub fn get_time() -> usize { + time::read() +} + +pub fn get_time_ms() -> usize { + time::read() / (CLOCK_FREQ / MSEC_PER_SEC) +} + +pub fn set_next_trigger() { + set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC); +} \ No newline at end of file diff --git a/codes/os/src/trap/context.rs b/codes/os/src/trap/context.rs new file mode 100644 index 00000000..16f81415 --- /dev/null +++ b/codes/os/src/trap/context.rs @@ -0,0 +1,37 @@ +use riscv::register::sstatus::{Sstatus, self, SPP}; + +#[repr(C)] +#[derive(Debug)] +pub struct TrapContext { + pub x: [usize; 32], + pub sstatus: Sstatus, + pub sepc: usize, + pub kernel_satp: usize, + pub kernel_sp: usize, + pub trap_handler: usize, +} + +impl TrapContext { + pub fn set_sp(&mut self, sp: usize) { self.x[2] = sp; } + pub fn app_init_context( + entry: usize, + sp: usize, + kernel_satp: usize, + kernel_sp: usize, + trap_handler: usize, + ) -> Self { + let mut sstatus = sstatus::read(); + // set CPU privilege to User after trapping back + sstatus.set_spp(SPP::User); + let mut cx = Self { + x: [0; 32], + sstatus, + sepc: entry, + kernel_satp, + kernel_sp, + trap_handler, + }; + cx.set_sp(sp); + cx + } +} diff --git a/codes/os/src/trap/mod.rs b/codes/os/src/trap/mod.rs new file mode 100644 index 00000000..f83560b2 --- /dev/null +++ b/codes/os/src/trap/mod.rs @@ -0,0 +1,117 @@ +mod context; + +use riscv::register::{ + mtvec::TrapMode, + stvec, + scause::{ + self, + Trap, + Exception, + Interrupt, + }, + stval, + sie, +}; +use crate::syscall::syscall; +use crate::task::{ + exit_current_and_run_next, + suspend_current_and_run_next, + current_user_token, + current_trap_cx, +}; +use crate::timer::set_next_trigger; +use crate::config::{TRAP_CONTEXT, TRAMPOLINE}; + +global_asm!(include_str!("trap.S")); + +pub fn init() { + set_kernel_trap_entry(); +} + +fn set_kernel_trap_entry() { + unsafe { + stvec::write(trap_from_kernel as usize, TrapMode::Direct); + } +} + +fn set_user_trap_entry() { + unsafe { + stvec::write(TRAMPOLINE as usize, TrapMode::Direct); + } +} + +pub fn enable_timer_interrupt() { + unsafe { sie::set_stimer(); } +} + +#[no_mangle] +pub fn trap_handler() -> ! { + set_kernel_trap_entry(); + let scause = scause::read(); + let stval = stval::read(); + match scause.cause() { + Trap::Exception(Exception::UserEnvCall) => { + // jump to next instruction anyway + let mut cx = current_trap_cx(); + cx.sepc += 4; + // get system call return value + let result = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]); + // cx is changed during sys_exec, so we have to call it again + cx = current_trap_cx(); + cx.x[10] = result as usize; + } + Trap::Exception(Exception::StoreFault) | + Trap::Exception(Exception::StorePageFault) | + Trap::Exception(Exception::InstructionFault) | + Trap::Exception(Exception::InstructionPageFault) | + Trap::Exception(Exception::LoadFault) | + Trap::Exception(Exception::LoadPageFault) => { + println!( + "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, core dumped.", + scause.cause(), + stval, + current_trap_cx().sepc, + ); + // page fault exit code + exit_current_and_run_next(-2); + } + Trap::Exception(Exception::IllegalInstruction) => { + println!("[kernel] IllegalInstruction in application, core dumped."); + // illegal instruction exit code + exit_current_and_run_next(-3); + } + Trap::Interrupt(Interrupt::SupervisorTimer) => { + set_next_trigger(); + suspend_current_and_run_next(); + } + _ => { + panic!("Unsupported trap {:?}, stval = {:#x}!", scause.cause(), stval); + } + } + //println!("before trap_return"); + trap_return(); +} + +#[no_mangle] +pub fn trap_return() -> ! { + set_user_trap_entry(); + let trap_cx_ptr = TRAP_CONTEXT; + let user_satp = current_user_token(); + extern "C" { + fn __alltraps(); + fn __restore(); + } + let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE; + unsafe { + llvm_asm!("fence.i" :::: "volatile"); + llvm_asm!("jr $0" :: "r"(restore_va), "{a0}"(trap_cx_ptr), "{a1}"(user_satp) :: "volatile"); + } + panic!("Unreachable in back_to_user!"); +} + +#[no_mangle] +pub fn trap_from_kernel() -> ! { + panic!("a trap {:?} from kernel!", scause::read().cause()); +} + +pub use context::{TrapContext}; diff --git a/codes/os/src/trap/trap.S b/codes/os/src/trap/trap.S new file mode 100644 index 00000000..c0e2d153 --- /dev/null +++ b/codes/os/src/trap/trap.S @@ -0,0 +1,69 @@ +.altmacro +.macro SAVE_GP n + sd x\n, \n*8(sp) +.endm +.macro LOAD_GP n + ld x\n, \n*8(sp) +.endm + .section .text.trampoline + .globl __alltraps + .globl __restore + .align 2 +__alltraps: + csrrw sp, sscratch, sp + # now sp->*TrapContext in user space, sscratch->user stack + # save other general purpose registers + sd x1, 1*8(sp) + # skip sp(x2), we will save it later + sd x3, 3*8(sp) + # skip tp(x4), application does not use it + # save x5~x31 + .set n, 5 + .rept 27 + SAVE_GP %n + .set n, n+1 + .endr + # we can use t0/t1/t2 freely, because they have been saved in TrapContext + csrr t0, sstatus + csrr t1, sepc + sd t0, 32*8(sp) + sd t1, 33*8(sp) + # read user stack from sscratch and save it in TrapContext + csrr t2, sscratch + sd t2, 2*8(sp) + # load kernel_satp into t0 + ld t0, 34*8(sp) + # load trap_handler into t1 + ld t1, 36*8(sp) + # move to kernel_sp + ld sp, 35*8(sp) + # switch to kernel space + csrw satp, t0 + sfence.vma + # jump to trap_handler + jr t1 + +__restore: + # a0: *TrapContext in user space(Constant); a1: user space token + # switch to user space + csrw satp, a1 + sfence.vma + csrw sscratch, a0 + mv sp, a0 + # now sp points to TrapContext in user space, start restoring based on it + # restore sstatus/sepc + ld t0, 32*8(sp) + ld t1, 33*8(sp) + csrw sstatus, t0 + csrw sepc, t1 + # restore general purpose registers except x0/sp/tp + ld x1, 1*8(sp) + ld x3, 3*8(sp) + .set n, 5 + .rept 27 + LOAD_GP %n + .set n, n+1 + .endr + # back to user stack + ld sp, 2*8(sp) + sret diff --git a/codes/rust-toolchain b/codes/rust-toolchain new file mode 100644 index 00000000..a08f00d1 --- /dev/null +++ b/codes/rust-toolchain @@ -0,0 +1 @@ +nightly-2021-01-30 diff --git a/codes/user/.cargo/config b/codes/user/.cargo/config new file mode 100644 index 00000000..e5ded8a1 --- /dev/null +++ b/codes/user/.cargo/config @@ -0,0 +1,7 @@ +[build] +target = "riscv64gc-unknown-none-elf" + +[target.riscv64gc-unknown-none-elf] +rustflags = [ + "-Clink-args=-Tsrc/linker.ld", +] diff --git a/codes/user/.gitignore b/codes/user/.gitignore new file mode 100644 index 00000000..e420ee4b --- /dev/null +++ b/codes/user/.gitignore @@ -0,0 +1 @@ +target/* diff --git a/codes/user/Cargo.toml b/codes/user/Cargo.toml new file mode 100644 index 00000000..d50a0ba0 --- /dev/null +++ b/codes/user/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "user_lib" +version = "0.1.0" +authors = ["Yifan Wu <shinbokuow@163.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +buddy_system_allocator = "0.6" +bitflags = "1.2.1" \ No newline at end of file diff --git a/codes/user/Makefile b/codes/user/Makefile new file mode 100644 index 00000000..e2eaf994 --- /dev/null +++ b/codes/user/Makefile @@ -0,0 +1,23 @@ +TARGET := riscv64gc-unknown-none-elf +MODE := release +APP_DIR := src/bin +TARGET_DIR := target/$(TARGET)/$(MODE) +APPS := $(wildcard $(APP_DIR)/*.rs) +ELFS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%, $(APPS)) +BINS := $(patsubst $(APP_DIR)/%.rs, $(TARGET_DIR)/%.bin, $(APPS)) + +OBJDUMP := rust-objdump --arch-name=riscv64 +OBJCOPY := rust-objcopy --binary-architecture=riscv64 + +elf: $(APPS) + @cargo build --release + +binary: elf + $(foreach elf, $(ELFS), $(OBJCOPY) $(elf) --strip-all -O binary $(patsubst $(TARGET_DIR)/%, $(TARGET_DIR)/%.bin, $(elf));) + +build: binary + +clean: + @cargo clean + +.PHONY: elf binary build clean diff --git a/codes/user/src/bin/cat.rs b/codes/user/src/bin/cat.rs new file mode 100644 index 00000000..988164d3 --- /dev/null +++ b/codes/user/src/bin/cat.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; +extern crate alloc; + +use user_lib::{ + open, + OpenFlags, + close, + read, +}; +use alloc::string::String; + +#[no_mangle] +pub fn main(argc: usize, argv: &[&str]) -> i32 { + assert!(argc == 2); + let fd = open(argv[1], OpenFlags::RDONLY); + if fd == -1 { + panic!("Error occured when opening file"); + } + let fd = fd as usize; + let mut buf = [0u8; 16]; + let mut s = String::new(); + loop { + let size = read(fd, &mut buf) as usize; + if size == 0 { break; } + s.push_str(core::str::from_utf8(&buf[..size]).unwrap()); + } + println!("{}", s); + close(fd); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/cmdline_args.rs b/codes/user/src/bin/cmdline_args.rs new file mode 100644 index 00000000..b49ec332 --- /dev/null +++ b/codes/user/src/bin/cmdline_args.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +#[macro_use] +extern crate user_lib; + +#[no_mangle] +pub fn main(argc: usize, argv: &[&str]) -> i32 { + println!("argc = {}", argc); + for i in 0..argc { + println!("argv[{}] = {}", i, argv[i]); + } + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/exit.rs b/codes/user/src/bin/exit.rs new file mode 100644 index 00000000..5bde550b --- /dev/null +++ b/codes/user/src/bin/exit.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; +use user_lib::{fork, yield_, waitpid, exit, wait}; + +const MAGIC: i32 = -0x10384; + +#[no_mangle] +pub fn main() -> i32 { + println!("I am the parent. Forking the child..."); + let pid = fork(); + if pid == 0 { + println!("I am the child."); + for _ in 0..7 { yield_(); } + exit(MAGIC); + } else { + println!("I am parent, fork a child pid {}", pid); + } + println!("I am the parent, waiting now.."); + let mut xstate: i32 = 0; + assert!(waitpid(pid as usize, &mut xstate) == pid && xstate == MAGIC); + assert!(waitpid(pid as usize, &mut xstate) < 0 && wait(&mut xstate) <= 0); + println!("waitpid {} ok.", pid); + println!("exit pass."); + 0 +} + diff --git a/codes/user/src/bin/fantastic_text.rs b/codes/user/src/bin/fantastic_text.rs new file mode 100644 index 00000000..bb51db30 --- /dev/null +++ b/codes/user/src/bin/fantastic_text.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +macro_rules! color_text { + ($text:expr, $color:expr) => {{ + format_args!("\x1b[{}m{}\x1b[0m", $color, $text) + }}; +} + +#[no_mangle] +pub fn main() -> i32 { + println!( + "{}{}{}{}{} {}{}{}{} {}{}{}{}{}{}", + color_text!("H", 31), + color_text!("e", 32), + color_text!("l", 33), + color_text!("l", 34), + color_text!("o", 35), + color_text!("R", 36), + color_text!("u", 37), + color_text!("s", 90), + color_text!("t", 91), + color_text!("u", 92), + color_text!("C", 93), + color_text!("o", 94), + color_text!("r", 95), + color_text!("e", 96), + color_text!("!", 97), + ); + + let text = + "reguler \x1b[4munderline\x1b[24m \x1b[7mreverse\x1b[27m \x1b[9mstrikethrough\x1b[29m"; + println!("\x1b[47m{}\x1b[0m", color_text!(text, 30)); + for i in 31..38 { + println!("{}", color_text!(text, i)); + } + for i in 90..98 { + println!("{}", color_text!(text, i)); + } + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/filetest_simple.rs b/codes/user/src/bin/filetest_simple.rs new file mode 100644 index 00000000..60fda6aa --- /dev/null +++ b/codes/user/src/bin/filetest_simple.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{ + open, + close, + read, + write, + OpenFlags, +}; + +#[no_mangle] +pub fn main() -> i32 { + let test_str = "Hello, world!"; + let filea = "filea\0"; + let fd = open(filea, OpenFlags::CREATE | OpenFlags::WRONLY); + assert!(fd > 0); + let fd = fd as usize; + write(fd, test_str.as_bytes()); + close(fd); + + let fd = open(filea, OpenFlags::RDONLY); + assert!(fd > 0); + let fd = fd as usize; + let mut buffer = [0u8; 100]; + let read_len = read(fd, &mut buffer) as usize; + close(fd); + + assert_eq!( + test_str, + core::str::from_utf8(&buffer[..read_len]).unwrap(), + ); + println!("file_test passed!"); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/forktest.rs b/codes/user/src/bin/forktest.rs new file mode 100644 index 00000000..fea6967c --- /dev/null +++ b/codes/user/src/bin/forktest.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{fork, wait, exit}; + +const MAX_CHILD: usize = 40; + +#[no_mangle] +pub fn main() -> i32 { + for i in 0..MAX_CHILD { + let pid = fork(); + if pid == 0 { + println!("I am child {}", i); + exit(0); + } else { + println!("forked child pid = {}", pid); + } + assert!(pid > 0); + } + let mut exit_code: i32 = 0; + for _ in 0..MAX_CHILD { + if wait(&mut exit_code) <= 0 { + panic!("wait stopped early"); + } + } + if wait(&mut exit_code) > 0 { + panic!("wait got too many"); + } + println!("forktest pass."); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/forktest2.rs b/codes/user/src/bin/forktest2.rs new file mode 100644 index 00000000..d08a4120 --- /dev/null +++ b/codes/user/src/bin/forktest2.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{fork, wait, getpid, exit, sleep, get_time}; + +static NUM: usize = 30; + +#[no_mangle] +pub fn main() -> i32 { + for _ in 0..NUM { + let pid = fork(); + if pid == 0 { + let current_time = get_time(); + let sleep_length = (current_time as i32 as isize) * (current_time as i32 as isize) % 1000 + 1000; + println!("pid {} sleep for {} ms", getpid(), sleep_length); + sleep(sleep_length as usize); + println!("pid {} OK!", getpid()); + exit(0); + } + } + + let mut exit_code: i32 = 0; + for _ in 0..NUM { + assert!(wait(&mut exit_code) > 0); + assert_eq!(exit_code, 0); + } + assert!(wait(&mut exit_code) < 0); + println!("forktest2 test passed!"); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/forktest_simple.rs b/codes/user/src/bin/forktest_simple.rs new file mode 100644 index 00000000..821fba64 --- /dev/null +++ b/codes/user/src/bin/forktest_simple.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{fork, getpid, wait}; + +#[no_mangle] +pub fn main() -> i32 { + assert_eq!(wait(&mut 0i32), -1); + println!("sys_wait without child process test passed!"); + println!("parent start, pid = {}!", getpid()); + let pid = fork(); + if pid == 0 { + // child process + println!("hello child process!"); + 100 + } else { + // parent process + let mut exit_code: i32 = 0; + println!("ready waiting on parent process!"); + assert_eq!(pid, wait(&mut exit_code)); + assert_eq!(exit_code, 100); + println!("child process pid = {}, exit code = {}", pid, exit_code); + 0 + } +} \ No newline at end of file diff --git a/codes/user/src/bin/forktree.rs b/codes/user/src/bin/forktree.rs new file mode 100644 index 00000000..26954b7a --- /dev/null +++ b/codes/user/src/bin/forktree.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{sleep, getpid, fork, exit, yield_}; + +const DEPTH: usize = 4; + +fn fork_child(cur: &str, branch: char) { + let mut next = [0u8; DEPTH + 1]; + let l = cur.len(); + if l >= DEPTH { + return; + } + &mut next[..l].copy_from_slice(cur.as_bytes()); + next[l] = branch as u8; + if fork() == 0 { + fork_tree(core::str::from_utf8(&next[..l + 1]).unwrap()); + yield_(); + exit(0); + } +} + +fn fork_tree(cur: &str) { + println!("pid{}: {}", getpid(), cur); + fork_child(cur, '0'); + fork_child(cur, '1'); +} + +#[no_mangle] +pub fn main() -> i32 { + fork_tree(""); + sleep(3000); + 0 +} diff --git a/codes/user/src/bin/hello_world.rs b/codes/user/src/bin/hello_world.rs new file mode 100644 index 00000000..de4a6a92 --- /dev/null +++ b/codes/user/src/bin/hello_world.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +#[no_mangle] +pub fn main() -> i32 { + println!("Hello world from user mode program!"); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/initproc.rs b/codes/user/src/bin/initproc.rs new file mode 100644 index 00000000..99563f47 --- /dev/null +++ b/codes/user/src/bin/initproc.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{ + fork, + wait, + exec, + yield_, +}; + +#[no_mangle] +fn main() -> i32 { + if fork() == 0 { + exec("user_shell\0", &[0 as *const u8]); + } else { + loop { + let mut exit_code: i32 = 0; + let pid = wait(&mut exit_code); + if pid == -1 { + yield_(); + continue; + } + println!( + "[initproc] Released a zombie process, pid={}, exit_code={}", + pid, + exit_code, + ); + } + } + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/matrix.rs b/codes/user/src/bin/matrix.rs new file mode 100644 index 00000000..8ef2c044 --- /dev/null +++ b/codes/user/src/bin/matrix.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{fork, wait, yield_, exit, getpid, get_time}; + +static NUM: usize = 35; +const N: usize = 10; +static P: i32 = 10007; +type Arr = [[i32; N]; N]; + +fn work(times: isize) { + let mut a: Arr = Default::default(); + let mut b: Arr = Default::default(); + let mut c: Arr = Default::default(); + for i in 0..N { + for j in 0..N { + a[i][j] = 1; + b[i][j] = 1; + } + } + yield_(); + println!("pid {} is running ({} times)!.", getpid(), times); + for _ in 0..times { + for i in 0..N { + for j in 0..N { + c[i][j] = 0; + for k in 0..N { + c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % P; + } + } + } + for i in 0..N { + for j in 0..N { + a[i][j] = c[i][j]; + b[i][j] = c[i][j]; + } + } + } + println!("pid {} done!.", getpid()); + exit(0); +} + +#[no_mangle] +pub fn main() -> i32 { + for _ in 0..NUM { + let pid = fork(); + if pid == 0 { + let current_time = get_time(); + let times = (current_time as i32 as isize) * (current_time as i32 as isize) % 1000; + work(times * 10); + } + } + + println!("fork ok."); + + let mut exit_code: i32 = 0; + for _ in 0..NUM { + if wait(&mut exit_code) < 0 { + panic!("wait failed."); + } + } + assert!(wait(&mut exit_code) < 0); + println!("matrix passed."); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/pipe_large_test.rs b/codes/user/src/bin/pipe_large_test.rs new file mode 100644 index 00000000..121987be --- /dev/null +++ b/codes/user/src/bin/pipe_large_test.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +extern crate alloc; + +use user_lib::{fork, close, pipe, read, write, wait, get_time}; +use alloc::format; + +const LENGTH: usize = 3000; +#[no_mangle] +pub fn main() -> i32 { + // create pipes + // parent write to child + let mut down_pipe_fd = [0usize; 2]; + // child write to parent + let mut up_pipe_fd = [0usize; 2]; + pipe(&mut down_pipe_fd); + pipe(&mut up_pipe_fd); + let mut random_str = [0u8; LENGTH]; + if fork() == 0 { + // close write end of down pipe + close(down_pipe_fd[1]); + // close read end of up pipe + close(up_pipe_fd[0]); + assert_eq!(read(down_pipe_fd[0], &mut random_str) as usize, LENGTH); + close(down_pipe_fd[0]); + let sum: usize = random_str.iter().map(|v| *v as usize).sum::<usize>(); + println!("sum = {}(child)", sum); + let sum_str = format!("{}", sum); + write(up_pipe_fd[1], sum_str.as_bytes()); + close(up_pipe_fd[1]); + println!("Child process exited!"); + 0 + } else { + // close read end of down pipe + close(down_pipe_fd[0]); + // close write end of up pipe + close(up_pipe_fd[1]); + // generate a long random string + for i in 0..LENGTH { + random_str[i] = get_time() as u8; + } + // send it + assert_eq!(write(down_pipe_fd[1], &random_str) as usize, random_str.len()); + // close write end of down pipe + close(down_pipe_fd[1]); + // calculate sum(parent) + let sum: usize = random_str.iter().map(|v| *v as usize).sum::<usize>(); + println!("sum = {}(parent)", sum); + // recv sum(child) + let mut child_result = [0u8; 32]; + let result_len = read(up_pipe_fd[0], &mut child_result) as usize; + close(up_pipe_fd[0]); + // check + assert_eq!( + sum, + str::parse::<usize>( + core::str::from_utf8(&child_result[..result_len]).unwrap() + ).unwrap() + ); + let mut _unused: i32 = 0; + wait(&mut _unused); + println!("pipe_large_test passed!"); + 0 + } +} \ No newline at end of file diff --git a/codes/user/src/bin/pipetest.rs b/codes/user/src/bin/pipetest.rs new file mode 100644 index 00000000..c151fbdd --- /dev/null +++ b/codes/user/src/bin/pipetest.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{fork, close, pipe, read, write, wait}; + +static STR: &str = "Hello, world!"; + +#[no_mangle] +pub fn main() -> i32 { + // create pipe + let mut pipe_fd = [0usize; 2]; + pipe(&mut pipe_fd); + // read end + assert_eq!(pipe_fd[0], 3); + // write end + assert_eq!(pipe_fd[1], 4); + if fork() == 0 { + // child process, read from parent + // close write_end + close(pipe_fd[1]); + let mut buffer = [0u8; 32]; + let len_read = read(pipe_fd[0], &mut buffer) as usize; + // close read_end + close(pipe_fd[0]); + assert_eq!(core::str::from_utf8(&buffer[..len_read]).unwrap(), STR); + println!("Read OK, child process exited!"); + 0 + } else { + // parent process, write to child + // close read end + close(pipe_fd[0]); + assert_eq!(write(pipe_fd[1], STR.as_bytes()), STR.len() as isize); + // close write end + close(pipe_fd[1]); + let mut child_exit_code: i32 = 0; + wait(&mut child_exit_code); + assert_eq!(child_exit_code, 0); + println!("pipetest passed!"); + 0 + } +} \ No newline at end of file diff --git a/codes/user/src/bin/run_pipe_test.rs b/codes/user/src/bin/run_pipe_test.rs new file mode 100644 index 00000000..000b82d5 --- /dev/null +++ b/codes/user/src/bin/run_pipe_test.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{fork, exec, wait}; + +#[no_mangle] +pub fn main() -> i32 { + for i in 0..1000 { + if fork() == 0 { + exec("pipe_large_test\0", &[0 as *const u8]); + } else { + let mut _unused: i32 = 0; + wait(&mut _unused); + println!("Iter {} OK.", i); + } + } + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/sleep.rs b/codes/user/src/bin/sleep.rs new file mode 100644 index 00000000..bd1e2204 --- /dev/null +++ b/codes/user/src/bin/sleep.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{sleep, exit, get_time, fork, waitpid}; + +fn sleepy() { + let time: usize = 1000; + for i in 0..5 { + sleep(time); + println!("sleep {} x {} msecs.", i + 1, time); + } + exit(0); +} + +#[no_mangle] +pub fn main() -> i32 { + let current_time = get_time(); + let pid = fork(); + let mut exit_code: i32 = 0; + if pid == 0 { + sleepy(); + } + assert!(waitpid(pid as usize, &mut exit_code) == pid && exit_code == 0); + println!("use {} msecs.", get_time() - current_time); + println!("sleep pass."); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/sleep_simple.rs b/codes/user/src/bin/sleep_simple.rs new file mode 100644 index 00000000..4c058f87 --- /dev/null +++ b/codes/user/src/bin/sleep_simple.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +use user_lib::{get_time, sleep}; + +#[no_mangle] +pub fn main() -> i32 { + println!("into sleep test!"); + let start = get_time(); + println!("current time_msec = {}", start); + sleep(100); + let end = get_time(); + println!("time_msec = {} after sleeping 100 ticks, delta = {}ms!", end, end - start); + println!("r_sleep passed!"); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/stack_overflow.rs b/codes/user/src/bin/stack_overflow.rs new file mode 100644 index 00000000..e0ea471d --- /dev/null +++ b/codes/user/src/bin/stack_overflow.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +fn f(d: usize) { + println!("d = {}",d); + f(d + 1); +} + +#[no_mangle] +pub fn main() -> i32 { + println!("It should trigger segmentation fault!"); + f(0); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/user_shell.rs b/codes/user/src/bin/user_shell.rs new file mode 100644 index 00000000..b2c33f2d --- /dev/null +++ b/codes/user/src/bin/user_shell.rs @@ -0,0 +1,138 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +#[macro_use] +extern crate user_lib; + +const LF: u8 = 0x0au8; +const CR: u8 = 0x0du8; +const DL: u8 = 0x7fu8; +const BS: u8 = 0x08u8; + +use alloc::string::String; +use alloc::vec::Vec; +use user_lib::{ + fork, + exec, + waitpid, + open, + OpenFlags, + close, + dup, +}; +use user_lib::console::getchar; + +#[no_mangle] +pub fn main() -> i32 { + println!("Rust user shell"); + let mut line: String = String::new(); + print!(">> "); + loop { + let c = getchar(); + match c { + LF | CR => { + println!(""); + if !line.is_empty() { + let args: Vec<_> = line.as_str().split(' ').collect(); + let mut args_copy: Vec<String> = args + .iter() + .map(|&arg| { + let mut string = String::new(); + string.push_str(arg); + string + }) + .collect(); + + args_copy + .iter_mut() + .for_each(|string| { + string.push('\0'); + }); + + // redirect input + let mut input = String::new(); + if let Some((idx, _)) = args_copy + .iter() + .enumerate() + .find(|(_, arg)| arg.as_str() == "<\0") { + input = args_copy[idx + 1].clone(); + args_copy.drain(idx..=idx + 1); + } + + // redirect output + let mut output = String::new(); + if let Some((idx, _)) = args_copy + .iter() + .enumerate() + .find(|(_, arg)| arg.as_str() == ">\0") { + output = args_copy[idx + 1].clone(); + args_copy.drain(idx..=idx + 1); + } + + let mut args_addr: Vec<*const u8> = args_copy + .iter() + .map(|arg| arg.as_ptr()) + .collect(); + args_addr.push(0 as *const u8); + let pid = fork(); + if pid == 0 { + // input redirection + if !input.is_empty() { + let input_fd = open(input.as_str(), OpenFlags::RDONLY); + if input_fd == -1 { + println!("Error when opening file {}", input); + return -4; + } + let input_fd = input_fd as usize; + close(0); + assert_eq!(dup(input_fd), 0); + close(input_fd); + } + // output redirection + if !output.is_empty() { + let output_fd = open( + output.as_str(), + OpenFlags::CREATE | OpenFlags::WRONLY + ); + if output_fd == -1 { + println!("Error when opening file {}", output); + return -4; + } + let output_fd = output_fd as usize; + close(1); + assert_eq!(dup(output_fd), 1); + close(output_fd); + } + // child process + if exec(args_copy[0].as_str(), args_addr.as_slice()) == -1 { + println!("Error when executing!"); + return -4; + } + unreachable!(); + } else { + let mut exit_code: i32 = 0; + let exit_pid = waitpid(pid as usize, &mut exit_code); + assert_eq!(pid, exit_pid); + println!("Shell: Process {} exited with code {}", pid, exit_code); + } + line.clear(); + } + print!(">> "); + } + BS | DL => { + if !line.is_empty() { + print!("{}", BS as char); + print!(" "); + print!("{}", BS as char); + line.pop(); + } + } + _ => { + print!("{}", c as char); + line.push(c as char); + } + } + } +} \ No newline at end of file diff --git a/codes/user/src/bin/usertests.rs b/codes/user/src/bin/usertests.rs new file mode 100644 index 00000000..e8be6c45 --- /dev/null +++ b/codes/user/src/bin/usertests.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; + +static TESTS: &[&str] = &[ + "exit\0", + "fantastic_text\0", + "forktest\0", + "forktest2\0", + "forktest_simple\0", + "hello_world\0", + "matrix\0", + "sleep\0", + "sleep_simple\0", + "stack_overflow\0", + "yield\0", +]; + +use user_lib::{exec, fork, waitpid}; + +#[no_mangle] +pub fn main() -> i32 { + for test in TESTS { + println!("Usertests: Running {}", test); + let pid = fork(); + if pid == 0 { + exec(*test, &[0 as *const u8]); + panic!("unreachable!"); + } else { + let mut exit_code: i32 = Default::default(); + let wait_pid = waitpid(pid as usize, &mut exit_code); + assert_eq!(pid, wait_pid); + println!("\x1b[32mUsertests: Test {} in Process {} exited with code {}\x1b[0m", test, pid, exit_code); + } + } + println!("Usertests passed!"); + 0 +} \ No newline at end of file diff --git a/codes/user/src/bin/yield.rs b/codes/user/src/bin/yield.rs new file mode 100644 index 00000000..55032e40 --- /dev/null +++ b/codes/user/src/bin/yield.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate user_lib; +use user_lib::{getpid, yield_}; + +#[no_mangle] +pub fn main() -> i32 { + println!("Hello, I am process {}.", getpid()); + for i in 0..5 { + yield_(); + println!("Back in process {}, iteration {}.", getpid(), i); + } + println!("yield pass."); + 0 +} \ No newline at end of file diff --git a/codes/user/src/console.rs b/codes/user/src/console.rs new file mode 100644 index 00000000..810ebba1 --- /dev/null +++ b/codes/user/src/console.rs @@ -0,0 +1,39 @@ +use core::fmt::{self, Write}; + +const STDIN: usize = 0; +const STDOUT: usize = 1; + +use super::{read, write}; + +struct Stdout; + +impl Write for Stdout { + fn write_str(&mut self, s: &str) -> fmt::Result { + write(STDOUT, s.as_bytes()); + Ok(()) + } +} + +pub fn print(args: fmt::Arguments) { + Stdout.write_fmt(args).unwrap(); +} + +#[macro_export] +macro_rules! print { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::console::print(format_args!($fmt $(, $($arg)+)?)); + } +} + +#[macro_export] +macro_rules! println { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)); + } +} + +pub fn getchar() -> u8 { + let mut c = [0u8; 1]; + read(STDIN, &mut c); + c[0] +} diff --git a/codes/user/src/lang_items.rs b/codes/user/src/lang_items.rs new file mode 100644 index 00000000..b5b98e08 --- /dev/null +++ b/codes/user/src/lang_items.rs @@ -0,0 +1,12 @@ +use super::exit; + +#[panic_handler] +fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { + let err = panic_info.message().unwrap(); + if let Some(location) = panic_info.location() { + println!("Panicked at {}:{}, {}", location.file(), location.line(), err); + } else { + println!("Panicked: {}", err); + } + exit(-1); +} \ No newline at end of file diff --git a/codes/user/src/lib.rs b/codes/user/src/lib.rs new file mode 100644 index 00000000..0ef03bfb --- /dev/null +++ b/codes/user/src/lib.rs @@ -0,0 +1,108 @@ +#![no_std] +#![feature(llvm_asm)] +#![feature(linkage)] +#![feature(panic_info_message)] +#![feature(alloc_error_handler)] + +#[macro_use] +pub mod console; +mod syscall; +mod lang_items; + +extern crate alloc; +#[macro_use] +extern crate bitflags; + +use syscall::*; +use buddy_system_allocator::LockedHeap; +use alloc::vec::Vec; + +const USER_HEAP_SIZE: usize = 32768; + +static mut HEAP_SPACE: [u8; USER_HEAP_SIZE] = [0; USER_HEAP_SIZE]; + +#[global_allocator] +static HEAP: LockedHeap = LockedHeap::empty(); + +#[alloc_error_handler] +pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! { + panic!("Heap allocation error, layout = {:?}", layout); +} + +#[no_mangle] +#[link_section = ".text.entry"] +pub extern "C" fn _start(argc: usize, argv: usize) -> ! { + unsafe { + HEAP.lock() + .init(HEAP_SPACE.as_ptr() as usize, USER_HEAP_SIZE); + } + let mut v: Vec<&'static str> = Vec::new(); + for i in 0..argc { + let str_start = unsafe { + ((argv + i * core::mem::size_of::<usize>()) as *const usize).read_volatile() + }; + let len = (0usize..).find(|i| unsafe { + ((str_start + *i) as *const u8).read_volatile() == 0 + }).unwrap(); + v.push( + core::str::from_utf8(unsafe { + core::slice::from_raw_parts(str_start as *const u8, len) + }).unwrap() + ); + } + exit(main(argc, v.as_slice())); +} + +#[linkage = "weak"] +#[no_mangle] +fn main(_argc: usize, _argv: &[&str]) -> i32 { + panic!("Cannot find main!"); +} + +bitflags! { + pub struct OpenFlags: u32 { + const RDONLY = 0; + const WRONLY = 1 << 0; + const RDWR = 1 << 1; + const CREATE = 1 << 9; + const TRUNC = 1 << 10; + } +} + +pub fn dup(fd: usize) -> isize { sys_dup(fd) } +pub fn open(path: &str, flags: OpenFlags) -> isize { sys_open(path, flags.bits) } +pub fn close(fd: usize) -> isize { sys_close(fd) } +pub fn pipe(pipe_fd: &mut [usize]) -> isize { sys_pipe(pipe_fd) } +pub fn read(fd: usize, buf: &mut [u8]) -> isize { sys_read(fd, buf) } +pub fn write(fd: usize, buf: &[u8]) -> isize { sys_write(fd, buf) } +pub fn exit(exit_code: i32) -> ! { sys_exit(exit_code); } +pub fn yield_() -> isize { sys_yield() } +pub fn get_time() -> isize { sys_get_time() } +pub fn getpid() -> isize { sys_getpid() } +pub fn fork() -> isize { sys_fork() } +pub fn exec(path: &str, args: &[*const u8]) -> isize { sys_exec(path, args) } +pub fn wait(exit_code: &mut i32) -> isize { + loop { + match sys_waitpid(-1, exit_code as *mut _) { + -2 => { yield_(); } + // -1 or a real pid + exit_pid => return exit_pid, + } + } +} + +pub fn waitpid(pid: usize, exit_code: &mut i32) -> isize { + loop { + match sys_waitpid(pid as isize, exit_code as *mut _) { + -2 => { yield_(); } + // -1 or a real pid + exit_pid => return exit_pid, + } + } +} +pub fn sleep(period_ms: usize) { + let start = sys_get_time(); + while sys_get_time() < start + period_ms as isize { + sys_yield(); + } +} \ No newline at end of file diff --git a/codes/user/src/linker.ld b/codes/user/src/linker.ld new file mode 100644 index 00000000..e05a98ba --- /dev/null +++ b/codes/user/src/linker.ld @@ -0,0 +1,29 @@ + +OUTPUT_ARCH(riscv) +ENTRY(_start) + +BASE_ADDRESS = 0x0; + +SECTIONS +{ + . = BASE_ADDRESS; + .text : { + *(.text.entry) + *(.text .text.*) + } + . = ALIGN(4K); + .rodata : { + *(.rodata .rodata.*) + } + . = ALIGN(4K); + .data : { + *(.data .data.*) + } + .bss : { + *(.bss .bss.*) + } + /DISCARD/ : { + *(.eh_frame) + *(.debug*) + } +} \ No newline at end of file diff --git a/codes/user/src/syscall.rs b/codes/user/src/syscall.rs new file mode 100644 index 00000000..1cd30f84 --- /dev/null +++ b/codes/user/src/syscall.rs @@ -0,0 +1,79 @@ +const SYSCALL_DUP: usize = 24; +const SYSCALL_OPEN: usize = 56; +const SYSCALL_CLOSE: usize = 57; +const SYSCALL_PIPE: usize = 59; +const SYSCALL_READ: usize = 63; +const SYSCALL_WRITE: usize = 64; +const SYSCALL_EXIT: usize = 93; +const SYSCALL_YIELD: usize = 124; +const SYSCALL_GET_TIME: usize = 169; +const SYSCALL_GETPID: usize = 172; +const SYSCALL_FORK: usize = 220; +const SYSCALL_EXEC: usize = 221; +const SYSCALL_WAITPID: usize = 260; + +fn syscall(id: usize, args: [usize; 3]) -> isize { + let mut ret: isize; + unsafe { + llvm_asm!("ecall" + : "={x10}" (ret) + : "{x10}" (args[0]), "{x11}" (args[1]), "{x12}" (args[2]), "{x17}" (id) + : "memory" + : "volatile" + ); + } + ret +} + +pub fn sys_dup(fd: usize) -> isize { + syscall(SYSCALL_DUP, [fd, 0, 0]) +} + +pub fn sys_open(path: &str, flags: u32) -> isize { + syscall(SYSCALL_OPEN, [path.as_ptr() as usize, flags as usize, 0]) +} + +pub fn sys_close(fd: usize) -> isize { + syscall(SYSCALL_CLOSE, [fd, 0, 0]) +} + +pub fn sys_pipe(pipe: &mut [usize]) -> isize { + syscall(SYSCALL_PIPE, [pipe.as_mut_ptr() as usize, 0, 0]) +} + +pub fn sys_read(fd: usize, buffer: &mut [u8]) -> isize { + syscall(SYSCALL_READ, [fd, buffer.as_mut_ptr() as usize, buffer.len()]) +} + +pub fn sys_write(fd: usize, buffer: &[u8]) -> isize { + syscall(SYSCALL_WRITE, [fd, buffer.as_ptr() as usize, buffer.len()]) +} + +pub fn sys_exit(exit_code: i32) -> ! { + syscall(SYSCALL_EXIT, [exit_code as usize, 0, 0]); + panic!("sys_exit never returns!"); +} + +pub fn sys_yield() -> isize { + syscall(SYSCALL_YIELD, [0, 0, 0]) +} + +pub fn sys_get_time() -> isize { + syscall(SYSCALL_GET_TIME, [0, 0, 0]) +} + +pub fn sys_getpid() -> isize { + syscall(SYSCALL_GETPID, [0, 0, 0]) +} + +pub fn sys_fork() -> isize { + syscall(SYSCALL_FORK, [0, 0, 0]) +} + +pub fn sys_exec(path: &str, args: &[*const u8]) -> isize { + syscall(SYSCALL_EXEC, [path.as_ptr() as usize, args.as_ptr() as usize, 0]) +} + +pub fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize { + syscall(SYSCALL_WAITPID, [pid as usize, exit_code as usize, 0]) +} \ No newline at end of file -- GitLab