Compare commits
9 Commits
1b38c7f445
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fc622c5da | ||
|
|
e754bac5e8 | ||
| 459c0caca7 | |||
| 6fd5085ed1 | |||
| c4575242e0 | |||
| 957ed987fe | |||
| 0c3d9b8370 | |||
|
|
1bfde637d0 | ||
|
|
c092a81331 |
808
LICENSE
808
LICENSE
@@ -1,202 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Apache License
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Version 2.0, January 2004
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
http://www.apache.org/licenses/
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
Preamble
|
||||||
|
|
||||||
1. Definitions.
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
The licenses for most software and other practical works are designed
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
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.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
When we speak of free software, we are referring to freedom, not
|
||||||
the copyright owner that is granting the License.
|
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.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
To protect your rights, we need to prevent others from denying you
|
||||||
other entities that control, are controlled by, or are under common
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
control with that entity. For the purposes of this definition,
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
For example, if you distribute copies of such a program, whether
|
||||||
exercising permissions granted by this License.
|
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.
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
including but not limited to software source code, documentation
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
source, and configuration files.
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
transformation or translation of a Source form, including but
|
that there is no warranty for this free software. For both users' and
|
||||||
not limited to compiled object code, generated documentation,
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
and conversions to other media types.
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
Some devices are designed to deny users access to install or run
|
||||||
Object form, made available under the License, as indicated by a
|
modified versions of the software inside them, although the manufacturer
|
||||||
copyright notice that is included in or attached to the work
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
(an example is provided in the Appendix below).
|
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.
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
Finally, every program is threatened constantly by software patents.
|
||||||
form, that is based on (or derived from) the Work and for which the
|
States should not allow patents to restrict development and use of
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
avoid the special danger that patents applied to a free program could
|
||||||
of this License, Derivative Works shall not include works that remain
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
patents cannot be used to render the program non-free.
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
The precise terms and conditions for copying, distribution and
|
||||||
the original version of the Work and any modifications or additions
|
modification follow.
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
TERMS AND CONDITIONS
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
0. Definitions.
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
works, such as semiconductor masks.
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
Derivative Works a copy of this License; and
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
stating that You changed the files; and
|
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.
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
A "covered work" means either the unmodified Program or a work based
|
||||||
that You distribute, all copyright, patent, trademark, and
|
on the Program.
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
To "propagate" a work means to do anything with it that, without
|
||||||
distribution, then any Derivative Works that You distribute must
|
permission, would make you directly or secondarily liable for
|
||||||
include a readable copy of the attribution notices contained
|
infringement under applicable copyright law, except executing it on a
|
||||||
within such NOTICE file, excluding those notices that do not
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
pertain to any part of the Derivative Works, in at least one
|
distribution (with or without modification), making available to the
|
||||||
of the following places: within a NOTICE text file distributed
|
public, and in some countries other activities as well.
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
To "convey" a work means any kind of propagation that enables other
|
||||||
may provide additional or different license terms and conditions
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
to the extent that it includes a convenient and prominently visible
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
this License, without any additional terms or conditions.
|
tells the user that there is no warranty for the work (except to the
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
extent that warranties are provided), that licensees may convey the
|
||||||
the terms of any separate license agreement you may have executed
|
work under this License, and how to view a copy of this License. If
|
||||||
with Licensor regarding such Contributions.
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
1. Source Code.
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
The "source code" for a work means the preferred form of the work
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
for making modifications to it. "Object code" means any non-source
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
form of a work.
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
A "Standard Interface" means an interface that either is an official
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
standard defined by a recognized standards body, or, in the case of
|
||||||
unless required by applicable law (such as deliberate and grossly
|
interfaces specified for a particular programming language, one that
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
is widely used among developers working in that language.
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
The "System Libraries" of an executable work include anything, other
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
packaging a Major Component, but which is not part of that Major
|
||||||
or other liability obligations and/or rights consistent with this
|
Component, and (b) serves only to enable use of the work with that
|
||||||
License. However, in accepting such obligations, You may act only
|
Major Component, or to implement a Standard Interface for which an
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
implementation is available to the public in source code form. A
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
"Major Component", in this context, means a major essential component
|
||||||
defend, and hold each Contributor harmless for any liability
|
(kernel, window system, and so on) of the specific operating system
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
of your accepting any such warranty or additional liability.
|
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
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
If you develop a new program, and you want it to be of the greatest
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
possible use to the public, the best way to achieve this is to make it
|
||||||
replaced with your own identifying information. (Don't include
|
free software which everyone can redistribute and change under these terms.
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
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.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
you may not use this file except in compliance with the License.
|
Copyright (C) <year> <name of author>
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
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.
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
This program is distributed in the hope that it will be useful,
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
See the License for the specific language governing permissions and
|
GNU General Public License for more details.
|
||||||
limitations under the License.
|
|
||||||
|
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>.
|
||||||
|
|||||||
94
MANIFEST.md
Normal file
94
MANIFEST.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
## Technical Overview
|
||||||
|
|
||||||
|
### Core Architecture and Memory Management
|
||||||
|
|
||||||
|
The Elyz architecture is built on a strict separation of physical resource ownership and logical resource management, where the kernel acts as a passive arbiter that verifies digital signatures in the form of descriptors. All memory within the system is represented as objects with precise ownership tracking—pages, regions, or segments—and each such object is always tied to a unique owner: a process or the kernel. The owner holds a strong reference, guaranteeing the object's existence until the hierarchical reference counter with per-CPU-core local pools drops to zero, which eliminates cache misses and bus conflicts during metadata updates.
|
||||||
|
|
||||||
|
Support for **revocable capabilities** allows for the forced revocation of rights via descriptor aliases: the kernel instantly invalidates access at the hardware level using Process-Context Identifiers (PCID) without traversing the ownership tree. Processes never operate with direct physical addresses; instead, they work through lightweight descriptor tokens that define access rights and the relationship type (strong, borrowed, or transferred).
|
||||||
|
|
||||||
|
### Distributed Actor Model
|
||||||
|
|
||||||
|
The distributed actor model (replacing a single PM) represents a network of independent PM actors that communicate asynchronously through lock-free message queues. They receive initial memory capital from the bootloader and distribute it without bottlenecks, utilizing **lazy allocation** (on-demand space reservation via page fault) and **ballooning** for the dynamic redistribution of unused memory.
|
||||||
|
|
||||||
|
> **The Asynchrony Tax:** While lock-free queues minimize locking, handling a page fault via asynchronous message passing to a PM actor inherently carries higher latency than a direct, synchronous interrupt handler call in a monolithic Linux kernel.
|
||||||
|
|
||||||
|
### Process Lifecycle and Optimization
|
||||||
|
|
||||||
|
When an application needs to launch, it requests raw memory from PM actors, which carve it into functional objects—thread blocks and descriptor tables. To minimize overhead, an intelligent system call **batching** mechanism is implemented: requests are grouped into queues, reducing the frequency of context switches.
|
||||||
|
|
||||||
|
This batching compromise introduces a baseline delay (**latency floor**) for non-critical tasks: the system appears incredibly smooth in real-time processes (multimedia and input utilize high-priority immediate-execution flags), but "raw" synthetic single-thread computation speed may lag behind Linux.
|
||||||
|
|
||||||
|
### The elibc Layer
|
||||||
|
|
||||||
|
To free developers from manual management, `elibc` is provided as a dynamic shared library (`libelibc.so`) with a standardized POSIX-like ABI based on `musl libc` for full portability. The translation layer inside `elibc` is optimized so that a C programmer sees the familiar `malloc`/`fork` functions, which are transparently converted into native `allocate`/`clone` calls with direct FFI-bindings for Rust/Go, avoiding double context switches.
|
||||||
|
|
||||||
|
When a program requires memory, `elibc` requests a strong descriptor from the Virtual Memory Manager (within the actor model), and the kernel configures the MMU directly after the reference is created, achieving hardware speeds without intermediaries. The results of descriptor verifications are cached in parallel with the TLB, making subsequent accesses practically free.
|
||||||
|
|
||||||
|
### Compatibility Layer (WinServer)
|
||||||
|
|
||||||
|
For a Wine analogue, the key element is the **Personality Server (WinServer)**, which emulates Windows logic within actor isolation, supporting specific WinAPI primitives through dedicated descriptor subsystems. Thanks to descriptors, performance is maximized via borrowing, transferring, and instant revoking: instead of copying data, it performs **zero-copy** transfers of memory rights.
|
||||||
|
|
||||||
|
* **Example:** When rendering a frame, the application transfers the graphics buffer descriptor to the server, or the PM actors project shared memory. Upon timeout or frame completion, access is revoked at the MMU/TLB level, followed by background lazy cleanup.
|
||||||
|
* **Failure Handling:** If a Wine application crashes, the actor-supervision mechanism automatically cascades and annuls all related descriptors, instantly returning resources to the pool and preventing rights fragmentation.
|
||||||
|
|
||||||
|
### Architectural Summary
|
||||||
|
|
||||||
|
In this hierarchy:
|
||||||
|
|
||||||
|
* **The Kernel** is an accountant, verifying links and rights through hardware caches without diving into the logic.
|
||||||
|
* **The Process** is an isolated safe with descriptor-keys stored inside its wallet.
|
||||||
|
* **Wine** is an overseer, translating external instructions into memory bank checks.
|
||||||
|
|
||||||
|
The combination of the actor model, revocable capabilities, and PCID-caching allows for the local "bankruptcy" of any individual component (driver/emulator) without affecting the rest of the system. The balance between performance and convenience is achieved by hiding complex ownership mechanics inside `elibc`, preserving a familiar development style while maintaining architectural reliability at a seL4 level.
|
||||||
|
|
||||||
|
### Focus Mode Feature
|
||||||
|
|
||||||
|
Elyz includes a dedicated feature for manual priority management, allowing either the user or a program (via API access) to determine which actor or process receives more attention. Alternatively, users can enable **"focus mode"**, where only one selected process executes (along with its dependencies and child processes) while everything else "freezes."
|
||||||
|
|
||||||
|
It does not stop; it freezes—once focus mode is deactivated, all operations resume. This serves as a developer tool designed to make compiling exceptionally heavy programs faster and easier directly under Elyz.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Philosophy: Nomarchy
|
||||||
|
|
||||||
|
### PART I. Philosophical Pillars (Elyz Axioms)
|
||||||
|
|
||||||
|
#### 1. Separation of Ownership from Management
|
||||||
|
|
||||||
|
In UNIX, the right to perform an action stems from identity (User ID). In Elyz, there are no identities—only assets and rights. A physical resource belongs to whoever created or received it, but logical management is delegated to a distributed network of entities. The system lives in a paradigm of separation of powers: executors (actors) manage, while the arbiter (the kernel) merely stamps digital signatures.
|
||||||
|
|
||||||
|
#### 2. Principle of Total Accounting and Zero Trust
|
||||||
|
|
||||||
|
Every byte of memory, every register, and every quantum of CPU time has a specific, legally bound owner. Security is not a fence around the system; it is the isolation of every single molecule within it. Resources can never be "ownerless" or "shared by default."
|
||||||
|
|
||||||
|
#### 3. Ephemerality and Revocability of Rights
|
||||||
|
|
||||||
|
No granted right is permanent. All trust in Elyz is a temporary loan. The ability to instantly, uncompromisingly, and via cascade annualize any permission at the hardware level is more critical than the procedure used to grant it.
|
||||||
|
|
||||||
|
#### 4. Pragmatic Dualism (Architecture for robots, interface for humans)
|
||||||
|
|
||||||
|
A mathematically sound and strict kernel should not make the programmer suffer. Elyz separates internal chaos from external aesthetics: under the hood lies a highly complex hierarchy of descriptors and asynchronous actors, but externally, the system must present the friendly face of classic POSIX. Rigid security must not compromise ergonomics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### PART II. Strict System Rules (Laws of the Kernel and Environment)
|
||||||
|
|
||||||
|
* **Rule 1. The Kernel is a Passive Accountant**
|
||||||
|
The Elyz kernel is deaf and blind to application business logic. It does not know what a "text editor," a "video stream," or a "game" is. Its sole task is to verify the validity of tokens (descriptors), check reference counters, and toggle MMU levers. The kernel never initiates memory management processes on its own—it only executes the will of the actors, provided it is verified by a signature.
|
||||||
|
* **Rule 2. Everything is an Object of Ownership (Replacing UNIX "Everything is a file")**
|
||||||
|
The "everything is a file" concept is obsolete. In Elyz, everything is an object with a strong reference. A thread, a memory region, a page, a descriptor—these are all objects. An object exists precisely as long as its distributed reference counter is alive. When the counter drops to zero, the object is instantly and traceably erased from the physical world by the local CPU core pool.
|
||||||
|
* **Rule 3. Isolation via the Descriptor Wallet**
|
||||||
|
A process has no eyes to see physical memory and no hands to reach it. A process is a blind safe containing a wallet filled with lightweight tokens (descriptors).
|
||||||
|
* *Want to write data?* Show the token.
|
||||||
|
* *Want to transfer data?* Hand over the token (transfer) or let them hold it (borrow).
|
||||||
|
* *Attempting to access a direct address* is a system crime punished by the immediate destruction of the context.
|
||||||
|
|
||||||
|
|
||||||
|
* **Rule 4. Decentralization of Power (Anti-PM)**
|
||||||
|
There is no single point of failure in the system. Instead of a monolithic Process and Memory Manager (as seen in UNIX/Linux), Elyz is governed by a federation of independent, asynchronous PM Actors. They communicate via lock-free message queues. If one actor misbehaves or becomes overloaded, the remaining actors continue carving out memory and distributing starting capital. The local bankruptcy of a component does not lead to the collapse of the state.
|
||||||
|
* **Rule 5. Zero-Copy via Rights Migration**
|
||||||
|
Copying bytes from user space to kernel space or between processes is an archaic overhead. Data must remain stationary. Only the rights to the data move. Passing a graphics frame or a network packet is an instantaneous tectonic shift of descriptors within the MMU/TLB tables. Ownership changes hands at the speed of CPU thought.
|
||||||
|
* **Rule 6. The Batching Compromise (Intelligent Batching)**
|
||||||
|
A system call is an expensive luxury. Elyz forbids processes from poking the kernel for every minor operation. Requests are packed into queues and sent in bulk (batching). However, the system knows the cost of delays: media streams (audio, frames, real-time input) are marked with an "Execute Immediately!" flag, bypassing the general queue.
|
||||||
|
* **Rule 7. Personalities are Simply Isolated Guests**
|
||||||
|
Elyz lacks native support for Windows, Linux, or macOS. Supporting other operating systems does not involve dirty hacks inside the kernel. Instead, isolated Personality Servers (such as WinServer) are used. To Elyz, a foreign OS is just another overseer actor that takes Elyz resources and translates them into a language the guest application understands. If the guest "dies," the actor-supervision mechanism cascades to annualize all its checks, returning memory to the pool without a single drop of fragmentation.
|
||||||
626
PLAN.md
Normal file
626
PLAN.md
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
# Elyz (LISA) Microkernel — Полный план оптимизации и полировки
|
||||||
|
|
||||||
|
> Анализ кода по состоянию на июнь 2026.
|
||||||
|
> Составлен на основе исходников: `kernel/src/`, linker-скрипты, Cargo.toml, Makefile.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
1. [Условные обозначения](#условные-обозначения)
|
||||||
|
2. [Критические баги (не работает)](#ф-1-критические-баги-фаза-1)
|
||||||
|
3. [Средние баги (UB / утечки)](#ф-1-критические-баги-фаза-1)
|
||||||
|
4. [Проблемы архитектуры](#ф-2-архитектурный-рефакторинг-фаза-2)
|
||||||
|
5. [Code Quality & Naming](#ф-3-code-quality--naming-фаза-3)
|
||||||
|
6. [Производительность](#ф-4-производительность-фаза-4)
|
||||||
|
7. [Инфраструктура](#ф-5-инфраструктура-фаза-5)
|
||||||
|
8. [Полный пошаговый план (сортированный)](#полный-пошаговый-план-сортированный-по-приоритету)
|
||||||
|
9. [Roadmap](#roadmap)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Условные обозначения
|
||||||
|
|
||||||
|
| Метка | Значение |
|
||||||
|
|-------|----------|
|
||||||
|
| 🔴 CRIT | Системный баг — крах, silent corruption, UB |
|
||||||
|
| 🟡 HIGH | Утечка памяти, логическая ошибка |
|
||||||
|
| 🟢 MED | Неоптимальность, технический долг |
|
||||||
|
| 🔵 LOW | Косметика, нейминг, кодстайл |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 1: Критические баги (Фаза 1 — CRIT 1.1–1.6 ✅ исправлены)
|
||||||
|
|
||||||
|
### 🔴 CRIT 1.6 — `tlb_flush_asid` не работает с >64 ядрами ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/vmm.rs:794-802`
|
||||||
|
|
||||||
|
**Проблема**: `1u64 << current_core` при `current_core >= 64` — UB (panic в debug, wrap в release).
|
||||||
|
|
||||||
|
**Решение**: Добавлена проверка `current_core < 64` перед shift. Для core >= 64 self-exclusion пропускается (core не входит в нижние 64 бита маски). В `handle_tlb_shootdown_ipi` аналогичная проверка предотвращает запись в маску по некорректному смещению.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let target_mask = if current_core < 64 {
|
||||||
|
active_cpus & !(1u64 << current_core)
|
||||||
|
} else {
|
||||||
|
active_cpus
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**TODO**: При реализации SMP с >64 ядер — перейти на `AtomicU128` или динамический список APIC ID.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 1.5: Средние баги (Фаза 1.5 — MED 1.7–1.10 ✅ исправлены)
|
||||||
|
|
||||||
|
### 🟡 MED 1.7 — `unwrap_unchecked()` на Option без full fence ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pm_router.rs:70-82`
|
||||||
|
|
||||||
|
**Проблема**: TOCTOU race между check `is_ready` и `unwrap_unchecked()`. Хотя `is_ready` никогда не сбрасывается, code smell.
|
||||||
|
|
||||||
|
**Решение**: Добавлен комментарий-инвариант, объясняющий почему TOCTOU безопасен (is_ready set-once, never cleared). Panic path вынесен в `#[cold]` функцию:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cold]
|
||||||
|
fn not_initialized() -> ! {
|
||||||
|
panic!("FATAL: PMRouter is accessed before initialization!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED 1.8 — `process_messages` не имеет квоты ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pm_manages.rs:328-354`
|
||||||
|
|
||||||
|
**Проблема**: Дренирует всю очередь за один вызов. Если 10 000 сообщений, заблокирует ядро на неопределённое время.
|
||||||
|
|
||||||
|
**Решение**: Добавлен лимит `MAX_MESSAGES_PER_CALL = 64`. После обработки 64 сообщений вызов возвращает управление, позволяя другим компонентам ядра получить процессорное время.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED 1.9 — Bitmap PMM инициализирует `ref_counts` в 1, потом маркирует used-фреймы ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pmm.rs:49-76`
|
||||||
|
|
||||||
|
**Проблема**: `fill(0xFF)` + `fill(1)` для всех страниц, затем итерация free по USABLE. Лишняя работа.
|
||||||
|
|
||||||
|
**Решение**: Инвертирована логика: `fill(0)` для bitmap и ref_counts, затем итерация по **не-USABLE** записям для mark_used. Убрана лишняя запись в резервированные страницы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED 1.10 — Page fault handler берёт `KERNEL_SPACE.lock()` каждый раз ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cpu/interrupts.rs:109-148`
|
||||||
|
|
||||||
|
**Проблема**: Двойной захват `KERNEL_SPACE.lock()` — в `process_deferred_mmu_events()` и в `rust_page_fault_handler()`. На SMP каждый PF будет сериализован.
|
||||||
|
|
||||||
|
**Решение**: `process_deferred_mmu_events()` встроен в `rust_page_fault_handler` — блокировка захватывается один раз и удерживается для обеих операций. Добавлен TODO о переходе на `RwLock` для SMP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 2: Архитектурный рефакторинг (Фаза 2)
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.1 — HHDM offset передаётся параметром в каждую функцию
|
||||||
|
|
||||||
|
**Проблема**: `hhdm: u64` тащится через все вызовы:
|
||||||
|
```rust
|
||||||
|
p4.map_region(virt, phys, size, flags, hhdm);
|
||||||
|
p4.map_page(virt, frame, flags, hhdm);
|
||||||
|
```
|
||||||
|
|
||||||
|
Загрязняет сигнатуры и не нужно (HHDM offset — константа после boot).
|
||||||
|
|
||||||
|
**Решение**: Глобальный `static HHDM_OFFSET: AtomicU64`:
|
||||||
|
```rust
|
||||||
|
pub fn init(offset: u64) { HHDM_OFFSET.store(offset, Ordering::Release); }
|
||||||
|
pub fn get() -> u64 { HHDM_OFFSET.load(Ordering::Relaxed) }
|
||||||
|
```
|
||||||
|
|
||||||
|
Убрать `hhdm` из параметров всех функций paging/vmm.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.2 — Buddy allocator: O(n) поиск при free()
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/buddy.rs:246`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
|
||||||
|
```
|
||||||
|
|
||||||
|
Линейный поиск по `Vec<usize>` — O(количество блоков этого порядка). При 10 000 блоков порядка 0, free() занимает тысячи итераций.
|
||||||
|
|
||||||
|
**Решение**: Перейти на **intrusive linked list** через сами страницы: первые 8 байт неиспользуемой страницы хранят `next` указатель. Даёт O(1) удаление.
|
||||||
|
|
||||||
|
Или хеш-таблицу: `HashMap<usize, ()>` (buddy_idx → exists).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.3 — PMM Bitmap: O(n) allocation scan
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pmm.rs:128-158`
|
||||||
|
|
||||||
|
Побайтовый (побитовый) scan всего bitmap в поиске нулевого бита. На системе с 64 GB RAM → 64M бит → до 8M итераций на аллокацию.
|
||||||
|
|
||||||
|
**Решение**: Использовать **tree bitmap** (buddy-style bitmap). Хранить дополнительный уровень с `u64` словами, где каждый бит = `all_ones(word)`. Позволяет за O(1) найти свободный блок через `ctz` / `clz`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.4 — Channel `wait_for_response` — busy-wait
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pm_router.rs:130-137`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
100% CPU consumption в ожидании. Для реального асинхронного рантайма нужен yield-to-scheduler или waker.
|
||||||
|
|
||||||
|
**Решение**: Добавить `Waker` registration: поток регистрирует свой `Waker` в канале, а `route_responses` вызывает `wake()` при записи результата. Пока планировщика нет — хотя бы HLT-based ожидание.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.5 — Capability revocation: O(n) рекурсия
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cap/mod.rs:97-137`
|
||||||
|
|
||||||
|
`revoke_internal()` сканирует **весь CNode** на каждом уровне рекурсии. С 256+ слотами и глубиной 10 → 2560 итераций.
|
||||||
|
|
||||||
|
**Решение**: Держать per-slot список children:
|
||||||
|
```rust
|
||||||
|
struct CNodeSlot {
|
||||||
|
cap: Capability,
|
||||||
|
parent: Option<usize>,
|
||||||
|
children: Vec<usize>, // явный список наследников
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Отзыв — проход по дереву без сканирования всей таблицы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.6 — Нет SMP startup кода
|
||||||
|
|
||||||
|
`ACTIVE_CPUS_MASK = 1` (только BSP). TLB shootdown кидает IPI в пустоту. Нет:
|
||||||
|
- SIPI trampoline page
|
||||||
|
- Startup sequence для AP
|
||||||
|
- Per-CPU data areas
|
||||||
|
- Stack allocation для AP
|
||||||
|
|
||||||
|
**Решение**: Реализовать SIPI-последовательность:
|
||||||
|
1. Выделить страницу по адресу <1MB (real mode trampoline)
|
||||||
|
2. Записать startup код (выход из real mode → long mode → setup stack → jump to rust)
|
||||||
|
3. Отправить SIPI с вектором на эту страницу
|
||||||
|
4. AP просыпается, инициализирует LAPIC, регистрируется в `ACTIVE_CPUS_MASK`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.7 — Вся память в HHDM identity-map при старте
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/main.rs:194-201`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
for entry in mmap_res.entries() {
|
||||||
|
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
||||||
|
if entry.entry_type != RESERVED {
|
||||||
|
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags, hhdm_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Все *не-reserved* регионы identity-map'ятся по физическому адресу (second mapping). На системе с 64GB RAM и 4KB страницами = 16M calls к `map_page`. Каждый call:
|
||||||
|
- Аллоцирует до 3 страниц таблиц (P3, P2, P1)
|
||||||
|
- INVLPG на каждую 4KB страницу
|
||||||
|
- Время: ~часы при эмуляции, минуты при KVM
|
||||||
|
|
||||||
|
**Решение**: Использовать **2MB huge pages** (`PageTableFlags::HUGE_PAGE`) для identity map. 64GB → 32K entries вместо 16M. INVLPG заменяется на CR3 reload.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 3: Code Quality & Naming (Фаза 3)
|
||||||
|
|
||||||
|
### 🧹 CQ 3.1 — Dead code: FramebufferDisplay + Console (main.rs)
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/main.rs:42-144`
|
||||||
|
|
||||||
|
Две структуры:
|
||||||
|
- `FramebufferDisplay` (реализует `DrawTarget` для `embedded-graphics`)
|
||||||
|
- `Console` (обёртка над `FramebufferDisplay` + `embedded-graphics`)
|
||||||
|
|
||||||
|
Никто не используется. Настоящий консольный вывод — `tty::Console`. При этом `embedded-graphics` висит в Cargo.toml как зависимость.
|
||||||
|
|
||||||
|
**Решение**: Удалить об struct + реализацию, убрать `embedded-graphics` из зависимостей.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.2 — Unused imports в main.rs
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/main.rs:6-16`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use crate::mem::paging::{PageTable, PageTableFlags};
|
||||||
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
|
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
||||||
|
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
||||||
|
```
|
||||||
|
|
||||||
|
Часть из них используется, часть — нет (например `fmt::self`, `PageTable`). После удаления dead code (3.1) ещё часть станет не нужна.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.3 — `#[allow(dead_code)]` на весь mod vmm
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/vmm.rs:27`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![allow(dead_code)]
|
||||||
|
```
|
||||||
|
|
||||||
|
Отключает warning для ВСЕГО модуля. 800+ строк кода с отключённой проверкой мёртвого кода. Означает что значительная часть VMM API не используется сейчас — надо либо использовать, либо убрать, либо маркировать `#[allow(dead_code)]` на конкретные единицы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.4 — Имя проекта: LISA / Elyz / kernel
|
||||||
|
|
||||||
|
| Где | Что написано |
|
||||||
|
|-----|-------------|
|
||||||
|
| `Cargo.toml` | `name = "LISA"` |
|
||||||
|
| `README.md` | "Elyz" |
|
||||||
|
| `limine.conf` | `Elyz (LISA KERNEL)` |
|
||||||
|
| `GNUmakefile` | `elyz-x86_64.iso` |
|
||||||
|
| `main.rs` | `LIS4` (LISA 4?) |
|
||||||
|
|
||||||
|
Три разных публичных имени.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.5 — Странное имя файла: `pm_manages.rs`
|
||||||
|
|
||||||
|
Грамматически некорректно (глагол в имени файла). Основная структура — `PMActor`, файл должен называться `pm_actor.rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.6 — Мёртвые linker-скрипты
|
||||||
|
|
||||||
|
- `linker-aarch64.ld`
|
||||||
|
- `linker-riscv64.ld`
|
||||||
|
- `linker-loongarch64.ld`
|
||||||
|
|
||||||
|
Не используются (build.rs хардкодит `linker-x86_64.ld`). Ни один из этих таргетов не подключен в `rust-toolchain.toml`. Dead weight.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.7 — Locked SpinLock без backoff
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/allocator.rs:27`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
while self.lock.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`spin_loop()` на x86 — это `REP NOP` (PAUSE). Это правильно, но при длительном ожидании нужно экспоненциально увеличивать задержку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.8 — `log!` максрос не lock-free
|
||||||
|
|
||||||
|
При вызове `info!(...)` внутри макроса:
|
||||||
|
1. Вызывается `SerialPort::init()` (см. CRIT 1.3)
|
||||||
|
2. Пишет на экран через framebuffer
|
||||||
|
3. Пишет в serial
|
||||||
|
|
||||||
|
Если в PF handler вызвать `info!`, и PF произошёл из-за framebuffer DMA или serial IRQ — будет reentrancy. Надо маркировать как `#[inline(never)]` или добавить reentrancy guard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 4: Производительность (Фаза 4)
|
||||||
|
|
||||||
|
### ⚡ PERF 4.1 — Bitmap PMM: word-level skip
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pmm.rs:139`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if self.bitmap[byte_idx] == 0xFF { continue; }
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка на уровне байта — можно на уровне `u64` слова: `if word == u64::MAX { skip 8 bytes }`. В 8x меньше итераций.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.2 — Buddy free: O(n) → O(1)
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/buddy.rs:246`
|
||||||
|
|
||||||
|
См. ARCH 2.2. Замена `Vec` на intrusive list — ключевой перф-фикс.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.4 — INVLPG в bulk_map: batch flush
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/paging.rs:119-121`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe {
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0, ...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`map_region` вызывает `map_page` в цикле — каждая итерация делает INVLPG. 16M страниц = 16M INVLPG. CPU заливает TLBs и потом их же сбрасывает.
|
||||||
|
|
||||||
|
**Решение**: Убрать INVLPG из `map_page` (оставить вызывающему), сделать отдельную функцию `flush_range(virt, pages)` или вызов `invpcid` type-2 (flush-by-range).
|
||||||
|
|
||||||
|
**Но осторожно**: INVLPG нужен при модификации активной таблицы. Если identity-map ещё не загружена — можно без INVLPG.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.5 — TLB shootdown: busy-wait → IPI + HLT
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/vmm.rs:809-811`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
while SHOOTDOWN_ACK.load(Ordering::Acquire) & target_mask != target_mask {
|
||||||
|
spin_loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Пока нет AP — код мёртвый. Когда AP появятся, надо чтобы AP висели в `HLT` и просыпались от IPI с TLB_SHOOTDOWN_VECTOR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.6 — Page fault handler: lock-free VMA tree
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cpu/interrupts.rs:110`
|
||||||
|
|
||||||
|
`KERNEL_SPACE.lock()` — каждый PF сериализуется. На одноядерной системе — OK, на SMP — bottleneck.
|
||||||
|
|
||||||
|
**Решение**: `RWLock` на VMA region или lock-free RB-tree для VMA поиска.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 5: Инфраструктура (Фаза 5)
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.1 — SMP AP Startup Code
|
||||||
|
|
||||||
|
Отсутствует. Нужен:
|
||||||
|
- Startup trampoline в real mode (<1MB)
|
||||||
|
- Переход в protected → long mode
|
||||||
|
- Настройка GDT/IDT для AP
|
||||||
|
- Per-CPU stack
|
||||||
|
- Регистрация в `ACTIVE_CPUS_MASK`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.2 — Мультиархитектурность
|
||||||
|
|
||||||
|
Есть linker-скрипты под aarch64, riscv64, loongarch64, но:
|
||||||
|
- `build.rs` хардкодит `linker-x86_64.ld`
|
||||||
|
- ASM в interrupts.rs только x86
|
||||||
|
- LAPIC — x86-specific
|
||||||
|
- Port IO — x86-specific
|
||||||
|
- IDT — x86-specific
|
||||||
|
|
||||||
|
Для портирования нужна архитектурная абстракция — traits:
|
||||||
|
```rust
|
||||||
|
trait Arch {
|
||||||
|
type PteFlags;
|
||||||
|
fn init_interrupts();
|
||||||
|
fn send_ipi(core_id: u32, vector: u8);
|
||||||
|
fn read_cycle_counter() -> u64;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.3 — Unit-тесты для Buddy Allocator
|
||||||
|
|
||||||
|
Buddy allocator — идеальный кандидат для `#[cfg(test)]`:
|
||||||
|
- Проверка инвариантов после alloc/free sequence
|
||||||
|
- Fuzzing случайных порядков
|
||||||
|
- Проверка coalescing (граничные случаи)
|
||||||
|
- Проверка alignment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.4 — Утилита для бинарного патчинга
|
||||||
|
|
||||||
|
Сейчас результирующий ELF надо руками objcopy'ить. Можно добавить Cargo alias или build.rs скрипт для автоматизации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.5 — GDB/QEMU debugging support
|
||||||
|
|
||||||
|
Добавить `.gdbinit` / `gdb.py` с:
|
||||||
|
- `target remote localhost:1234`
|
||||||
|
- Разгрузка символов из ELF
|
||||||
|
- Макросы для просмотра page table, capability, VMA regions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 6: Безопасность (сквозная)
|
||||||
|
|
||||||
|
### 🛡️ SEC 6.1 — Capability minting не проверяет `Relation` parent
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cap/mod.rs:64-77`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if !src_slot.cap.rights.contains(CapRights::GRANT) {
|
||||||
|
return Err("Insufficient rights to mint (Missing GRANT flag)");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяет только GRANT флаг. Но не проверяет `relation`: если source — `Borrow`, можно ли mint? В текущей реализации — можно. Должен быть check:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if src_slot.cap.relation == Relation::Borrow {
|
||||||
|
return Err("Cannot mint from borrowed capability");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛡️ SEC 6.2 — CapGuardToken не реализован
|
||||||
|
|
||||||
|
В дескрипторе есть упоминание `guards: Vec<CapGuardToken>`, `Borrow` relation и guard-токены, логика самих guard'ов не реализована. Если есть borrow, mint не должен позволять дальнейшее распространение — но это не проверяется.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛡️ SEC 6.3 — Нет проверки прав на операции с PMM через capabilities
|
||||||
|
|
||||||
|
`BitmapPMM` полностью глобальный: любой код может вызвать `alloc_frame()`, `free_frame()` без проверки capability. Капы пока хранят токены и права, но PMM их не проверяет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полный пошаговый план (сортированный по приоритету)
|
||||||
|
|
||||||
|
### Этап 1: Исправить критические баги (сейчас — не работает)
|
||||||
|
|
||||||
|
```
|
||||||
|
[X] 1. ✅ HIGH — Исправить target_mask для >64 core
|
||||||
|
Добавлена проверка current_core < 64 перед shift.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 2. 🔥 HIGH — Исправить ref_counts инициализацию
|
||||||
|
Вместо fill(1) + free, сделать fill(0) + mark_used.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 2: Архитектурный рефакторинг
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 3. 🏗️ ARCH — Глобальный HHDM_OFFSET
|
||||||
|
AtomicU64 static, убрать hhdm из сигнатур.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
|
||||||
|
[ ] 4. 🏗️ ARCH — Intrusive free-list в buddy allocator
|
||||||
|
O(1) free вместо O(n).
|
||||||
|
Estimate: ~2 ч
|
||||||
|
|
||||||
|
[ ] 5. 🏗️ ARCH — Tree bitmap для PMM
|
||||||
|
word-level scan с ctz.
|
||||||
|
Estimate: ~1.5 ч
|
||||||
|
|
||||||
|
[ ] 6. 🏗️ ARCH — HLT-based wait в router channel
|
||||||
|
Пока нет планировщика — HLT вместо spin.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 7. 🏗️ ARCH — Capability revoke без O(n) scan
|
||||||
|
Children list в слоте.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
|
||||||
|
[ ] 8. 🏗️ ARCH — Identity-map через 2MB huge pages
|
||||||
|
map_region с HUGE_PAGE флагом для identity.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 3: Code Quality
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 9. 🧹 CQ — Удалить dead code (FramebufferDisplay, Console, embedded-graphics)
|
||||||
|
+ неиспользуемые импорты.
|
||||||
|
Estimate: ~20 мин
|
||||||
|
|
||||||
|
[ ] 10. 🧹 CQ — Убрать #![allow(dead_code)] из vmm.rs
|
||||||
|
Маркировать конкретные единицы.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
|
||||||
|
[ ] 11. 🧹 CQ — Удалить мёртвые linker-скрипты
|
||||||
|
aarch64/riscv64/loongarch64.ld.
|
||||||
|
Estimate: ~5 мин
|
||||||
|
|
||||||
|
[ ] 12. 🧹 CQ — Переименовать pm_manages.rs → pm_actor.rs
|
||||||
|
+ все референсы.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 13. 🧹 CQ — Вынести panic.rs из main.rs
|
||||||
|
Отдельный файл для panic_handler.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 14. 🧹 CQ — Синхронизировать имена: LISA → Elyz
|
||||||
|
Cargo.toml, main.rs ("LIS4"), README.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
|
||||||
|
[ ] 15. 🧹 CQ — Locked spinlock с exponential backoff
|
||||||
|
PAUSE + цикл задержки.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 4: Производительность
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 16. ⚡ PERF — Bitmap scan: word-level (u64) вместо byte-level
|
||||||
|
+ ctzl для поиска бита.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 17. ⚡ PERF — INVLPG-free bulk map
|
||||||
|
Убрать INVLPG из map_page, вызов flush_range после.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 18. ⚡ PERF — process_messages quota
|
||||||
|
Max N сообщений за вызов.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 5: Инфраструктура
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 19. 🛠️ INFRA — SMP AP startup code
|
||||||
|
SIPI → trampoline → long mode → Rust.
|
||||||
|
Estimate: ~1 неделя
|
||||||
|
|
||||||
|
[ ] 20. 🛠️ INFRA — Unit tests для buddy allocator
|
||||||
|
#[cfg(test)] модуль.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
|
||||||
|
[ ] 21. 🛠️ INFRA — GDB script + QEMU -s
|
||||||
|
.gdbinit для отладки.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 22. 🛠️ INFRA — Arch trait для x86_64
|
||||||
|
Подготовка к портированию.
|
||||||
|
Estimate: ~3-4 ч
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 6: Безопасность
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 23. 🛡️ SEC — Проверка Relation при mint
|
||||||
|
Запретить mint из Borrow.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 24. 🛡️ SEC — CapGuard реализация
|
||||||
|
Привязка guard-токенов к source slot при mint.
|
||||||
|
Estimate: ~45 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roadmap (визуально)
|
||||||
|
|
||||||
|
```
|
||||||
|
Неделя 1 Неделя 2 Неделя 3
|
||||||
|
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
|
||||||
|
│ ✅ CRIT 1–6 готово │▸▸ │ ARCH 3 HHDM global │▸▸ │ PERF 16 word-level PMM │
|
||||||
|
│ ✅ MED 1.7–1.10 │ │ ARCH 4 intrusive list │ │ PERF 17 INVLPG batch │
|
||||||
|
│ HIGH 2 ref_counts │ │ ARCH 5 tree bitmap │ │ INFRA 19 SMP startup │
|
||||||
|
│ │ │ CQ 9 clean dead code │ │ INFRA 20 unit tests │
|
||||||
|
│ │ │ CQ 11-15 rename/refact│ │ SEC 23-24 caps │
|
||||||
|
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
|
||||||
|
↓ ↓ ↓
|
||||||
|
Kernel bootable и Код читаемый, Производительность:
|
||||||
|
логирует без крашей архитектура чище SMP + оптимизации
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Документ создан 29 июня 2026 на основе анализа исходного кода проекта Elyz.*
|
||||||
152
kernel/docs/boot/introduction.md
Normal file
152
kernel/docs/boot/introduction.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Процесс загрузки: концептуальная модель
|
||||||
|
|
||||||
|
## Цепочка загрузки
|
||||||
|
|
||||||
|
```
|
||||||
|
Питание включено
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
CPU reset vector (0xFFFFFFF0)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
UEFI firmware / BIOS
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Limine bootloader ─────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 1. Переводит CPU в 64-bit long mode │
|
||||||
|
│ 2. Настраивает page tables (identity map) │
|
||||||
|
│ 3. Загружает ядро по физическому адресу │
|
||||||
|
│ 4. Настраивает HHDM │
|
||||||
|
│ 5. Заполняет Limine requests │
|
||||||
|
│ 6. Передаёт управление на kmain │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
kmain() (точка входа) │
|
||||||
|
│ │
|
||||||
|
... инициализация ... │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
HCF (останов CPU) ─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limine Boot Protocol
|
||||||
|
|
||||||
|
Elyz использует [Limine](https://github.com/limine-bootloader/limine) —
|
||||||
|
современный bootloader с открытым исходным кодом.
|
||||||
|
|
||||||
|
Статические `Limine requests` сообщают bootloader'у, что нужно ядру:
|
||||||
|
|
||||||
|
| Request | Назначение |
|
||||||
|
|---------|------------|
|
||||||
|
| `FramebufferRequest` | Получить framebuffer для графического вывода |
|
||||||
|
| `MemoryMapRequest` | Получить карту физической памяти |
|
||||||
|
| `HhdmRequest` | Получить HHDM offset |
|
||||||
|
| `ExecutableAddressRequest` | Получить физический/виртуальный адрес ядра |
|
||||||
|
| `BaseRevision` | Проверить совместимость с Limine |
|
||||||
|
|
||||||
|
### Размещение в секциях
|
||||||
|
|
||||||
|
Requests размещаются между специальными маркерами в секции `.data`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[used]
|
||||||
|
#[unsafe(link_section = ".requests_start_marker")]
|
||||||
|
static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
|
||||||
|
|
||||||
|
// ... все requests ...
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
#[unsafe(link_section = ".requests_end_marker")]
|
||||||
|
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
Линкер скрипт сохраняет эти секции:
|
||||||
|
```ld
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
} :data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Точка входа — kmain
|
||||||
|
|
||||||
|
Линкер скрипт: `ENTRY(kmain)`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn kmain() -> ! { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
- `no_mangle` — сохраняет имя `kmain` (линкер ищет именно его).
|
||||||
|
- `extern "C"` — C ABI (Linux x86-64 calling convention: RDI, RSI, ...).
|
||||||
|
- `unsafe` — на этапе инициализации все операции потенциально опасны.
|
||||||
|
- `-> !` — kmain никогда не возвращается (HALT).
|
||||||
|
|
||||||
|
## Build system
|
||||||
|
|
||||||
|
### GNUmakefile
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
KARCH ?= x86_64
|
||||||
|
RUST_TARGET ?= $(KARCH)-unknown-none
|
||||||
|
RUST_PROFILE ?= dev
|
||||||
|
|
||||||
|
all:
|
||||||
|
RUSTFLAGS="-C relocation-model=static" \
|
||||||
|
cargo build --target $(RUST_TARGET) --profile $(RUST_PROFILE)
|
||||||
|
cp target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR)/kernel .
|
||||||
|
```
|
||||||
|
|
||||||
|
- `relocation-model=static`: ядро не использует динамическую перелокацию.
|
||||||
|
- `--target x86_64-unknown-none`: bare-metal target (без ОС).
|
||||||
|
- Результат копируется в `kernel/` (корень).
|
||||||
|
|
||||||
|
### build.rs — linker script
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||||
|
println!("cargo:rustc-link-arg=-Tlinker-{arch}.ld");
|
||||||
|
println!("cargo:rerun-if-changed=linker-{arch}.ld");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Подставляет правильный linker script для архитектуры.
|
||||||
|
|
||||||
|
### rust-toolchain.toml
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
targets = ["x86_64-unknown-none"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Требуется nightly Rust из-за:
|
||||||
|
- `#![no_std]`, `#![no_main]`.
|
||||||
|
- `core::arch::global_asm!`, `core::arch::asm!`.
|
||||||
|
- `const { ... }` в инициализации констант.
|
||||||
|
|
||||||
|
## Память: расположение после загрузки
|
||||||
|
|
||||||
|
```
|
||||||
|
Физическая память:
|
||||||
|
┌───────────────────────┐ 0x0
|
||||||
|
│ Reserved │
|
||||||
|
├───────────────────────┤
|
||||||
|
│ Usable (free) │ ← входит в mmap entries
|
||||||
|
├───────────────────────┤
|
||||||
|
│ Kernel image │ ← загружен Limine
|
||||||
|
├───────────────────────┤
|
||||||
|
│ PMM metadata │ ← bitmap + ref_counts + L1
|
||||||
|
├───────────────────────┤
|
||||||
|
│ ... (usable) │
|
||||||
|
└───────────────────────┘ max_addr
|
||||||
|
|
||||||
|
Виртуальная память (Higher Half):
|
||||||
|
0xFFFF_8000_0000_0000 ─── HHDM (вся физическая память 1:1)
|
||||||
|
0xFFFF_9000_0000_0000 ─── Kernel heap (8 MiB)
|
||||||
|
```
|
||||||
151
kernel/docs/boot/linker.md
Normal file
151
kernel/docs/boot/linker.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Линкер-скрипты: архитектурные детали
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Линкер-скрипты управляют расположением секций ELF-образа ядра.
|
||||||
|
Для каждой архитектуры — свой скрипт, но все они следуют одной схеме.
|
||||||
|
|
||||||
|
## Общая структура
|
||||||
|
|
||||||
|
```
|
||||||
|
OUTPUT_FORMAT(...) ← формат ELF (зависит от архитектуры)
|
||||||
|
ENTRY(kmain) ← точка входа
|
||||||
|
|
||||||
|
PHDRS ← сегменты (program headers)
|
||||||
|
{
|
||||||
|
text PT_LOAD;
|
||||||
|
rodata PT_LOAD;
|
||||||
|
data PT_LOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS ← расположение секций
|
||||||
|
{
|
||||||
|
. = 0xffffffff80000000; ← база higher half
|
||||||
|
|
||||||
|
.text : { *(.text .text.*) } :text
|
||||||
|
|
||||||
|
. = ALIGN(MAXPAGESIZE);
|
||||||
|
.rodata : { *(.rodata .rodata.*) } :rodata
|
||||||
|
|
||||||
|
. = ALIGN(MAXPAGESIZE);
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
} :data
|
||||||
|
|
||||||
|
.bss : { *(.bss .bss.*) *(COMMON) } :data
|
||||||
|
|
||||||
|
/DISCARD/ : { *(.eh_frame*) *(.note .note.*) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Детали
|
||||||
|
|
||||||
|
### OUTPUT_FORMAT
|
||||||
|
|
||||||
|
| Архитектура | Формат |
|
||||||
|
|-------------|--------|
|
||||||
|
| x86_64 | `elf64-x86-64` |
|
||||||
|
| aarch64 | `elf64-littleaarch64` |
|
||||||
|
| riscv64 | `elf64-littleriscv` |
|
||||||
|
| loongarch64 | `elf64-loongarch` |
|
||||||
|
|
||||||
|
### Базовый адрес: 0xFFFFFFFF80000000
|
||||||
|
|
||||||
|
Ядро размещается в **higher half** — верхней 2 GiB виртуального
|
||||||
|
адресного пространства. Это стандартная практика для x86-64:
|
||||||
|
|
||||||
|
```
|
||||||
|
0x0000_0000_0000_00000 ─── user space (не используется ядром)
|
||||||
|
0xFFFF_8000_0000_00000 ─── kernel space (higher half)
|
||||||
|
0xFFFF_FFFF_FFFF_FFFF ─── конец
|
||||||
|
```
|
||||||
|
|
||||||
|
Любой адрес в области 0xFFFF800000000000 — 0xFFFFFFFFFFFFFFFF корректен;
|
||||||
|
0xFFFFFFFF80000000 выбран как начало typical higher half региона.
|
||||||
|
|
||||||
|
### PHDRS: PT_LOAD сегменты
|
||||||
|
|
||||||
|
Bootloader загружает только PT_LOAD сегменты. Их три:
|
||||||
|
|
||||||
|
1. **text**: код + inline-константы.
|
||||||
|
2. **rodata**: неизменяемые данные (строки, таблицы).
|
||||||
|
3. **data**: изменяемые данные + BSS.
|
||||||
|
|
||||||
|
### Section alignment
|
||||||
|
|
||||||
|
```ld
|
||||||
|
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||||
|
```
|
||||||
|
|
||||||
|
MAXPAGESIZE = 0x1000 (4 KiB). Каждая секция начинается с новой страницы,
|
||||||
|
что даёт bootloader'у правильные MMU permissionы:
|
||||||
|
- text = read + execute (no write)
|
||||||
|
- rodata = read (no write, no execute)
|
||||||
|
- data = read + write (no execute)
|
||||||
|
|
||||||
|
### Limine Requests в .data
|
||||||
|
|
||||||
|
```ld
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
```
|
||||||
|
|
||||||
|
- `KEEP` — запрещает линкеру выбрасывать эти секции (dead code elimination).
|
||||||
|
- `.requests_start_marker` и `.requests_end_marker` — маркеры границ.
|
||||||
|
- Bootloader сканирует память между ними, чтобы найти requests.
|
||||||
|
|
||||||
|
### BSS
|
||||||
|
|
||||||
|
```ld
|
||||||
|
.bss : {
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
} :data
|
||||||
|
```
|
||||||
|
|
||||||
|
- BSS — неинициализированные глобальные переменные.
|
||||||
|
- Занимает место в виртуальной памяти, но не в ELF-файле.
|
||||||
|
- Bootloader обнуляет BSS перед передачей управления.
|
||||||
|
|
||||||
|
### DISCARD
|
||||||
|
|
||||||
|
```ld
|
||||||
|
/DISCARD/ : {
|
||||||
|
*(.eh_frame*)
|
||||||
|
*(.note .note.*)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `.eh_frame*` — исключительные фреймы C++/Rust unwinding.
|
||||||
|
- `.note.*` — ELF notes.
|
||||||
|
- Не нужны bare-metal ядру, могут вызвать проблемы.
|
||||||
|
|
||||||
|
## Архитектурные различия
|
||||||
|
|
||||||
|
### RISC-V
|
||||||
|
|
||||||
|
```ld
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
*(.sdata .sdata.*) ← RISC-V: small data
|
||||||
|
} :data
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
*(.sbss .sbss.*) ← RISC-V: small BSS
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
} :data
|
||||||
|
```
|
||||||
|
|
||||||
|
RISC-V имеет `.sdata`/`.sbss` секции для small data (GP-relative addressing).
|
||||||
|
|
||||||
|
### AArch64 и LoongArch64
|
||||||
|
|
||||||
|
Идентичны x86_64, за исключением OUTPUT_FORMAT.
|
||||||
146
kernel/docs/capability/cnode.md
Normal file
146
kernel/docs/capability/cnode.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# CNode: таблица capability: `mod.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**CNode** (Capability Node) — это массив слотов, каждый из которых
|
||||||
|
может хранить один capability. Это аналог файловой таблицы в Unix,
|
||||||
|
но для capabilities.
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode {
|
||||||
|
slots: Vec<Locked<CNodeSlot>>
|
||||||
|
}
|
||||||
|
|
||||||
|
CNodeSlot {
|
||||||
|
cap: Capability,
|
||||||
|
parent_idx: Option<usize>, // индекс родительского слота
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn new(size: usize) -> Self {
|
||||||
|
let mut slots = Vec::with_capacity(size);
|
||||||
|
for _ in 0..size {
|
||||||
|
slots.push(Locked::new(CNodeSlot {
|
||||||
|
cap: Capability::empty(),
|
||||||
|
parent_idx: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Self { slots }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Все слоты изначально пустые (`Capability::empty()`).
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### insert(slot, cap) — вставка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn insert(&self, slot: usize, cap: Capability) -> Result<(), &'static str> {
|
||||||
|
if slot >= self.slots.len() { return Err("Index out of bounds"); }
|
||||||
|
let mut s = self.slots[slot].lock();
|
||||||
|
s.cap = cap;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Простая вставка без проверки (перезаписывает существующий).
|
||||||
|
|
||||||
|
### mint(src, dest, relation, rights) — создание потомка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn mint(&self, src: usize, dest: usize, relation: Relation, rights: CapRights) -> Result<(), &'static str>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Валидация:**
|
||||||
|
- src и dest в пределах массива.
|
||||||
|
- src != dest (дедлок не имеет смысла, но блокировка была бы корректна).
|
||||||
|
- src содержит валидный capability.
|
||||||
|
- src имеет GRANT.
|
||||||
|
|
||||||
|
**Процесс:**
|
||||||
|
1. Захват блокировок в порядке возрастания индекса (lock ranking).
|
||||||
|
2. Вычисление `final_rights = src.rights & rights`.
|
||||||
|
3. Копирование Capability в dest с новыми правами и relation.
|
||||||
|
4. Установка `parent_idx = Some(src)`.
|
||||||
|
|
||||||
|
### revoke(slot_idx) — отзыв
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn revoke(&self, slot_idx: usize) -> Result<(), &'static str>
|
||||||
|
```
|
||||||
|
|
||||||
|
Каскадное удаление:
|
||||||
|
|
||||||
|
```
|
||||||
|
revoke_internal(slot_idx):
|
||||||
|
│
|
||||||
|
├── 1. Поиск потомков:
|
||||||
|
│ for i in 0..slots.len():
|
||||||
|
│ if slots[i].parent_idx == Some(slot_idx):
|
||||||
|
│ revoke_internal(i) ← рекурсивно!
|
||||||
|
│
|
||||||
|
├── 2. Уничтожение себя:
|
||||||
|
│ cap = Capability::empty()
|
||||||
|
│ parent_idx = None
|
||||||
|
│ token = old_token_sig
|
||||||
|
│
|
||||||
|
└── 3. Отправка token в очередь:
|
||||||
|
if token != 0 && token != 0xDEAD_BEEF:
|
||||||
|
MMU_REVOCATION_QUEUE.push(token)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Почему нет блокировок при рекурсии?**
|
||||||
|
- На каждом шаге проверка `is_child` захватывает и отпускает блокировку.
|
||||||
|
- Рекурсивный вызов происходит **после** освобождения блокировки.
|
||||||
|
- Это предотвращает взаимоблокировки.
|
||||||
|
|
||||||
|
**Фильтр токенов:**
|
||||||
|
- `token == 0`: пустой/невалидный capability.
|
||||||
|
- `token == 0xDEAD_BEEF`: сырой Untyped (для тестов/отладки).
|
||||||
|
- Эти токены не отправляются в очередь (бессмысленно).
|
||||||
|
|
||||||
|
### get_cap(slot) — чтение
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn get_cap(&self, slot: usize) -> Option<Capability> {
|
||||||
|
self.slots.get(slot).map(|s| s.lock().cap)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lock Ranking — детали
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let (src_slot, dest_slot) = if src < dest {
|
||||||
|
_guard_low = self.slots[src].lock(); // меньший → первый
|
||||||
|
_guard_high = self.slots[dest].lock(); // больший → второй
|
||||||
|
} else {
|
||||||
|
_guard_low = self.slots[dest].lock();
|
||||||
|
_guard_high = self.slots[src].lock();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Причина:** если поток A делает mint(5, 10), а поток B делает mint(10, 5),
|
||||||
|
без lock ranking они могут взаимно заблокироваться:
|
||||||
|
- A: lock(5) → ждёт lock(10)
|
||||||
|
- B: lock(10) → ждёт lock(5)
|
||||||
|
|
||||||
|
С lock ranking:
|
||||||
|
- A: lock(5) → lock(10)
|
||||||
|
- B: lock(5) → ждёт... → дождался → lock(10)
|
||||||
|
|
||||||
|
## Интеграция с событиями
|
||||||
|
|
||||||
|
После `revoke()`, токен попадает в `MMU_REVOCATION_QUEUE` (см.
|
||||||
|
`events.rs`). VMM обрабатывает очередь в `process_pending_revocations()`.
|
||||||
|
|
||||||
|
## Особенности реализации
|
||||||
|
|
||||||
|
1. **Vec<Locked<CNodeSlot>>**: каждый слот — отдельная spinlock-ячейка.
|
||||||
|
Это позволяет параллельно читать разные слоты.
|
||||||
|
2. **`pub slots`**: прямой доступ к слоту возможен (для тестов).
|
||||||
|
3. **panic на overflow**: если очередь отзыва переполнена — паника.
|
||||||
|
Это критический сбой подсистемы ресурсов.
|
||||||
115
kernel/docs/capability/descriptors.md
Normal file
115
kernel/docs/capability/descriptors.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Дескрипторы capability: `descriptor.rs`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Файл определяет базовые типы capability-системы:
|
||||||
|
что такое capability, какие бывают объекты, права и отношения.
|
||||||
|
|
||||||
|
## CapObject — что представляет capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum CapObject {
|
||||||
|
Empty, // Пустой слот
|
||||||
|
Memory { phys: PhysAddr, size_pages: usize }, // Фрейм памяти
|
||||||
|
CNode { phys: PhysAddr, slots: usize }, // Другой CNode
|
||||||
|
PMActor { id: u64 }, // PM Actor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory
|
||||||
|
|
||||||
|
Capability на физическую память:
|
||||||
|
- `phys` — физический адрес начала.
|
||||||
|
- `size_pages` — размер в страницах.
|
||||||
|
|
||||||
|
### CNode
|
||||||
|
|
||||||
|
Capability на другой CNode:
|
||||||
|
- `phys` — физический адрес CNode.
|
||||||
|
- `slots` — количество слотов.
|
||||||
|
|
||||||
|
### PMActor
|
||||||
|
|
||||||
|
Capability на PMActor:
|
||||||
|
- `id` — уникальный идентификатор актора.
|
||||||
|
|
||||||
|
### Empty
|
||||||
|
|
||||||
|
Слот пуст. `is_valid()` возвращает `false`.
|
||||||
|
|
||||||
|
## CapRights — права доступа
|
||||||
|
|
||||||
|
```rust
|
||||||
|
bitflags! {
|
||||||
|
pub struct CapRights: u8 {
|
||||||
|
const READ = 1 << 0; // 0x01 — чтение
|
||||||
|
const WRITE = 1 << 1; // 0x02 — запись
|
||||||
|
const EXECUTE = 1 << 2; // 0x04 — исполнение
|
||||||
|
const GRANT = 1 << 3; // 0x08 — разрешение на mint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Правила:**
|
||||||
|
- Права дочернего capability = `родительские_права & запрошенные_права`.
|
||||||
|
- Нельзя расширить права: если родитель не имеет GRANT, mint невозможен.
|
||||||
|
- `CapRights::all()` = R | W | X | G = 0x0F.
|
||||||
|
|
||||||
|
## Relation — тип связи
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum Relation {
|
||||||
|
Strong, // Владелец — сильная ссылка (объект жив, пока есть Strong)
|
||||||
|
Borrow, // Заёмщик — временный доступ
|
||||||
|
Transfer, // Передача — владение переходит без возможности отзыва
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Strong**: capability владеет объектом. При revoke, объект может
|
||||||
|
быть уничтожен или возвращён пулу.
|
||||||
|
- **Borrow**: capability предоставляет временный доступ.
|
||||||
|
При revoke родителя, borrow-потомки тоже отзываются.
|
||||||
|
- **Transfer**: полная передача владения. Используется при IPC.
|
||||||
|
|
||||||
|
## Capability — полный дескриптор
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Capability {
|
||||||
|
pub object: CapObject, // Целевой объект
|
||||||
|
pub rights: CapRights, // Права доступа
|
||||||
|
pub relation: Relation, // Тип связи
|
||||||
|
pub token_sig: u64, // Уникальный подписывающий токен
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### token_sig — назначение
|
||||||
|
|
||||||
|
- Уникальный 64-битный идентификатор capability.
|
||||||
|
- Используется для:
|
||||||
|
1. **Отзыва**: при revoke, token_sig отправляется в MMU_REVOCATION_QUEUE.
|
||||||
|
2. **Идентификации в VMM**: VMA хранят `cap_token == token_sig`.
|
||||||
|
3. **Отладки**: каждый capability можно однозначно отследить.
|
||||||
|
|
||||||
|
### Методы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl Capability {
|
||||||
|
pub const fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
object: CapObject::Empty,
|
||||||
|
rights: CapRights::empty(),
|
||||||
|
relation: Relation::Borrow,
|
||||||
|
token_sig: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
!matches!(self.object, CapObject::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`token_sig = 0` зарезервирован для пустых/невалидных capability.
|
||||||
|
`token_sig = 0xDEAD_BEEF` используется для сырых Untyped
|
||||||
|
(не отправляется в MMU_REVOCATION_QUEUE, см. `cap/mod.rs`).
|
||||||
126
kernel/docs/capability/introduction.md
Normal file
126
kernel/docs/capability/introduction.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Capability-система: концептуальная модель
|
||||||
|
|
||||||
|
## Философия
|
||||||
|
|
||||||
|
**Capability** (дескриптор возможности) — это **неподделываемый токен**,
|
||||||
|
дающий право выполнить определённую операцию над определённым объектом.
|
||||||
|
|
||||||
|
В традиционных ОС (Linux, Windows) доступ контролируется через:
|
||||||
|
- PID + UID/GID + проверка при каждом системном вызове.
|
||||||
|
- MMU: page tables определяют, что отображено, но не кто отобразил.
|
||||||
|
|
||||||
|
В модели capabilities:
|
||||||
|
- **Если у вас нет capability — у вас нет доступа.**
|
||||||
|
- Capability хранятся в CNode — защищённой таблице, доступной только ядру.
|
||||||
|
- Capability можно создавать только от родительского capability
|
||||||
|
(иерархия наследования).
|
||||||
|
- Права можно только **урезать** (mint), но не расширить.
|
||||||
|
- Capability можно **отозвать** (revoke), что уничтожает его
|
||||||
|
и всех его потомков.
|
||||||
|
|
||||||
|
## Основные понятия
|
||||||
|
|
||||||
|
```
|
||||||
|
Capability {
|
||||||
|
object: CapObject, // на что указывает (Memory, CNode, PMActor...)
|
||||||
|
rights: CapRights, // права (R, W, X, G)
|
||||||
|
relation: Relation, // Strong (владеет), Borrow (заём), Transfer
|
||||||
|
token_sig: u64, // уникальный идентификатор (для revoke)
|
||||||
|
}
|
||||||
|
|
||||||
|
Relation {
|
||||||
|
Strong: владеет объектом (capability владеет памятью)
|
||||||
|
Borrow: заём — временный доступ без права распоряжаться
|
||||||
|
Transfer: передача — владение переходит получателю
|
||||||
|
}
|
||||||
|
|
||||||
|
CapRights {
|
||||||
|
READ = 0x1,
|
||||||
|
WRITE = 0x2,
|
||||||
|
EXECUTE = 0x4,
|
||||||
|
GRANT = 0x8, // разрешение создавать дочерние capability
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Иерархия и отзыв
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode (массив слотов)
|
||||||
|
┌────────┬────────┬────────┬────────┐
|
||||||
|
│ slot 0 │ slot 1 │ slot 2 │ slot 3 │ ...
|
||||||
|
├────────┼────────┼────────┼────────┤
|
||||||
|
│ cap │ cap │ cap │ cap │
|
||||||
|
│ parent:│ parent:│ parent:│ parent:│
|
||||||
|
│ None │ Some(0)│ Some(0)│ Some(1)│
|
||||||
|
└────────┴────────┴────────┴────────┘
|
||||||
|
│
|
||||||
|
┌────────┴────────┐
|
||||||
|
▼ ▼
|
||||||
|
slot 1 slot 2
|
||||||
|
(mint from 0) (mint from 0)
|
||||||
|
|
||||||
|
revoke(0) → slot 0 уничтожается
|
||||||
|
→ рекурсивно: slot 1, slot 2 тоже уничтожаются
|
||||||
|
→ token_sig slot 0 отправляется в MMU_REVOCATION_QUEUE
|
||||||
|
→ VMM обработает отзыв при следующем page fault
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mint — создание дочернего capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cnode.mint(src, dest, relation, rights)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src` — исходный слот (должен иметь GRANT).
|
||||||
|
- `dest` — целевой слот (должен быть пустым).
|
||||||
|
- `relation` — как наследник связан с родителем.
|
||||||
|
- `rights` — права наследника (∩ с правами родителя).
|
||||||
|
|
||||||
|
### Revoke — отзыв capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cnode.revoke(slot_idx)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Рекурсивно находит всех потомков и уничтожает их.
|
||||||
|
2. Уничтожает сам capability.
|
||||||
|
3. Отправляет `token_sig` в глобальную `MMU_REVOCATION_QUEUE`.
|
||||||
|
4. VMM при следующем page fault обрабатывает все накопленные отзывы.
|
||||||
|
|
||||||
|
## Lock Ranking — предотвращение дедлоков
|
||||||
|
|
||||||
|
В `mint()` захватываются две блокировки (src и dest). Чтобы избежать
|
||||||
|
инверсии блокировок, используется строгий порядок:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let (src_slot, dest_slot) = if src < dest {
|
||||||
|
// Захватываем меньший индекс первым
|
||||||
|
_guard_low = self.slots[src].lock();
|
||||||
|
_guard_high = self.slots[dest].lock();
|
||||||
|
} else {
|
||||||
|
_guard_low = self.slots[dest].lock();
|
||||||
|
_guard_high = self.slots[src].lock();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Интеграция с VMM
|
||||||
|
|
||||||
|
При отзыве capability, VMM должна аннулировать все VMA, связанные
|
||||||
|
с отозванным токеном. Для этого:
|
||||||
|
|
||||||
|
1. `revoke()` пушит `token_sig` в lock-free очередь.
|
||||||
|
2. При page fault: `process_pending_revocations()` дренирует очередь.
|
||||||
|
3. VMA с `cap_token == token_sig` удаляются из AddressSpace.
|
||||||
|
4. TLB flush для синхронизации MMU.
|
||||||
|
|
||||||
|
## Использование в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let root_cnode = CNode::new(256);
|
||||||
|
// Вставка capability на фрейм
|
||||||
|
root_cnode.insert(0, mem_cap).unwrap();
|
||||||
|
// Mint с урезанными правами
|
||||||
|
root_cnode.mint(0, 10, Relation::Borrow, R | W).unwrap();
|
||||||
|
// Revoke — отзыв всех потомков
|
||||||
|
root_cnode.revoke(0);
|
||||||
|
```
|
||||||
73
kernel/docs/capability/objects.md
Normal file
73
kernel/docs/capability/objects.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Ядерные объекты: `object.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
`KernelObject` — это ref-counted представление объекта ядра.
|
||||||
|
В отличие от `Capability` (которая указывает на объект), `KernelObject`
|
||||||
|
— это сам объект с подсчётом ссылок.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct KernelObject {
|
||||||
|
pub phys_addr: PhysAddr, // физический адрес объекта
|
||||||
|
pub size_bits: u8, // размер в битах (2^size_bits)
|
||||||
|
pub obj_type: ObjectType, // тип объекта
|
||||||
|
pub ref_count: AtomicUsize, // счётчик ссылок
|
||||||
|
pub owner_id: u64, // ID владельца
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ObjectType — классификация объектов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum ObjectType {
|
||||||
|
Untyped, // Сырая память без типа
|
||||||
|
Frame, // Фрейм (4KiB страница)
|
||||||
|
CNode, // Capability Node
|
||||||
|
ThreadBlock, // Блок управления потоком (TCB)
|
||||||
|
PageTable, // Таблица страниц
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ref-counting
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl KernelObject {
|
||||||
|
pub fn add_ref(&self) {
|
||||||
|
self.ref_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
// ^ Relaxed: нас не волнует порядок других операций
|
||||||
|
// при увеличении счётчика. Только атомарность.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(&self) -> bool {
|
||||||
|
self.ref_count.fetch_sub(1, Ordering::Release) == 1
|
||||||
|
// ^ Release: все операции до release видны тому,
|
||||||
|
// кто Acquire-читает ref_count.
|
||||||
|
// Возвращает true, если это была последняя ссылка
|
||||||
|
// (объект должен быть уничтожен).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Текущее состояние
|
||||||
|
|
||||||
|
`KernelObject` определён, но пока не используется активно.
|
||||||
|
Capability-система в текущей версии работает напрямую с `CapObject`
|
||||||
|
и `PhysAddr`, без обёртки в `KernelObject`.
|
||||||
|
|
||||||
|
Планируемое использование:
|
||||||
|
- При выделении памяти через PMActor: создаётся KernelObject.
|
||||||
|
- Capability ссылается на KernelObject через ID/индекс.
|
||||||
|
- Когда последняя Strong capability удалена → KernelObject
|
||||||
|
уничтожается → память возвращается.
|
||||||
|
|
||||||
|
## Отличие CapObject vs KernelObject
|
||||||
|
|
||||||
|
| Характеристика | CapObject | KernelObject |
|
||||||
|
|---------------|-----------|--------------|
|
||||||
|
| Роль | Что capability представляет | Сам объект в памяти ядра |
|
||||||
|
| Ref-count | Нет | AtomicUsize |
|
||||||
|
| Хранение | В CNode slot | В отдельной таблице |
|
||||||
|
| Типы | Memory, CNode, PMActor | Untyped, Frame, CNode, ThreadBlock, PageTable |
|
||||||
|
| Связь | CapObject.Memory.phys = KernelObject.phys_addr | — |
|
||||||
115
kernel/docs/cpu/idt.md
Normal file
115
kernel/docs/cpu/idt.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Interrupt Descriptor Table: `idt.rs`
|
||||||
|
|
||||||
|
## Аппаратная модель
|
||||||
|
|
||||||
|
IDT (Interrupt Descriptor Table) — это таблица из 256 entry (по 16 байт каждая),
|
||||||
|
которая сообщает CPU, куда передавать управление при прерываниях и исключениях.
|
||||||
|
|
||||||
|
```
|
||||||
|
IDT:
|
||||||
|
┌──────┬──────────────────────────────────────────────────────┐
|
||||||
|
│ 0 │ #DE — Divide Error │
|
||||||
|
│ 1 │ #DB — Debug │
|
||||||
|
│ 2 │ #NMI — Non-Maskable Interrupt │
|
||||||
|
│ 3 │ #BP — Breakpoint │
|
||||||
|
│ 4 │ #OF — Overflow │
|
||||||
|
│ 5 │ #BR — Bound Range Exceeded │
|
||||||
|
│ 6 │ #UD — Undefined Opcode │
|
||||||
|
│ 7 │ #NM — Device Not Available │
|
||||||
|
│ 8 │ #DF — Double Fault │
|
||||||
|
│ 9 │ #MF — Coprocessor Segment Overrun │
|
||||||
|
│ 10 │ #TS — Invalid TSS │
|
||||||
|
│ 11 │ #NP — Segment Not Present │
|
||||||
|
│ 12 │ #SS — Stack-Segment Fault │
|
||||||
|
│ 13 │ #GP — General Protection Fault │
|
||||||
|
│ 14 │ #PF — Page Fault │
|
||||||
|
│ 15-31│ Reserved / CPU exceptions │
|
||||||
|
│ 32-255│ User-defined (hardware interrupts) │
|
||||||
|
└──────┴──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Загрузка IDT: инструкция `lidt [idtr_ptr]`, где `idtr_ptr` — это
|
||||||
|
6-байтовая структура `IdtPtr`:
|
||||||
|
```
|
||||||
|
IdtPtr:
|
||||||
|
┌──────────┬──────────┐
|
||||||
|
│ limit:16 │ base:48 │
|
||||||
|
└──────────┴──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## IdtEntry — 16-байтовая запись
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct IdtEntry {
|
||||||
|
offset_low: u16, // Бит 0:15 адреса обработчика
|
||||||
|
selector: u16, // Селектор сегмента кода (0x28 для ядра)
|
||||||
|
ist: u8, // Interrupt Stack Table
|
||||||
|
type_attr: u8, // Тип вентиля + флаги
|
||||||
|
offset_mid: u16, // Бит 16:31 адреса обработчика
|
||||||
|
offset_high: u32, // Бит 32:63 адреса обработчика
|
||||||
|
ignore: u32, // Зарезервировано
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**type_attr:**
|
||||||
|
- Бит 7: Present (должен быть 1)
|
||||||
|
- Бит 6-5: DPL (Descriptor Privilege Level)
|
||||||
|
- Бит 4: Reserved (0)
|
||||||
|
- Бит 3-0: Gate Type (0xE = Interrupt Gate, 0xF = Trap Gate)
|
||||||
|
|
||||||
|
`set_handler(handler, selector, flags)`:
|
||||||
|
```rust
|
||||||
|
pub fn set_handler(&mut self, handler: u64, selector: u16, flags: u8) {
|
||||||
|
self.offset_low = handler as u16;
|
||||||
|
self.selector = selector;
|
||||||
|
self.ist = 0;
|
||||||
|
self.type_attr = flags | 0x80; // Present bit forced on
|
||||||
|
self.offset_mid = (handler >> 16) as u16;
|
||||||
|
self.offset_high = (handler >> 32) as u32;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## InterruptDescriptorTable — 256 entry
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct InterruptDescriptorTable {
|
||||||
|
entries: [IdtEntry; 256], // 256 × 16 = 4096 байт
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Методы:**
|
||||||
|
- `set_handler(vector, handler)` — устанавливает обработчик с
|
||||||
|
селектором 0x28 (GDT code segment) и type_attr 0x8E
|
||||||
|
(Interrupt Gate, Ring 0, Present).
|
||||||
|
- `load()` — `lidt` инструкция.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn load(&'static self) {
|
||||||
|
let ptr = IdtPtr {
|
||||||
|
limit: (size_of::<Self>() - 1) as u16, // 4095
|
||||||
|
base: self as *const _ as u64,
|
||||||
|
};
|
||||||
|
asm!("lidt [{}]", in(reg) &ptr);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальная IDT
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
`static mut` — потому что IDT модифицируется в ранней инициализации,
|
||||||
|
до включения прерываний. Потенциально может быть заменён на `static`
|
||||||
|
с `UnsafeCell`.
|
||||||
|
|
||||||
|
## Детали конфигурации
|
||||||
|
|
||||||
|
- **Selector**: `0x28` — это GDT entry для ring 0 code segment
|
||||||
|
(дескриптор 5, 5 × 8 = 0x28). Селектор сегмента кода в long mode.
|
||||||
|
- **Type 0x8E**: `1000_1110` = бит 7 (Present) + бит 3:0 = 1110
|
||||||
|
(Interrupt Gate, 32-bit). В 64-bit режиме все вентили — 64-bit,
|
||||||
|
флаг 0xE остаётся корректным.
|
||||||
|
- **IST**: 0 — не используем Interrupt Stack Table (один стек для
|
||||||
|
всех обработчиков).
|
||||||
200
kernel/docs/cpu/interrupts.md
Normal file
200
kernel/docs/cpu/interrupts.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
# Обработчики прерываний и исключений: `interrupts.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
Файл объединяет **обработчики исключений** (таблицу IDT) и
|
||||||
|
**межпроцессорные прерывания** (TLB shootdown). Это связующий слой
|
||||||
|
между аппаратурой (CPU exceptions, APIC) и программными подсистемами
|
||||||
|
(VMM, Memory).
|
||||||
|
|
||||||
|
## Структура обработчика (stub + Rust handler)
|
||||||
|
|
||||||
|
Каждый обработчик состоит из двух частей:
|
||||||
|
|
||||||
|
1. **Сборочный stub** (global_asm): сохраняет контекст, вызывает
|
||||||
|
Rust-функцию, восстанавливает контекст, iretq.
|
||||||
|
2. **Rust handler**: собственно обработка.
|
||||||
|
|
||||||
|
```
|
||||||
|
Пример: Page Fault
|
||||||
|
[stack]
|
||||||
|
page_fault_stub: error_code ← CPU пушет
|
||||||
|
push rax ...регистры...
|
||||||
|
push rcx
|
||||||
|
...
|
||||||
|
mov rdi, [rsp + 15*8] ← error_code как аргумент
|
||||||
|
call rust_page_fault_handler ← вызов Rust
|
||||||
|
pop r15 восстановление
|
||||||
|
...
|
||||||
|
pop rax
|
||||||
|
add rsp, 8 ← убираем error_code
|
||||||
|
iretq ← возврат
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exception stubs (макрос)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! exception_stub {
|
||||||
|
($name:ident, $handler:ident) => {
|
||||||
|
concat!(
|
||||||
|
".global ", stringify!($name), "\n",
|
||||||
|
stringify!($name), ":\n",
|
||||||
|
"push rax\npush rcx\n...push r15\n", // сохранение
|
||||||
|
"mov rdi, [rsp + 15*8]\n", // error_code
|
||||||
|
"call ", stringify!($handler), "\n",
|
||||||
|
"pop r15\n...pop rax\n", // восстановление
|
||||||
|
"add rsp, 8\n", // очистка error_code
|
||||||
|
"iretq\n",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Используется для:
|
||||||
|
- `page_fault_stub` → `rust_page_fault_handler` (вектор 14)
|
||||||
|
- `gpf_stub` → `rust_gpf_handler` (вектор 13)
|
||||||
|
- `double_fault_stub` → `rust_double_fault_handler` (вектор 8)
|
||||||
|
|
||||||
|
## TLB shootdown stub
|
||||||
|
|
||||||
|
```rust
|
||||||
|
global_asm!(
|
||||||
|
"tlb_shootdown_stub:",
|
||||||
|
"push rax\npush rcx\n...", // сохранение
|
||||||
|
"call rust_tlb_shootdown_handler",
|
||||||
|
"pop r15\n...pop rax\n", // восстановление
|
||||||
|
"iretq"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Early handlers — для начальной загрузки
|
||||||
|
|
||||||
|
До того как VMM и slab allocator готовы, ядро не может обрабатывать
|
||||||
|
сложные исключения. Для векторов 0-31 генерируются ранние заглушки.
|
||||||
|
|
||||||
|
### Генерация (макрос `.altmacro`)
|
||||||
|
|
||||||
|
```asm
|
||||||
|
.macro early_stub vec
|
||||||
|
.globl early_handler_\vec
|
||||||
|
.balign 16
|
||||||
|
early_handler_\vec:
|
||||||
|
push 0 /* dummy error code (если нет аппаратного) */
|
||||||
|
push \vec /* номер вектора */
|
||||||
|
jmp early_common
|
||||||
|
.endm
|
||||||
|
```
|
||||||
|
|
||||||
|
`early_stub 0`..`early_stub 31` генерирует 32 обработчика.
|
||||||
|
|
||||||
|
### `early_common`
|
||||||
|
|
||||||
|
```asm
|
||||||
|
early_common:
|
||||||
|
push rax\npush rcx\n... // сохранение всех GP-регистров
|
||||||
|
mov rdi, [rsp + 15*8] // vector number
|
||||||
|
mov rsi, [rsp + 16*8] // error code (или dummy 0)
|
||||||
|
call rust_early_exception_handler
|
||||||
|
// never returns
|
||||||
|
```
|
||||||
|
|
||||||
|
### `rust_early_exception_handler(vector, error_code)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub extern "C" fn rust_early_exception_handler(vector: u64, _error_code: u64) -> ! {
|
||||||
|
serial::write_global(format_args!(
|
||||||
|
"\n!!! EARLY EXCEPTION !!! vector={} error_code={:#x}\nCPU halted.\n",
|
||||||
|
vector, _error_code
|
||||||
|
));
|
||||||
|
loop { asm!("cli; hlt"); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Всегда HALT — раннее исключение фатально.
|
||||||
|
|
||||||
|
## init_early_exceptions() — настройка IDT для ранней загрузки
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init_early_exceptions() {
|
||||||
|
let idt = addr_of_mut!(IDT);
|
||||||
|
// Устанавливаем early_handler_N для векторов 0-31
|
||||||
|
for (v, &handler) in early_handlers.iter().enumerate() {
|
||||||
|
(*idt).set_handler(v as u8, handler);
|
||||||
|
}
|
||||||
|
// Переопределяем критические:
|
||||||
|
(*idt).set_handler(8, double_fault_stub); // #DF
|
||||||
|
(*idt).set_handler(13, gpf_stub); // #GPF
|
||||||
|
(*idt).set_handler(14, page_fault_stub); // #PF
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub);
|
||||||
|
|
||||||
|
let ptr: &'static IDT = &*addr_of!(IDT);
|
||||||
|
ptr.load(); // lidt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## init_idt() — перезагрузка после полной инициализации
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init_idt() {
|
||||||
|
// Переустанавливаем только Page Fault и TLB Shootdown
|
||||||
|
// (их обработчики уже переключились на VMM-aware версии)
|
||||||
|
let idt = addr_of_mut!(IDT);
|
||||||
|
(*idt).set_handler(14, page_fault_stub);
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub);
|
||||||
|
let ptr: &'static IDT = &*addr_of!(IDT);
|
||||||
|
ptr.load();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработчики исключений
|
||||||
|
|
||||||
|
### `rust_page_fault_handler(error_code)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
||||||
|
let fault_addr: u64;
|
||||||
|
asm!("mov {}, cr2", out(reg) fault_addr); // читаем CR2
|
||||||
|
|
||||||
|
let write = (error_code & 0x2) != 0; // fault на запись?
|
||||||
|
let present = (error_code & 0x1) != 0; // PTE был PRESENT?
|
||||||
|
|
||||||
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
|
space.process_pending_revocations(); // обрабатываем отзывы
|
||||||
|
match space.handle_fault(fault_addr, write) {
|
||||||
|
Ok(_) => {} // обработано: iretq retry
|
||||||
|
Err(e) => panic!(...), // необработанный fault
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Page fault before KERNEL_SPACE!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `rust_gpf_handler(error_code)` и `rust_double_fault_handler(error_code)`
|
||||||
|
|
||||||
|
Оба — HALT с сообщением.
|
||||||
|
|
||||||
|
## TLB Shootdown обработчик
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub extern "C" fn rust_tlb_shootdown_handler() {
|
||||||
|
crate::mem::vmm::handle_tlb_shootdown_ipi();
|
||||||
|
crate::cpu::lapic::send_eoi(); // подтверждаем LAPIC прерывание
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тайминги и безопасность
|
||||||
|
|
||||||
|
- TLB shootdown — IPI, требует минимальной задержки.
|
||||||
|
- Все операции в `handle_tlb_shootdown_ipi` — простые и быстрые.
|
||||||
|
- LAPIC EOI отправляется сразу после локального TLB flush.
|
||||||
|
|
||||||
|
## Константы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
||||||
|
```
|
||||||
|
|
||||||
|
Вектор 0xFD (253) — в диапазоне пользовательских прерываний (32-255),
|
||||||
|
намеренно далеко от системных векторов 0-31.
|
||||||
106
kernel/docs/cpu/introduction.md
Normal file
106
kernel/docs/cpu/introduction.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# CPU подсистема: концептуальная модель
|
||||||
|
|
||||||
|
## Состав и ответственность
|
||||||
|
|
||||||
|
CPU подсистема отвечает за:
|
||||||
|
|
||||||
|
1. **Обработку прерываний и исключений** — IDT, обработчики.
|
||||||
|
2. **Исключения ранней загрузки** — пока ядро ещё не полностью инициализировано.
|
||||||
|
3. **TLB Shootdown** — межпроцессорное прерывание для синхронизации TLB.
|
||||||
|
4. **Local APIC** — программируемый контроллер прерываний.
|
||||||
|
|
||||||
|
```
|
||||||
|
CPU Subsystem
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ CPU Core #0 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌─────────────────────────┐ │
|
||||||
|
│ │ LAPIC │ │ IDT │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ICR ────────┼────────► 0: #DE (Divide Error) │ │
|
||||||
|
│ │ EOI │ │ 1: #DB (Debug) │ │
|
||||||
|
│ │ (timer) │ │ ... │ │
|
||||||
|
│ │ │ │ 8: #DF (Double Fault) │ │
|
||||||
|
│ │ │ │ 13: #GP (GPF) │ │
|
||||||
|
│ │ │ │ 14: #PF (Page Fault) │ │
|
||||||
|
│ │ │ │ ... │ │
|
||||||
|
│ │ │ │ 0xFD: TLB Shootdown │ │
|
||||||
|
│ └──────────────┘ └─────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Модули
|
||||||
|
|
||||||
|
| Файл | Компонент | Функция |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| `idt.rs` | IDT структуры | Определение IdtEntry, IdtPtr, InterruptDescriptorTable |
|
||||||
|
| `interrupts.rs` | Обработчики | Early handlers, Page Fault, Double Fault, GPF, TLB shootdown |
|
||||||
|
| `lapic.rs` | Local APIC | Инициализация LAPIC, EOI, broadcast IPI |
|
||||||
|
|
||||||
|
## Порядок инициализации
|
||||||
|
|
||||||
|
```
|
||||||
|
kmain()
|
||||||
|
│
|
||||||
|
├── cpu::interrupts::init_early_exceptions()
|
||||||
|
│ └── IDT для векторов 0-31 + TLB shootdown (0xFD)
|
||||||
|
│
|
||||||
|
├── ... (PMM, LAPIC, Page tables) ...
|
||||||
|
│
|
||||||
|
├── cpu::interrupts::init_idt()
|
||||||
|
│ └── Перезагрузка IDT с полными обработчиками
|
||||||
|
│
|
||||||
|
├── STI (разрешение прерываний)
|
||||||
|
│
|
||||||
|
└── ... (работа с прерываниями)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработка исключений: два этапа
|
||||||
|
|
||||||
|
### Этап 1: Early (до VMM)
|
||||||
|
|
||||||
|
На раннем этапе загрузки (до настройки page tables и heap) ядро
|
||||||
|
не может обрабатывать сложные исключения. Для векторов 0-31
|
||||||
|
устанавливаются `early_handler_N`, которые:
|
||||||
|
|
||||||
|
1. Пушат вектор и (возможно) dummy error code.
|
||||||
|
2. Переходят в `early_common`.
|
||||||
|
3. Сохраняют все регистры.
|
||||||
|
4. Вызывают `rust_early_exception_handler(vector, error_code)`.
|
||||||
|
5. Паникуют (HALT).
|
||||||
|
|
||||||
|
### Этап 2: Полный (после VMM)
|
||||||
|
|
||||||
|
После инициализации VMM три критических исключения получают
|
||||||
|
полноценные обработчики:
|
||||||
|
|
||||||
|
- **Page Fault (#PF, вектор 14)**: попытка обработать (COW, lazy).
|
||||||
|
- **Double Fault (#DF, вектор 8)**: HALT с сообщением.
|
||||||
|
- **General Protection Fault (#GPF, вектор 13)**: HALT с сообщением.
|
||||||
|
- **TLB Shootdown (вектор 0xFD)**: межпроцессорный TLB сброс.
|
||||||
|
|
||||||
|
## TLB Shootdown: модель
|
||||||
|
|
||||||
|
При изменении page tables на одном ядре, TLB других ядер устаревает.
|
||||||
|
Протокол:
|
||||||
|
|
||||||
|
```
|
||||||
|
CPU 0 (инициатор) CPU 1 (мишень)
|
||||||
|
│ │
|
||||||
|
├── local_tlb_flush_asid() │
|
||||||
|
├── SHOOTDOWN_LOCK.lock() │
|
||||||
|
├── SHOOTDOWN_ASID = asid │
|
||||||
|
├── SHOOTDOWN_ACK = 0 │
|
||||||
|
├── LAPIC: broadcast IPI │
|
||||||
|
│ (вектор TLB_SHOOTDOWN_VECTOR) ───► прерывание!
|
||||||
|
│ ├── handle_tlb_shootdown_ipi()
|
||||||
|
│ ├── local_tlb_flush_asid()
|
||||||
|
│ ├── SHOOTDOWN_ACK |= 1 << core
|
||||||
|
│ ├── LAPIC::send_eoi()
|
||||||
|
│ └── iretq
|
||||||
|
│ │
|
||||||
|
├── spin_loop() ◄────────────────────┤
|
||||||
|
│ (ждёт ACK от всех ядер) │
|
||||||
|
├── SHOOTDOWN_LOCK.unlock() │
|
||||||
|
└── continue │
|
||||||
|
```
|
||||||
115
kernel/docs/cpu/lapic.md
Normal file
115
kernel/docs/cpu/lapic.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Local APIC: `lapic.rs`
|
||||||
|
|
||||||
|
## Аппаратная модель
|
||||||
|
|
||||||
|
**Local APIC** (Advanced Programmable Interrupt Controller) — это
|
||||||
|
встроенный в каждое ядро x86-64 контроллер прерываний.
|
||||||
|
|
||||||
|
```
|
||||||
|
Local APIC (MMIO, начиная с 0xFEE00_000)
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ 0x020: IRR (In-Service Reg) │
|
||||||
|
│ 0x030: TMR (Trigger Mode) │
|
||||||
|
│ 0x080: EOI │ ← запись 0 = подтверждение прерывания
|
||||||
|
│ 0x0B0: LINT0/LINT1 │
|
||||||
|
│ 0x0E0: Timer │
|
||||||
|
│ 0x200: LVT Error │
|
||||||
|
│ 0x300: ICR (Interrupt Cmd) │ ← отправка межпроцессорного прерывания
|
||||||
|
│ 0x310: ICR_HIGH (APIC ID) │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Регистры
|
||||||
|
|
||||||
|
| Смещение | Регистр | Назначение |
|
||||||
|
|----------|---------|------------|
|
||||||
|
| 0x0B0 | EOI | End Of Interrupt — подтверждение обработки |
|
||||||
|
| 0x300 | ICR (low) | Interrupt Command Register — отправка IPI |
|
||||||
|
| 0x310 | ICR (high) | Destination APIC ID |
|
||||||
|
|
||||||
|
## Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
// LAPIC отображён bootloader'ом в HHDM по адресу 0xFEE00_000
|
||||||
|
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
LAPIC расположен по физическому адресу `0xFEE0_0000`. Ядро получает
|
||||||
|
виртуальный базовый адрес, добавляя HHDM offset.
|
||||||
|
|
||||||
|
## Доступ к регистрам
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_lapic_reg(offset: u64, value: u32) {
|
||||||
|
let base = LAPIC_VIRT_BASE.load(Ordering::Relaxed);
|
||||||
|
if base == 0 { return; } // LAPIC ещё не инициализирован
|
||||||
|
unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `write_volatile` — запрещает компилятору оптимизировать обращение
|
||||||
|
(регистры MMIO).
|
||||||
|
- `Relaxed` ordering — для отладки/инициализации достаточно.
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### send_eoi() — конец прерывания
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn send_eoi() {
|
||||||
|
write_lapic_reg(LAPIC_EOI, 0); // 0x0B0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### broadcast_ipi_exclude_self(vector) — IPI всем, кроме себя
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn broadcast_ipi_exclude_self(vector: u8) {
|
||||||
|
// ICR[19:18] = 10b (All Excluding Self)
|
||||||
|
// ICR[14] = 1 (Assert)
|
||||||
|
// ICR[7:0] = vector
|
||||||
|
let icr_low = (2 << 18) | (1 << 14) | (vector as u32);
|
||||||
|
write_lapic_reg(LAPIC_ICR_LOW, icr_low);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Используется для TLB shootdown — необходимо разослать всем ядрам
|
||||||
|
(кроме текущего) IPI с вектором `TLB_SHOOTDOWN_VECTOR`.
|
||||||
|
|
||||||
|
### current_core_id() — определение текущего ядра
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn current_core_id() -> u32 {
|
||||||
|
// CPUID leaf 1: EBX[31:24] = Local APIC ID
|
||||||
|
let mut ebx: u32;
|
||||||
|
asm!(
|
||||||
|
"mov {tmp:r}, rbx", // спрятать rbx (резерв LLVM)
|
||||||
|
"mov eax, 1", "cpuid",
|
||||||
|
"mov {out:e}, ebx", // сохранить EBX
|
||||||
|
"mov rbx, {tmp:r}", // восстановить rbx
|
||||||
|
tmp = out(reg) _,
|
||||||
|
out = out(reg) ebx,
|
||||||
|
...
|
||||||
|
);
|
||||||
|
ebx >> 24
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**RBX проблема:** LLVM резервирует RBX, поэтому его нужно сохранять
|
||||||
|
и восстанавливать вручную вокруг CPUID инструкции.
|
||||||
|
|
||||||
|
## Тонкости
|
||||||
|
|
||||||
|
1. **MMIO vs MSR**: LAPIC можно программировать через MSR (IA32_APIC_BASE)
|
||||||
|
и через MMIO. Bootloader (Limine) настраивает MMIO mapping в HHDM.
|
||||||
|
2. **Инициализация**: LAPIC уже включён bootloader'ом. `init()` просто
|
||||||
|
сохраняет виртуальный адрес.
|
||||||
|
3. **x2APIC**: не используется (в текущей версии — MMIO xAPIC).
|
||||||
|
4. **EOI**: обязателен после каждого прерывания от LAPIC (включая IPI).
|
||||||
|
5. **ICR запись**: после записи в ICR Low, шина APIC доставляет
|
||||||
|
прерывание. Запись блокирующая (ждёт готовности шины).
|
||||||
88
kernel/docs/debug/introduction.md
Normal file
88
kernel/docs/debug/introduction.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Подсистема отладки: концептуальная модель
|
||||||
|
|
||||||
|
## Два канала вывода
|
||||||
|
|
||||||
|
Ядро имеет два параллельных канала для отладки:
|
||||||
|
|
||||||
|
1. **Экранный (framebuffer console)** — через `tty::Console`.
|
||||||
|
- Использует PSF2-шрифты.
|
||||||
|
- Цветной вывод (зелёный/жёлтый/красный для Info/Warn/Error).
|
||||||
|
- Медленнее, но визуально нагляднее.
|
||||||
|
|
||||||
|
2. **Serial port (COM1)** — через `debug::serial`.
|
||||||
|
- Текстовый вывод с ANSI escape codes.
|
||||||
|
- Работает через QEMU/KVM serial console.
|
||||||
|
- Быстрее, может быть перенаправлен в файл.
|
||||||
|
|
||||||
|
## LogLevel — уровни логирования
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum LogLevel {
|
||||||
|
Info, // Зелёный на экране, зелёный в serial
|
||||||
|
Warn, // Жёлтый
|
||||||
|
Error, // Красный
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый уровень имеет:
|
||||||
|
- `serial_color_code()` — ANSI escape code для serial.
|
||||||
|
- `console_color()` — RGB значение для framebuffer.
|
||||||
|
|
||||||
|
## Макросы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Основной макрос
|
||||||
|
log!(console, level, module, format_args...)
|
||||||
|
|
||||||
|
// Специализированные
|
||||||
|
info!(console, module, format_args...)
|
||||||
|
warn!(console, module, format_args...)
|
||||||
|
error!(console, module, format_args...)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат вывода на экран:**
|
||||||
|
```
|
||||||
|
[ LOG ] <module> | <message>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат в serial:**
|
||||||
|
```
|
||||||
|
GREEN[ LOG] RESET <module> | <message>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Цветовое кодирование
|
||||||
|
|
||||||
|
| Уровень | Экран (RGB) | Serial (ANSI) |
|
||||||
|
|---------|-------------|---------------|
|
||||||
|
| Info | 0x00FF00 | `\x1b[32m` (green) |
|
||||||
|
| Warn | 0xFFFF00 | `\x1b[33m` (yellow) |
|
||||||
|
| Error | 0xFF0000 | `\x1b[31m` (red) |
|
||||||
|
| Текст | 0xFFFFFF (white) | `\x1b[0m` (reset) |
|
||||||
|
|
||||||
|
## Использование в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// После инициализации serial
|
||||||
|
debug::serial::init_global();
|
||||||
|
|
||||||
|
// После инициализации console
|
||||||
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
info!(console, "MEM", "BitmapPMM initialized.");
|
||||||
|
info!(console, "LAPIC", "Local APIC initialized.");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌───────────────────┐
|
||||||
|
│ kmain() │────►│ log!() macro │
|
||||||
|
└──────────────┘ └────────┬──────────┘
|
||||||
|
│
|
||||||
|
┌──────────────┼──────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||||
|
│ Экран │ │ Serial │ │ Паника │
|
||||||
|
│ Console │ │ COM1 │ │ Handler │
|
||||||
|
│ (tty.rs) │ │(serial.rs)│ │(main.rs) │
|
||||||
|
└──────────┘ └──────────┘ └──────────┘
|
||||||
|
```
|
||||||
110
kernel/docs/debug/serial.md
Normal file
110
kernel/docs/debug/serial.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Serial Port драйвер: `serial.rs`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Драйвер последовательного порта (UART 16550, COM1) для отладочного
|
||||||
|
вывода. Позволяет видеть сообщения ядра через QEMU serial console,
|
||||||
|
minicom, screen и т.д.
|
||||||
|
|
||||||
|
## Аппаратная модель
|
||||||
|
|
||||||
|
COM1 расположен по портам ввода-вывода `0x3F8`-`0x3FF`:
|
||||||
|
|
||||||
|
| Порт | Регистр | Назначение |
|
||||||
|
|------|---------|------------|
|
||||||
|
| 0x3F8 | DATA | Чтение/запись данных |
|
||||||
|
| 0x3F9 | IER | Interrupt Enable |
|
||||||
|
| 0x3FA | IIR/FCR | Interrupt ID / FIFO Control |
|
||||||
|
| 0x3FB | LCR | Line Control |
|
||||||
|
| 0x3FC | MCR | Modem Control |
|
||||||
|
| 0x3FD | LSR | Line Status |
|
||||||
|
| 0x3FE | MSR | Modem Status |
|
||||||
|
|
||||||
|
## Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn init() -> Self {
|
||||||
|
let port = Self::COM1; // 0x3F8
|
||||||
|
outb(port + 1, 0x00); // IER = 0 (disable interrupts)
|
||||||
|
outb(port + 3, 0x80); // LCR DLAB=1 (enable baud rate programming)
|
||||||
|
outb(port + 0, 0x03); // Divisor LSB = 3 (38400 baud)
|
||||||
|
outb(port + 1, 0x00); // Divisor MSB = 0
|
||||||
|
outb(port + 3, 0x03); // LCR = 8N1 (8 bits, No parity, 1 stop)
|
||||||
|
outb(port + 2, 0xC7); // FCR = enable FIFO, clear, 14-byte threshold
|
||||||
|
outb(port + 4, 0x0B); // MCR = DTR+RTS+OUT2 (enable IRQ + handshake)
|
||||||
|
SerialPort(port)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Детали конфигурации
|
||||||
|
|
||||||
|
1. **IER = 0**: отключаем прерывания UART (TODO: включить для RX).
|
||||||
|
2. **DLAB = 1**: разрешаем программирование делителя бода.
|
||||||
|
3. **Divisor = 3**: при тактовой 1.8432 MHz → 115200 / 3 = 38400 бод.
|
||||||
|
4. **LCR = 0x03**: 8N1 — 8 бит данных, нет чётности, 1 стоп-бит.
|
||||||
|
5. **FCR = 0xC7**: enable FIFO, clear both FIFOs, trigger at 14 bytes.
|
||||||
|
6. **MCR = 0x0B**: DTR=1, RTS=1, OUT2=1 (необходимо для IRQ на ISA шине).
|
||||||
|
|
||||||
|
## Отправка байта
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn is_transmit_empty(&self) -> bool {
|
||||||
|
unsafe { (inb(self.0 + 5) & 0x20) != 0 } // LSR bit 5 = Transmitter Holding Register Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, data: u8) {
|
||||||
|
while !self.is_transmit_empty() {} // Ждём, пока UART готов
|
||||||
|
unsafe { outb(self.0, data); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## fmt::Write реализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl core::fmt::Write for SerialPort {
|
||||||
|
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||||
|
for byte in s.bytes() { self.send(byte); }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static SERIAL_PORT: Locked<Option<SerialPort>> = Locked::new(None);
|
||||||
|
|
||||||
|
pub fn init_global() {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
*guard = Some(unsafe { SerialPort::init() });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_global(args: core::fmt::Arguments) {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
if let Some(ref mut sp) = *guard {
|
||||||
|
let _ = sp.write_fmt(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`init_global()` вызывается из `kmain()` после инициализации TTY.
|
||||||
|
`write_global()` используется:
|
||||||
|
- В `log!()` макросе (через debug.rs).
|
||||||
|
- В `rust_panic()` обработчике.
|
||||||
|
- В обработчиках исключений (GPF, Double Fault, Early Exception).
|
||||||
|
|
||||||
|
## Низкоуровневый I/O
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe fn outb(port: u16, val: u8) {
|
||||||
|
asm!("out dx, al", in("dx") port, in("al") val,
|
||||||
|
options(nomem, nostack, preserves_flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn inb(port: u16) -> u8 {
|
||||||
|
let res: u8;
|
||||||
|
asm!("in al, dx", out("al") res, in("dx") port,
|
||||||
|
options(nomem, nostack, preserves_flags));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
```
|
||||||
128
kernel/docs/events/introduction.md
Normal file
128
kernel/docs/events/introduction.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Очередь отзыва RevocationQueue: `events.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**RevocationQueue** — это глобальная lock-free кольцевая очередь,
|
||||||
|
которая соединяет capability-систему (CNode) с виртуальной памятью (VMM).
|
||||||
|
|
||||||
|
### Назначение
|
||||||
|
|
||||||
|
Когда capability отзывается, VMM должна аннулировать все VMA,
|
||||||
|
связанные с этим capability. Но:
|
||||||
|
- CNode.revoke() не может напрямую обратиться к VMM (разные подсистемы,
|
||||||
|
разное время жизни блокировок).
|
||||||
|
- VMM может быть в обработке page fault'а.
|
||||||
|
- Отзыв может быть каскадным и рекурсивным.
|
||||||
|
|
||||||
|
Решение: **асинхронная очередь**.
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode.revoke(token_1)
|
||||||
|
│
|
||||||
|
└──→ push(token_1) ───→ MMU_REVOCATION_QUEUE ──→ pop() ──→ VMM
|
||||||
|
│
|
||||||
|
CNode.revoke(token_2) │
|
||||||
|
│ │
|
||||||
|
└──→ push(token_2) ─────────────────────────────────────────┤
|
||||||
|
│
|
||||||
|
Page Fault handler │
|
||||||
|
│ │
|
||||||
|
└──→ process_pending_revocations() ←────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## RevocationQueue — детали
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct RevocationQueue {
|
||||||
|
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
|
||||||
|
_pad0: [u8; 64], // padding: кэш-линия
|
||||||
|
head: AtomicUsize, // читатель (VMM)
|
||||||
|
_pad1: [u8; 64], // padding: кэш-линия
|
||||||
|
tail: AtomicUsize, // писатели (CNode revoke)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Тот же паттерн, что и в `PMActorQueue`: разделение head и tail
|
||||||
|
по разным кэш-линиям предотвращает false sharing.
|
||||||
|
|
||||||
|
### push(token_sig)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn push(&self, token_sig: u64) -> Result<(), &'static str> {
|
||||||
|
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
let head = self.head.load(Ordering::Acquire);
|
||||||
|
if tail.wrapping_sub(head) >= QUEUE_SIZE {
|
||||||
|
return Err("Revocation queue overflow");
|
||||||
|
}
|
||||||
|
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.buffer[tail & QUEUE_MASK].store(token_sig, Release);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(actual) => tail = actual,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### pop()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn pop(&self) -> Option<u64> {
|
||||||
|
let head = self.head.load(Ordering::Relaxed);
|
||||||
|
let tail = self.tail.load(Ordering::Acquire);
|
||||||
|
if head == tail { return None; }
|
||||||
|
let token = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
|
||||||
|
self.head.store(head.wrapping_add(1), Ordering::Release);
|
||||||
|
Some(token)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static MMU_REVOCATION_QUEUE: RevocationQueue = RevocationQueue::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
## VMM: обработка отзывов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// В interrupts.rs: rust_page_fault_handler()
|
||||||
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
|
space.process_pending_revocations(); // ← дренирует очередь
|
||||||
|
match space.handle_fault(fault_addr, write) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// В vmm.rs: AddressSpace
|
||||||
|
pub fn process_pending_revocations(&mut self) {
|
||||||
|
let mut needs_flush = false;
|
||||||
|
while let Some(token) = MMU_REVOCATION_QUEUE.pop() {
|
||||||
|
self.do_revoke_by_token(token);
|
||||||
|
needs_flush = true;
|
||||||
|
}
|
||||||
|
if needs_flush {
|
||||||
|
tlb_flush_asid(self.asid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему именно так?
|
||||||
|
|
||||||
|
1. **Lock-free**: отзыв capability не блокируется VMM.
|
||||||
|
2. **Batch processing**: VMM обрабатывает сразу все накопленные отзывы
|
||||||
|
при следующем page fault, экономя TLB flush'ы.
|
||||||
|
3. **Deadlock prevention**: process_pending_revocations вызывается
|
||||||
|
**до** handle_fault, избегая циклических зависимостей между
|
||||||
|
блокировками capability и VMM.
|
||||||
|
4. **Простота**: кольцевой буфер с CAS — проверенная временем структура.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Размер: 1024 entry. Если очередь переполняется — **паника**.
|
||||||
|
Это проектный выбор: переполнение означает, что VMM не успевает
|
||||||
|
обрабатывать отзывы быстрее, чем CNode генерирует новые.
|
||||||
|
- Однопоточный pop (только page fault handler дренирует очередь).
|
||||||
1055
kernel/docs/glossary/terms.md
Normal file
1055
kernel/docs/glossary/terms.md
Normal file
File diff suppressed because it is too large
Load Diff
70
kernel/docs/index.md
Normal file
70
kernel/docs/index.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Документация ядра Elyz (LISA)
|
||||||
|
|
||||||
|
## Структура документации
|
||||||
|
|
||||||
|
```
|
||||||
|
kernel/docs/
|
||||||
|
├── index.md ← этот файл: оглавление
|
||||||
|
│
|
||||||
|
├── overview/
|
||||||
|
│ ├── introduction.md ← Концептуальная модель (7 принципов)
|
||||||
|
│ └── architecture.md ← Архитектура, схема, порядок инициализации
|
||||||
|
│
|
||||||
|
├── memory/
|
||||||
|
│ ├── introduction.md ← Трёхуровневая модель памяти
|
||||||
|
│ ├── address-translation.md ← PhysAddr, VirtAddr, HHDM
|
||||||
|
│ ├── physical-memory.md ← BitmapPMM (глобальный)
|
||||||
|
│ ├── paging.md ← PageTable, 4-level walk
|
||||||
|
│ ├── buddy.md ← BuddyAllocator (intrusive list)
|
||||||
|
│ ├── pm-actor.md ← PMActor + MPSC очередь
|
||||||
|
│ ├── pm-router.md ← PMRouter (65536 каналов)
|
||||||
|
│ ├── allocator.md ← Slab-аллокатор (куча)
|
||||||
|
│ └── vmm.md ← AddressSpace, VMA, COW, TLB
|
||||||
|
│
|
||||||
|
├── cpu/
|
||||||
|
│ ├── introduction.md ← CPU подсистема, 2 этапа обработки
|
||||||
|
│ ├── idt.md ← IDT entry, InterruptDescriptorTable
|
||||||
|
│ ├── interrupts.md ← Обработчики, stubs, early exceptions
|
||||||
|
│ └── lapic.md ← Local APIC, IPI, EOI
|
||||||
|
│
|
||||||
|
├── capability/
|
||||||
|
│ ├── introduction.md ← Capability model, mint, revoke
|
||||||
|
│ ├── descriptors.md ← Capability, CapObject, CapRights
|
||||||
|
│ ├── cnode.md ← CNode, lock ranking
|
||||||
|
│ └── objects.md ← KernelObject (ref-counted)
|
||||||
|
│
|
||||||
|
├── debug/
|
||||||
|
│ ├── introduction.md ← Отладка: экран + serial
|
||||||
|
│ └── serial.md ← COM1 UART 16550 драйвер
|
||||||
|
│
|
||||||
|
├── boot/
|
||||||
|
│ ├── introduction.md ← Процесс загрузки, Limine
|
||||||
|
│ └── linker.md ← Линкер-скрипты
|
||||||
|
│
|
||||||
|
└── events/
|
||||||
|
└── introduction.md ← RevocationQueue (lock-free)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Быстрый старт по файлам
|
||||||
|
|
||||||
|
| Файл ядра | Что делает | Документация |
|
||||||
|
|-----------|-----------|--------------|
|
||||||
|
| `src/main.rs` | Точка входа, инициализация, тесты | `overview/architecture.md` (раздел порядка инициализации) |
|
||||||
|
| `src/mem/pmm.rs` | Глобальный PMM (Bitmap + L1) | `memory/physical-memory.md` |
|
||||||
|
| `src/mem/buddy.rs` | Buddy-аллокатор | `memory/buddy.md` |
|
||||||
|
| `src/mem/paging.rs` | 4-уровневые page tables | `memory/paging.md` |
|
||||||
|
| `src/mem/pm_manages.rs` | PMActor + очередь | `memory/pm-actor.md` |
|
||||||
|
| `src/mem/pm_router.rs` | PM Router каналы | `memory/pm-router.md` |
|
||||||
|
| `src/mem/vmm.rs` | AddressSpace, COW, TLB | `memory/vmm.md` |
|
||||||
|
| `src/mem/allocator.rs` | Slab-аллокатор | `memory/allocator.md` |
|
||||||
|
| `src/mem/address.rs` | Физические/виртуальные адреса | `memory/address-translation.md` |
|
||||||
|
| `src/cpu/idt.rs` | IDT структуры | `cpu/idt.md` |
|
||||||
|
| `src/cpu/interrupts.rs` | Обработчики исключений | `cpu/interrupts.md` |
|
||||||
|
| `src/cpu/lapic.rs` | Local APIC | `cpu/lapic.md` |
|
||||||
|
| `src/cap/mod.rs` | CNode | `capability/cnode.md` |
|
||||||
|
| `src/cap/descriptor.rs` | Capability типы | `capability/descriptors.md` |
|
||||||
|
| `src/cap/object.rs` | KernelObject | `capability/objects.md` |
|
||||||
|
| `src/debug/mod.rs` | Log macros | `debug/introduction.md` |
|
||||||
|
| `src/debug/serial.rs` | Serial driver | `debug/serial.md` |
|
||||||
|
| `src/events.rs` | Очередь отзыва | `events/introduction.md` |
|
||||||
|
| `src/tty.rs` | Framebuffer console | `tty.md` |
|
||||||
103
kernel/docs/memory/address-translation.md
Normal file
103
kernel/docs/memory/address-translation.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Адресация и трансляция: `address.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
В x86-64 MMU транслирует виртуальные адреса (48 бит, канонические)
|
||||||
|
в физические (до 52 бит) через 4-уровневую иерархию page tables.
|
||||||
|
|
||||||
|
Ядро использует **Higher-Half Direct Map (HHDM)** — область виртуальной
|
||||||
|
памяти, где вся физическая память отображена 1:1 с фиксированным смещением.
|
||||||
|
|
||||||
|
```
|
||||||
|
Физический адрес 0x0
|
||||||
|
│
|
||||||
|
│ + HHDM_OFFSET (например, 0xFFFF8000_0000_0000)
|
||||||
|
▼
|
||||||
|
Виртуальный адрес 0xFFFF8000_0000_0000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Физический адрес: `PhysAddr(pub u64)`
|
||||||
|
|
||||||
|
Представляет собой физический адрес. Это **newtype** над `u64` — строгая
|
||||||
|
типизация предотвращает случайное смешивание физических и виртуальных адресов.
|
||||||
|
|
||||||
|
**Методы:**
|
||||||
|
- `to_virt()` — преобразует в виртуальный адрес через HHDM:
|
||||||
|
`VirtAddr(self.0 + HHDM_OFFSET)`
|
||||||
|
- `is_aligned()` — проверка выравнивания на 4KiB
|
||||||
|
- `align_down()` / `align_up()` — выравнивание вниз/вверх до границы 4KiB
|
||||||
|
|
||||||
|
## Виртуальный адрес: `VirtAddr(pub u64)`
|
||||||
|
|
||||||
|
**Методы:**
|
||||||
|
- `to_phys()` — обратное преобразование: вычитает HHDM, возвращает `None`
|
||||||
|
если адрес ниже HHDM (не канонический для физической памяти)
|
||||||
|
- `as_ptr<T>()` — получить `*const T` (для чтения)
|
||||||
|
- `as_mut_ptr<T>()` — получить `*mut T` (для записи)
|
||||||
|
|
||||||
|
## HHDM Offset: глобальное состояние
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `init_hhdm(offset: u64)` — вызывается в `kmain()` сразу после получения
|
||||||
|
ответа от bootloader'а. Сохраняет offset через `Ordering::Release`.
|
||||||
|
- `get_hhdm() -> u64` — читает offset через `Ordering::Relaxed` (высокая
|
||||||
|
производительность, на этой архитектуре атомарность гарантирована).
|
||||||
|
|
||||||
|
### Почему Atomic?
|
||||||
|
|
||||||
|
HHDM инициализируется однократно до включения прерываний и создания
|
||||||
|
дополнительных потоков. Atomic гарантирует, что даже если в будущем
|
||||||
|
инструкции будут переупорядочены, значение будет видно всем ядрам.
|
||||||
|
|
||||||
|
## Код и абстракция
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// address.rs (полный код)
|
||||||
|
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn init_hhdm(offset: u64) {
|
||||||
|
HHDM_OFFSET.store(offset, Ordering::Release);
|
||||||
|
// ^ Release: все предыдущие записи становятся видимы для
|
||||||
|
// всех acquire-операций на HHDM_OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hhdm() -> u64 {
|
||||||
|
HHDM_OFFSET.load(Ordering::Relaxed)
|
||||||
|
// ^ Relaxed: в однопоточном контексте и при инициализации
|
||||||
|
// до включения прерываний это безопасно и максимально быстро
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct PhysAddr(pub u64);
|
||||||
|
// ^ repr(transparent): гарантирует, что PhysAddr имеет то же
|
||||||
|
// представление в памяти, что и u64 — важно для FFI и передачи
|
||||||
|
// в ассемблерные инструкции (mov cr3, rax)
|
||||||
|
|
||||||
|
impl PhysAddr {
|
||||||
|
pub fn to_virt(self) -> VirtAddr {
|
||||||
|
VirtAddr(self.0 + get_hhdm())
|
||||||
|
// ^ Концептуально: физический адрес + HHDM смещение даёт
|
||||||
|
// виртуальный адрес, по которому этот физический фрейм
|
||||||
|
// отображён в higher half
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtAddr {
|
||||||
|
pub fn as_mut_ptr<T>(self) -> *mut T {
|
||||||
|
self.0 as *mut T
|
||||||
|
// ^ Прямая интерпретация числа как указателя.
|
||||||
|
// Безопасность на стороне вызывающего кода.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Где используется
|
||||||
|
|
||||||
|
- `PhysAddr::to_virt()` — в `paging.rs` для доступа к Page Table entries,
|
||||||
|
в `pmm.rs` для обнуления фреймов, в `buddy.rs` для intrusive list.
|
||||||
|
- `VirtAddr::as_mut_ptr()` — в `paging.rs` для создания ссылок на PTE.
|
||||||
|
- `init_hhdm()` — в `kmain()` при старте.
|
||||||
|
- `get_hhdm()` — в `lapic.rs` для вычисления LAPIC_base.
|
||||||
211
kernel/docs/memory/allocator.md
Normal file
211
kernel/docs/memory/allocator.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Slab-аллокатор: куча ядра
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**SlabAllocator** — это глобальный аллокатор кучи для ядра.
|
||||||
|
Rust-программы используют `alloc::vec::Vec`, `alloc::boxed::Box` и т.д. —
|
||||||
|
все они в конечном счёте вызывают `GlobalAlloc::alloc()`.
|
||||||
|
|
||||||
|
### Стратегия
|
||||||
|
|
||||||
|
Для маленьких блоков (≤ 2048 байт) — **slab lists**:
|
||||||
|
предварительно нарезанные блоки фиксированного размера.
|
||||||
|
|
||||||
|
Для больших блоков (> 2048 байт) — **freelist больших блоков**:
|
||||||
|
освобождённые блоки переиспользуются.
|
||||||
|
|
||||||
|
Если ни там, ни там нет — **bump allocation**:
|
||||||
|
последовательная раздача из заранее выделенного региона.
|
||||||
|
|
||||||
|
```
|
||||||
|
Запрос alloc(32 байта):
|
||||||
|
1. list_index(32) = 2 (BLOCK_SIZES[2] = 32)
|
||||||
|
2. list_heads[2] есть свободный блок?
|
||||||
|
- Да: отдаём его, заменяем голову списка
|
||||||
|
- Нет: bump-аллокация блока размером 32 (fallback_alloc)
|
||||||
|
|
||||||
|
Запрос alloc(4096 байт):
|
||||||
|
1. list_index(4096) = None (максимум 2048)
|
||||||
|
2. large_block_free есть блок ≥ 4096?
|
||||||
|
- Да: отдаём
|
||||||
|
- Нет: bump-аллокация
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структуры данных
|
||||||
|
|
||||||
|
### Slab списки — `list_heads`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||||
|
|
||||||
|
struct ListNode {
|
||||||
|
next: Option<&'static mut ListNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SlabAllocator {
|
||||||
|
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()], // 9 списков
|
||||||
|
large_block_free: Option<&'static mut LargeBlockNode>,
|
||||||
|
heap_start: usize,
|
||||||
|
heap_end: usize,
|
||||||
|
next_bump: usize,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### LargeBlockNode — для блоков > 2048 байт
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct LargeBlockNode {
|
||||||
|
size: usize,
|
||||||
|
next: Option<&'static mut LargeBlockNode>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Инициализация: `init(start, size)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init(&mut self, start: usize, size: usize) {
|
||||||
|
self.heap_start = start;
|
||||||
|
self.next_bump = start;
|
||||||
|
self.heap_end = start + size;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Вызывается в kmain() после настройки page table для области `0xFFFF_9000_0000_0000`.
|
||||||
|
|
||||||
|
## GlobalAlloc — реализация
|
||||||
|
|
||||||
|
### alloc(layout)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let mut allocator = self.lock();
|
||||||
|
match SlabAllocator::list_index(&layout) {
|
||||||
|
Some(index) => {
|
||||||
|
// 1. Пробуем slab list
|
||||||
|
match allocator.list_heads[index].take() {
|
||||||
|
Some(node) => {
|
||||||
|
allocator.list_heads[index] = node.next.take();
|
||||||
|
node as *mut ListNode as *mut u8
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// 2. Нет в slab — bump alloc целого блока
|
||||||
|
let block_size = BLOCK_SIZES[index];
|
||||||
|
allocator.fallback_alloc(
|
||||||
|
Layout::from_size_align(block_size, block_size).unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => allocator.fallback_alloc(layout) // > 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### fallback_alloc(layout)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
||||||
|
let size = layout.size().max(layout.align());
|
||||||
|
|
||||||
|
// 1. Пробуем large block free list (для > 2048)
|
||||||
|
if size > 2048 {
|
||||||
|
// поиск по large_block_free
|
||||||
|
while let Some(ref mut node) = *field {
|
||||||
|
if node.size >= size {
|
||||||
|
// отдаём, удаляем из списка
|
||||||
|
return node as *mut u8;
|
||||||
|
}
|
||||||
|
field = &mut node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Bump alloc
|
||||||
|
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
||||||
|
let alloc_end = alloc_start.checked_add(layout.size())?;
|
||||||
|
if alloc_end > self.heap_end {
|
||||||
|
null_mut() // OOM
|
||||||
|
} else {
|
||||||
|
self.next_bump = alloc_end;
|
||||||
|
alloc_start as *mut u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### dealloc(ptr, layout)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||||
|
let mut allocator = self.lock();
|
||||||
|
match SlabAllocator::list_index(&layout) {
|
||||||
|
Some(index) => {
|
||||||
|
// Добавляем в slab list (переиспользование)
|
||||||
|
let new_node = ListNode { next: allocator.list_heads[index].take() };
|
||||||
|
let new_node_ptr = ptr as *mut ListNode;
|
||||||
|
unsafe { new_node_ptr.write(new_node); }
|
||||||
|
allocator.list_heads[index] = Some(&mut *new_node_ptr);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Добавляем в large block free list
|
||||||
|
let new_node = LargeBlockNode {
|
||||||
|
size: layout.size().max(layout.align()),
|
||||||
|
next: allocator.large_block_free.take(),
|
||||||
|
};
|
||||||
|
let new_node_ptr = ptr as *mut LargeBlockNode;
|
||||||
|
unsafe { new_node_ptr.write(new_node); }
|
||||||
|
allocator.large_block_free = Some(&mut *new_node_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Важно:** `layout.size()` должен быть ≥ `size_of::<ListNode>()`, чтобы
|
||||||
|
освобождённый блок мог хранить указатели списка. Гарантируется, потому
|
||||||
|
что наименьший BLOCK_SIZE (8) ≥ `size_of::<Option<&'static mut ListNode>>`
|
||||||
|
(8 байт на 64-bit).
|
||||||
|
|
||||||
|
## Locked<T> — примитивная spinlock-обёртка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Locked<A> {
|
||||||
|
inner: UnsafeCell<A>,
|
||||||
|
lock: AtomicBool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `lock()` — spin-wait с CAS + `hint::spin_loop()`.
|
||||||
|
- `LockedGuard` — RAII guard, при Drop отпускает блокировку.
|
||||||
|
- `unsafe impl Sync` — потому что `lock()` гарантирует взаимное исключение.
|
||||||
|
|
||||||
|
Используется не только для аллокатора, но и для PMM, serial port,
|
||||||
|
VMM KERNEL_SPACE и других глобальных структур.
|
||||||
|
|
||||||
|
## Глобальный аллокатор
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[global_allocator]
|
||||||
|
pub static ALLOCATOR: Locked<SlabAllocator> = Locked::new(SlabAllocator::new());
|
||||||
|
```
|
||||||
|
|
||||||
|
Этот `static` перехватывает все вызовы `alloc::alloc::alloc()`,
|
||||||
|
делая возможным использование `Vec`, `Box`, `String` и т.д. в ядре.
|
||||||
|
|
||||||
|
## Heap init в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let heap_start = 0xFFFF_9000_0000_0000;
|
||||||
|
let heap_size = 8 * 1024 * 1024; // 8 MiB
|
||||||
|
// Предварительно map'им все страницы кучи
|
||||||
|
for i in (0..heap_size).step_by(4096) {
|
||||||
|
let frame = mem::pmm::alloc_frame().expect("OOM");
|
||||||
|
p4.map_page(VirtAddr(heap_start + i), frame, flags);
|
||||||
|
}
|
||||||
|
// Инициализируем аллокатор
|
||||||
|
allocator::ALLOCATOR.lock().init(heap_start as usize, heap_size);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему slab?
|
||||||
|
|
||||||
|
1. **Скорость**: alloc/dealloc — O(1) для малых блоков.
|
||||||
|
2. **Нет фрагментации**: блоки фиксированного размера.
|
||||||
|
3. **Локальность**: блоки одного размера рядом в памяти.
|
||||||
|
4. **Простота**: ~180 строк кода.
|
||||||
267
kernel/docs/memory/buddy.md
Normal file
267
kernel/docs/memory/buddy.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
# Buddy Allocator: `buddy.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
Buddy-аллокатор — это алгоритм управления памятью, который:
|
||||||
|
- Делит память на блоки размером `2^order` страниц.
|
||||||
|
- Каждый блок может быть либо свободен, либо занят.
|
||||||
|
- При освобождении блок объединяется (coalesce) с соседом (buddy),
|
||||||
|
если тот тоже свободен, образуя блок вдвое большего размера.
|
||||||
|
|
||||||
|
```
|
||||||
|
Пример: порядок 0 (1 страница), порядок 1 (2 страницы), порядок 2 (4 страницы)
|
||||||
|
|
||||||
|
Order 2: [ 0-3 ] [ 4-7 ] [ 8-11 ]
|
||||||
|
Order 1: [ 0-1 ][ 2-3 ] [ 4-5 ][ 6-7 ] [ 8-9 ][ 10-11 ]
|
||||||
|
Order 0: [0][1][2][3] [4][5][6][7] [8][9][10][11] [12][13][14][15]
|
||||||
|
^
|
||||||
|
buddy-пара: (0,1), (2,3), (4,5)...
|
||||||
|
buddy(i) = i XOR (1 << order)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Intrusive List (список в самих страницах)
|
||||||
|
|
||||||
|
Вместо отдельной структуры данных для списков свободных блоков,
|
||||||
|
Elyz использует **intrusive linked list** — указатели хранятся прямо
|
||||||
|
внутри свободных физических страниц:
|
||||||
|
|
||||||
|
```
|
||||||
|
Страница (4 KiB):
|
||||||
|
┌──────────────┐
|
||||||
|
│ next: usize │ ← указатель на следующую свободную страницу
|
||||||
|
├──────────────┤
|
||||||
|
│ prev: usize │ ← указатель на предыдущую свободную страницу
|
||||||
|
├──────────────┤
|
||||||
|
│ │
|
||||||
|
│ (не занято) │
|
||||||
|
│ │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Для доступа к странице по индексу используется HHDM:
|
||||||
|
```rust
|
||||||
|
fn page_virt(&self, idx: usize) -> VirtAddr {
|
||||||
|
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура BuddyAllocator
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct BuddyAllocator {
|
||||||
|
free_heads: [usize; MAX_ORDER + 1], // головы списков для каждого порядка
|
||||||
|
total_pages: usize, // всего страниц в управлении
|
||||||
|
free_pages: usize, // свободно страниц
|
||||||
|
base_phys: u64, // физический адрес начала
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MAX_ORDER = 11` — максимальный порядок (2^11 = 2048 страниц = 8 MiB).
|
||||||
|
- `NEXT_SENTINEL = usize::MAX` — маркер конца списка.
|
||||||
|
- `free_heads[order]` — индекс первой свободной страницы порядка `order`.
|
||||||
|
|
||||||
|
## Операции со списком
|
||||||
|
|
||||||
|
### flist_push(order, idx) — добавить в голову
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn flist_push(&mut self, order: usize, idx: usize) {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
// Устанавливаем: node.next = head, node.prev = SENTINEL
|
||||||
|
self.write_node(idx, head, NEXT_SENTINEL);
|
||||||
|
if head != NEXT_SENTINEL {
|
||||||
|
// head.prev = idx
|
||||||
|
self.write_node(head, self.read_next(head), idx);
|
||||||
|
}
|
||||||
|
self.free_heads[order] = idx;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### flist_remove(order, idx) — удалить из списка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn flist_remove(&mut self, order: usize, idx: usize) {
|
||||||
|
let next = self.read_next(idx);
|
||||||
|
let prev = self.read_prev(idx);
|
||||||
|
// Очищаем указатели удаляемого узла
|
||||||
|
self.write_node(idx, NEXT_SENTINEL, NEXT_SENTINEL);
|
||||||
|
if prev != NEXT_SENTINEL {
|
||||||
|
self.write_node(prev, next, self.read_prev(prev));
|
||||||
|
} else {
|
||||||
|
self.free_heads[order] = next; // удалили голову
|
||||||
|
}
|
||||||
|
if next != NEXT_SENTINEL {
|
||||||
|
self.write_node(next, self.read_next(next), prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### flist_pop(order) — извлечь из головы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn flist_pop(&mut self, order: usize) -> Option<usize> {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
if head == NEXT_SENTINEL { return None; }
|
||||||
|
self.flist_remove(order, head);
|
||||||
|
Some(head)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инициализация: `new(total_pages, base_phys)`
|
||||||
|
|
||||||
|
Стратегия: разбить весь диапазон на максимально возможные блоки.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn new(total_pages: usize, base_phys: u64) -> Self {
|
||||||
|
let mut this = Self {
|
||||||
|
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
|
||||||
|
total_pages, free_pages: 0, base_phys,
|
||||||
|
};
|
||||||
|
if total_pages == 0 { return this; }
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
while idx < total_pages {
|
||||||
|
let remaining = total_pages - idx;
|
||||||
|
// Максимальный порядок с учётом выравнивания и остатка
|
||||||
|
let align_order = /* макс порядок по выравниванию idx */;
|
||||||
|
let size_order = /* макс порядок по remaining */;
|
||||||
|
let order = MAX_ORDER.min(align_order).min(size_order);
|
||||||
|
let block_size = 1usize << order;
|
||||||
|
this.flist_push(order, idx);
|
||||||
|
this.free_pages += block_size;
|
||||||
|
idx += block_size;
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Как определяются align_order и size_order
|
||||||
|
|
||||||
|
- `align_order`: если `idx == 0` — `MAX_ORDER`; иначе `(idx.trailing_zeros()).min(MAX_ORDER)`.
|
||||||
|
Чем больше нулевых битов в idx, тем выше порядок выравнивания.
|
||||||
|
- `size_order`: `(64 - 1) - remaining.leading_zeros()` — максимальная степень двойки
|
||||||
|
≤ remaining.
|
||||||
|
|
||||||
|
## Аллокация: `alloc(order)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
||||||
|
// 1. Ищем первый непустой список начиная с order
|
||||||
|
let found_order = (order..=MAX_ORDER)
|
||||||
|
.find(|&o| self.free_heads[o] != NEXT_SENTINEL)?;
|
||||||
|
|
||||||
|
// 2. Извлекаем блок из found_order
|
||||||
|
let block_idx = self.flist_pop(found_order);
|
||||||
|
self.free_pages -= 1 << found_order;
|
||||||
|
|
||||||
|
// 3. Разбиваем до нужного порядка (split)
|
||||||
|
let mut cur_order = found_order;
|
||||||
|
while cur_order > order {
|
||||||
|
cur_order -= 1;
|
||||||
|
let buddy_idx = block_idx + (1 << cur_order);
|
||||||
|
self.flist_push(cur_order, buddy_idx);
|
||||||
|
self.free_pages += 1 << cur_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(block_idx)
|
||||||
|
// Возвращается индекс первого блока
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример split
|
||||||
|
|
||||||
|
```
|
||||||
|
Запрос: order 1 (2 страницы)
|
||||||
|
Найден: order 3 (8 страниц, блок [0-7])
|
||||||
|
|
||||||
|
Шаг 1: cur_order = 3 → 2
|
||||||
|
buddy = 0 + 4 = 4
|
||||||
|
push(order=2, idx=4) — блок [4-7] в order 2
|
||||||
|
Шаг 2: cur_order = 2 → 1
|
||||||
|
buddy = 0 + 2 = 2
|
||||||
|
push(order=1, idx=2) — блок [2-3] в order 1
|
||||||
|
Результат: order=1, idx=0 — блок [0-1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Освобождение: `free(block_idx, order)` + coalesce
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
||||||
|
// Пытаемся объединить с buddy
|
||||||
|
while order < MAX_ORDER {
|
||||||
|
let buddy_idx = block_idx ^ (1 << order);
|
||||||
|
|
||||||
|
// Проверка: buddy в пределах памяти?
|
||||||
|
let buddy_end = buddy_idx.checked_add(1 << order)?;
|
||||||
|
if buddy_end > self.total_pages { break; }
|
||||||
|
|
||||||
|
// Buddy свободен?
|
||||||
|
if self.flist_contains(order, buddy_idx) {
|
||||||
|
// Удаляем buddy из его списка
|
||||||
|
self.flist_remove(order, buddy_idx);
|
||||||
|
self.free_pages -= 1 << order;
|
||||||
|
// Объединяем: block_idx = min(block_idx, buddy_idx)
|
||||||
|
block_idx = block_idx.min(buddy_idx);
|
||||||
|
order += 1;
|
||||||
|
} else {
|
||||||
|
break; // buddy занят, не можем объединить
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем объединённый блок в список
|
||||||
|
self.free_pages += 1 << order;
|
||||||
|
self.flist_push(order, block_idx);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример coalesce
|
||||||
|
|
||||||
|
```
|
||||||
|
Освобождаем: order 0, idx = 0
|
||||||
|
Блок [0] возвращается в список order 0.
|
||||||
|
|
||||||
|
Проверка: buddy(0, order 0) = 0 XOR 1 = 1
|
||||||
|
Если idx=1 тоже свободен в order 0:
|
||||||
|
- Удаляем idx=1 из order 0
|
||||||
|
- block_idx = min(0, 1) = 0
|
||||||
|
- order = 1
|
||||||
|
- Проверка: buddy(0, order 1) = 0 XOR 2 = 2
|
||||||
|
Если idx=2 свободен в order 1:
|
||||||
|
- Удаляем idx=2 из order 1
|
||||||
|
- order = 2
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## alloc_pages(page_count) — универсальная аллокация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
||||||
|
let order = order_for(page_count);
|
||||||
|
self.alloc(order).map(|idx| (idx, order))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`order_for()` вычисляет `ceil(log2(page_count))`.
|
||||||
|
|
||||||
|
## Вспомогательные функции
|
||||||
|
|
||||||
|
- `free_pages()` / `total_pages()` — геттеры.
|
||||||
|
- `is_exhausted()` — `free_pages == 0`.
|
||||||
|
- `stats()` — возвращает `[(order, count)]` для каждого порядка (количество
|
||||||
|
свободных блоков). Используется для отладки.
|
||||||
|
- `flist_contains(order, idx)` — проверка, есть ли блок в списке
|
||||||
|
(проверяет `prev != NEXT_SENTINEL` или `head == idx`).
|
||||||
|
|
||||||
|
## Почему Intrusive List?
|
||||||
|
|
||||||
|
1. **Zero overhead**: не нужно отдельной аллокации для списков.
|
||||||
|
2. **O(1)**: push/pop/remove — все O(1) операции.
|
||||||
|
3. **Константная память**: аллокатор не потребляет дополнительной памяти
|
||||||
|
сверх управляемых страниц.
|
||||||
|
4. **Кэш-дружественность**: доступ к next/prev обращается к той же странице,
|
||||||
|
которая будет выделена.
|
||||||
|
|
||||||
|
## Где используется
|
||||||
|
|
||||||
|
- `PMActor` содержит `BuddyAllocator` для управления своим диапазоном.
|
||||||
|
- Прямые тесты в `kmain()` демонстрируют all/alloc/free/coalesce/exhaustion.
|
||||||
82
kernel/docs/memory/introduction.md
Normal file
82
kernel/docs/memory/introduction.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Управление памятью: концептуальная модель
|
||||||
|
|
||||||
|
## Архитектурная философия
|
||||||
|
|
||||||
|
Управление памятью в Elyz построено как **трёхуровневая иерархия**,
|
||||||
|
где каждый уровень решает свою задачу и взаимодействует с соседними
|
||||||
|
через строго определённые интерфейсы.
|
||||||
|
|
||||||
|
```
|
||||||
|
Уровень 1: BitmapPMM (глобальный, физический)
|
||||||
|
│
|
||||||
|
│ предоставляет сырые фреймы
|
||||||
|
▼
|
||||||
|
Уровень 2: PMActor + BuddyAllocator (распределённый, физический)
|
||||||
|
│
|
||||||
|
│ управляет диапазонами, выдаёт под-диапазоны
|
||||||
|
▼
|
||||||
|
Уровень 3: AddressSpace (VMM) (виртуальный)
|
||||||
|
│
|
||||||
|
│ отображает физические фреймы в виртуальные адреса
|
||||||
|
▼
|
||||||
|
CPU (MMU, page tables)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Зачем три уровня?
|
||||||
|
|
||||||
|
- **BitmapPMM** — глобальный аллокатор физических фреймов. Простой,
|
||||||
|
надёжный, но неэффективный для частых alloc/free маленьких блоков.
|
||||||
|
Используется для начальной загрузки и для Page Table страниц.
|
||||||
|
|
||||||
|
- **BuddyAllocator + PMActor** — распределённая модель. Каждый актор
|
||||||
|
управляет своим диапазоном физической памяти через buddy-алгоритм.
|
||||||
|
Это даёт: (1) изоляцию — актор A не может истощить память актора B;
|
||||||
|
(2) масштабирование — акторы могут работать параллельно;
|
||||||
|
(3) предсказуемость — каждый актор знает свой лимит.
|
||||||
|
|
||||||
|
- **AddressSpace (VMM)** — виртуальные адресные пространства. PML4,
|
||||||
|
VMA-деревья, copy-on-write, lazy mapping. Использует PMM для
|
||||||
|
аллокации Page Table страниц (через `pmm_alloc` в `paging.rs`).
|
||||||
|
|
||||||
|
### Разделение ответственности по файлам
|
||||||
|
|
||||||
|
| Файл | Компонент | Роль в абстракции |
|
||||||
|
|------|-----------|-------------------|
|
||||||
|
| `address.rs` | PhysAddr / VirtAddr / HHDM | Базовые типы для адресов |
|
||||||
|
| `pmm.rs` | BitmapPMM | Глобальный менеджер физических фреймов |
|
||||||
|
| `buddy.rs` | BuddyAllocator | O(1) buddy allocator (intrusive list) |
|
||||||
|
| `pm_manages.rs` | PMActor + PMActorQueue | Актёр физической памяти + MPSC очередь |
|
||||||
|
| `pm_router.rs` | PMRouter | Lock-free маршрутизация запросов/ответов |
|
||||||
|
| `paging.rs` | PageTable | Аппаратные 4-уровневые page tables |
|
||||||
|
| `vmm.rs` | AddressSpace + VMA | Виртуальные адресные пространства |
|
||||||
|
| `allocator.rs` | SlabAllocator | Кучевой аллокатор (global_allocator) |
|
||||||
|
| `mod.rs` | Экспорт | Фасад подсистемы |
|
||||||
|
|
||||||
|
### Поток данных: типичная аллокация
|
||||||
|
|
||||||
|
```
|
||||||
|
Процесс A хочет 16 страниц:
|
||||||
|
1. Код пользователя отправляет PMRequest::Allocate в PMActor через
|
||||||
|
PMRouter::alloc_channel() + actor.submit_request()
|
||||||
|
2. Когда актор получает CPU, process_messages() вызывает
|
||||||
|
buddy.alloc_pages(16)
|
||||||
|
3. BuddyAllocator находит блок порядка 4 (2^4 = 16) или больше,
|
||||||
|
разбивает его при необходимости
|
||||||
|
4. PMActor создаёт Capability с CapObject::Memory { phys, size_pages }
|
||||||
|
5. Ответ (PMResponse::Allocated) отправляется через PMRouter
|
||||||
|
6. Получатель может отобразить фреймы в своё AddressSpace через
|
||||||
|
map_region() или map_shared()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поток данных: page fault
|
||||||
|
|
||||||
|
```
|
||||||
|
CPU ловит #PF (page fault):
|
||||||
|
1. interrupt.rs: rust_page_fault_handler() читает CR2
|
||||||
|
2. Получает блокировку KERNEL_SPACE
|
||||||
|
3. process_pending_revocations() — обрабатывает накопленные отзывы
|
||||||
|
4. handle_fault() проверяет: это COW? это lazy region?
|
||||||
|
5. Если COW — копируем страницу (copy-on-write)
|
||||||
|
6. Если lazy — alloc_frame() из PMM + map_page()
|
||||||
|
7. Если нераспознанный fault — KERNEL PANIC
|
||||||
|
```
|
||||||
196
kernel/docs/memory/paging.md
Normal file
196
kernel/docs/memory/paging.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Page Tables и отображение памяти: `paging.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
x86-64 использует **4-уровневую иерархию таблиц страниц**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Виртуальный адрес (48 бит):
|
||||||
|
┌─────────┬─────────┬─────────┬─────────┬─────────┐
|
||||||
|
│ PML4 │ PDPT │ PD │ PT │ offset │
|
||||||
|
│ bits │ bits │ bits │ bits │ (12) │
|
||||||
|
│ 47:39 │ 38:30 │ 29:21 │ 20:12 │ 11:0 │
|
||||||
|
└────┬────┴────┬────┴────┬────┴────┬────┴─────────┘
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
PML4 ──► PDPT ──► PD ──► PT ──► Физический фрейм
|
||||||
|
(512 ent) (512 ent) (512 ent) (512 ent) (4 KiB)
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждая запись (PTE) = 8 байт:
|
||||||
|
```
|
||||||
|
Bit 63: NX (No Execute)
|
||||||
|
Bits 62:52: Ignored/AVL
|
||||||
|
Bits 51:12: Physical frame address (4KiB aligned)
|
||||||
|
Bits 11:9: Ignored/AVL
|
||||||
|
Bits 8: Global
|
||||||
|
Bits 7: PS (Page Size = 1 GiB для PDPT, 2 MiB для PD)
|
||||||
|
Bits 6: Dirty
|
||||||
|
Bits 5: Accessed
|
||||||
|
Bits 4: PCD (Cache Disable)
|
||||||
|
Bits 3: PWT (Write Through)
|
||||||
|
Bits 2: U/S (User/Supervisor)
|
||||||
|
Bits 1: R/W (Read/Write)
|
||||||
|
Bit 0: Present
|
||||||
|
```
|
||||||
|
|
||||||
|
## PageTable — представление таблицы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C, align(4096))]
|
||||||
|
pub struct PageTable {
|
||||||
|
entries: [u64; 512],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `512` entries × 8 байт = 4096 байт (ровно одна страница).
|
||||||
|
- `align(4096)` — гарантирует, что структура размещается на физической
|
||||||
|
странице, как требует аппаратура.
|
||||||
|
|
||||||
|
## PageTableFlags — битовые флаги
|
||||||
|
|
||||||
|
```rust
|
||||||
|
bitflags! {
|
||||||
|
pub struct PageTableFlags: u64 {
|
||||||
|
const PRESENT = 1 << 0;
|
||||||
|
const WRITABLE = 1 << 1;
|
||||||
|
const USER = 1 << 2;
|
||||||
|
const WRITE_THROUGH = 1 << 3;
|
||||||
|
const NO_CACHE = 1 << 4;
|
||||||
|
const ACCESSED = 1 << 5;
|
||||||
|
const DIRTY = 1 << 6;
|
||||||
|
const HUGE_PAGE = 1 << 7;
|
||||||
|
const GLOBAL = 1 << 8;
|
||||||
|
const COW = 1 << 9; // Elyz-specific: Copy-on-Write marker
|
||||||
|
const NO_EXECUTE = 1 << 63;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Флаг `COW` (бит 9) — Elyz-специфичный, используется в AddressSpace для
|
||||||
|
маркировки страниц, которые нужно копировать при записи.
|
||||||
|
|
||||||
|
## Основные операции PageTable
|
||||||
|
|
||||||
|
### map_page(virt, phys, flags) — отображение одной страницы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn map_page(&mut self, virt: VirtAddr, phys: PhysAddr, flags: PageTableFlags) {
|
||||||
|
let p1 = self.walk_to_p1_mut(virt, true).expect("...");
|
||||||
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
|
p1.entries[p1_idx] = phys.0 | flags.bits();
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Процесс:**
|
||||||
|
1. `walk_to_p1_mut(virt, create=true)` проходит 4 уровня (P4→P3→P2→P1),
|
||||||
|
создавая недостающие таблицы через `pmm_alloc()`.
|
||||||
|
2. Записывает физический адрес с флагами в P1 entry.
|
||||||
|
3. `INVLPG` — сбрасывает TLB для этой страницы.
|
||||||
|
|
||||||
|
### map_region(virt, phys, size, flags) — отображение диапазона
|
||||||
|
|
||||||
|
Вызывает `map_page()` для каждой страницы в диапазоне.
|
||||||
|
`size.div_ceil(4096)` — количество страниц.
|
||||||
|
|
||||||
|
### unmap_page(virt) — удаление отображения
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||||||
|
// walk_to_p1_mut(virt, false) — не создаём, только ищем
|
||||||
|
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return; };
|
||||||
|
p1.entries[p1_idx] = 0; // Clear PTE
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Важно:** не освобождает физический фрейм — это ответственность
|
||||||
|
Caller'а (VMM). Идемпотентна: если PTE нет, ничего не делает.
|
||||||
|
|
||||||
|
### translate(virt) — трансляция виртуального адреса
|
||||||
|
|
||||||
|
Проходит 4 уровня, проверяя PRESENT на каждом уровне.
|
||||||
|
Поддерживает huge pages (1 GiB и 2 MiB):
|
||||||
|
- Если P3 entry имеет HUGE_PAGE — 1 GiB страница.
|
||||||
|
- Если P2 entry имеет HUGE_PAGE — 2 MiB страница.
|
||||||
|
|
||||||
|
### get_flags(virt) / update_flags(virt) — чтение/запись флагов
|
||||||
|
|
||||||
|
`get_flags` возвращает флаги PTE для виртуального адреса.
|
||||||
|
|
||||||
|
`update_flags` меняет флаги, сохраняя физический адрес, и вызывает INVLPG.
|
||||||
|
|
||||||
|
### activate(phys_addr) — загрузка в CR3
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn activate(&self, phys_addr: PhysAddr) {
|
||||||
|
asm!("mov cr3, {0}", in(reg) phys_addr.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Полная замена таблиц страниц. Полный TLB flush (без PCID).
|
||||||
|
|
||||||
|
В `vmm.rs::AddressSpace::activate()` используется улучшенная версия
|
||||||
|
с PCID и битом NOFLUSH.
|
||||||
|
|
||||||
|
## walk_to_p1_mut — сердце навигации
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn walk_to_p1_mut(&mut self, virt: VirtAddr, create: bool) -> Option<&mut Self> {
|
||||||
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
|
|
||||||
|
let p3 = self.get_or_create_next_table(p4_idx, create)?;
|
||||||
|
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
|
||||||
|
p2.get_or_create_next_table(p2_idx, create)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### get_or_create_next_table(index, create) — следующий уровень
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn get_or_create_next_table(&mut self, index: usize, create: bool) -> Option<&mut Self> {
|
||||||
|
if entry & PRESENT == 0 {
|
||||||
|
if !create { return None; }
|
||||||
|
// Аллоцируем новый фрейм для таблицы
|
||||||
|
let pt_phys = pmm_alloc().expect("OOM");
|
||||||
|
// Обнуляем (все entry = 0 = not present)
|
||||||
|
core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096);
|
||||||
|
// Устанавливаем entry: PRESENT | WRITABLE | USER
|
||||||
|
self.entries[index] = pt_phys.0 | (PRESENT | WRITABLE | USER);
|
||||||
|
}
|
||||||
|
// Преобразуем физический адрес entry в ссылку на PageTable
|
||||||
|
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
||||||
|
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### pte_addr_mask — маска адреса
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
|
||||||
|
```
|
||||||
|
|
||||||
|
Биты 51:12 — физический адрес с выравниванием 4KiB.
|
||||||
|
(0x000F_FFFF_FFFF_F000 = 52 бита адреса, очищенные нижние 12 бит).
|
||||||
|
|
||||||
|
## pmm_alloc — шлюз к PMM
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn pmm_alloc() -> Option<PhysAddr> {
|
||||||
|
PMM.lock().as_mut()?.alloc_frame()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Тонкая обёртка, чтобы избежать циклической зависимости между paging и pmm.
|
||||||
|
|
||||||
|
## Ключевые инварианты
|
||||||
|
|
||||||
|
1. Каждая PageTable занимает ровно одну физическую страницу (4 KiB).
|
||||||
|
2. Физический адрес таблицы всегда 4KiB-выровнен.
|
||||||
|
3. Все intermediate таблицы создаются с флагом USER (чтобы и ядро,
|
||||||
|
и пользователь могли их использовать). Разделение на user/kernel
|
||||||
|
контролируется на уровне P1 entry.
|
||||||
|
4. `INVLPG` вызывается после каждого изменения PTE для консистентности TLB.
|
||||||
132
kernel/docs/memory/physical-memory.md
Normal file
132
kernel/docs/memory/physical-memory.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Глобальный менеджер физической памяти: `pmm.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
`BitmapPMM` — это **глобальный, единственный в системе**, распределитель
|
||||||
|
физических фреймов (4 KiB). Он отвечает на вопрос:
|
||||||
|
|
||||||
|
> Какой физический фрейм сейчас свободен?
|
||||||
|
|
||||||
|
### Структуры данных
|
||||||
|
|
||||||
|
PMM использует **трёхуровневую битовую карту**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Уровень 1 (L1 bitmap): u64 слова
|
||||||
|
бит в L1 = 1 если соответствующее слово L0 не полностью занято
|
||||||
|
|
||||||
|
Уровень 0 (L0 bitmap): байты
|
||||||
|
каждый байт = 8 фреймов, каждый бит = 1 фрейм
|
||||||
|
бит = 1 → фрейм занят
|
||||||
|
бит = 0 → фрейм свободен
|
||||||
|
|
||||||
|
Ref-counts (счётчики ссылок): u16
|
||||||
|
для каждого фрейма — сколько раз он был захвачен
|
||||||
|
```
|
||||||
|
|
||||||
|
### Зачем L1 (ускорение)?
|
||||||
|
|
||||||
|
Наивный линейный поиск по битовой карте размером в мегабайты — O(N).
|
||||||
|
L1-битмап позволяет пропускать целые группы по 64 слова (4096 фреймов),
|
||||||
|
где нет свободных страниц.
|
||||||
|
|
||||||
|
**Поиск:**
|
||||||
|
1. Ищем `l1_bitmap[l1_idx]` с ненулевым значением (есть свободные).
|
||||||
|
2. Вычисляем `word_idx` по позиции бита внутри L1-слова.
|
||||||
|
3. Читаем `bitmap[word_idx]` как u64.
|
||||||
|
4. Если слово == `!0` (все заняты) — очищаем бит в L1, продолжаем.
|
||||||
|
5. Иначе — `(!word).trailing_zeros()` даёт номер свободного бита.
|
||||||
|
6. Вычисляем `page_idx` и возвращаем `PhysAddr`.
|
||||||
|
|
||||||
|
### Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Находим максимальный физический адрес из всех entry карты памяти.
|
||||||
|
2. Вычисляем `total_pages = max_addr / PAGE_SIZE`.
|
||||||
|
3. Вычисляем размер метаданных:
|
||||||
|
- `bitmap_size = total_pages / 8` (1 бит на фрейм)
|
||||||
|
- `ref_counts_size = total_pages * 2` (u16 на фрейм)
|
||||||
|
- `l1_size = (total_pages / 64 / 64) * 8`
|
||||||
|
4. Ищем **первый USABLE регион** размером >= метаданные.
|
||||||
|
5. Размещаем метаданные в этом регионе: сначала bitmap, потом ref_counts,
|
||||||
|
потом L1.
|
||||||
|
6. **Заполняем bitmap = 0xFF** (все занято).
|
||||||
|
7. **Проходим по карте памяти**: для каждого USABLE entry очищаем биты
|
||||||
|
(free_frame).
|
||||||
|
8. **Блокируем фреймы метаданных** (они теперь заняты PMM).
|
||||||
|
9. **Блокируем фрейм 0** (традиционно reserved).
|
||||||
|
10. Сохраняем `PMM.lock() = Some(pmm)`.
|
||||||
|
|
||||||
|
### alloc_frame() — пошагово
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Двухпроходный поиск: сначала от `last_word` до конца, потом от начала.
|
||||||
|
(`last_word` — хинт для локализации, даёт амортизированное O(1)).
|
||||||
|
2. Для каждого L1-слова: если != 0 — есть свободные.
|
||||||
|
3. `trailing_zeros()` даёт первое слово с хотя бы одним свободным битом.
|
||||||
|
4. Читаем слово как u64.
|
||||||
|
5. `(!word).trailing_zeros()` даёт свободный бит.
|
||||||
|
6. Вычисляем `page_idx`.
|
||||||
|
7. Устанавливаем бит = 1, ref_count = 1, used_pages += 1.
|
||||||
|
8. Если слово стало полностью занятым — очищаем бит в L1.
|
||||||
|
9. Обновляем `last_word`.
|
||||||
|
|
||||||
|
### free_frame() — пошагово
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Если бит уже 1 (фрейм занят):
|
||||||
|
- Уменьшаем ref_count (saturating_sub).
|
||||||
|
- Если ref_count == 0: очищаем бит, used_pages -= 1, обновляем L1.
|
||||||
|
2. Если бит == 0 (уже свободен) — ничего не делаем (идемпотентность).
|
||||||
|
|
||||||
|
### lock_frame() — захват без освобождения
|
||||||
|
|
||||||
|
Используется для резервирования фреймов, которые не должны быть
|
||||||
|
освобождены (нулевой фрейм, фреймы метаданных PMM).
|
||||||
|
|
||||||
|
### inc_ref_frame() — увеличение счётчика
|
||||||
|
|
||||||
|
Используется COW (Copy-on-Write) при fork для учёта разделяемых страниц.
|
||||||
|
|
||||||
|
## Thread Safety
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
||||||
|
```
|
||||||
|
|
||||||
|
Locked<T> — примитивная spinlock-обёртка (см. `allocator.rs`).
|
||||||
|
Все операции PMM требуют захвата блокировки.
|
||||||
|
|
||||||
|
## Глобальные функции
|
||||||
|
|
||||||
|
В `pmm.rs` определены функции-обёртки для удобства:
|
||||||
|
|
||||||
|
| Функция | Вызывает |
|
||||||
|
|---------|----------|
|
||||||
|
| `alloc_frame()` | `PMM.lock().as_mut()?.alloc_frame()` |
|
||||||
|
| `alloc_contiguous(n)` | `PMM.lock().as_mut()?.alloc_contiguous(n)` |
|
||||||
|
| `free_frame(addr)` | `PMM.lock().as_mut()?.free_frame(addr)` |
|
||||||
|
| `inc_ref_frame(addr)` | `PMM.lock().as_mut()?.inc_ref_frame(addr)` |
|
||||||
|
| `get_stats()` | `PMM.lock().as_ref()?.used_pages/total_pages` |
|
||||||
|
|
||||||
|
## alloc_contiguous() — выделение непрерывной области
|
||||||
|
|
||||||
|
Линейный проход по всем страницам, поиск `count` последовательных
|
||||||
|
свободных фреймов. O(total_pages) — используется редко, только для
|
||||||
|
устройств без IOMMU.
|
||||||
|
|
||||||
|
## Ключевая инварианта
|
||||||
|
|
||||||
|
`BitmapPMM` не знает о buddy-аллокаторах и PMActors. Он просто
|
||||||
|
отвечает на запросы «дай фрейм» и «забери фрейм». PMActors получают
|
||||||
|
свои диапазоны из PMM на этапе инициализации и больше к нему не
|
||||||
|
обращаются.
|
||||||
258
kernel/docs/memory/pm-actor.md
Normal file
258
kernel/docs/memory/pm-actor.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
# PM Actor: распределённый менеджер физической памяти
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**PMActor** (Physical Memory Actor) — это изолированный агент, который
|
||||||
|
владеет фиксированным диапазоном физической памяти и управляет им через
|
||||||
|
BuddyAllocator. Он следует **модели акторов**:
|
||||||
|
|
||||||
|
- У каждого актора есть **почтовый ящик** (MPSC queue).
|
||||||
|
- Другие компоненты **отправляют ему сообщения** (Allocate, Free, Carve).
|
||||||
|
- Актёр **обрабатывает сообщения**, когда получает CPU time.
|
||||||
|
- Актёр **отправляет ответы** через PM Router.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ PMActor #1 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────┐ │
|
||||||
|
│ │ MPSC Inbox (PMActorQueue) │ │
|
||||||
|
│ │ [Alloc] [Free] [Carve] [Alloc] [Free] ... │ │
|
||||||
|
│ └──────────────────────┬───────────────────────┘ │
|
||||||
|
│ │ pop │
|
||||||
|
│ ┌──────────────────────▼───────────────────────┐ │
|
||||||
|
│ │ process_messages() │ │
|
||||||
|
│ │ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||||
|
│ │ │handle_alloc│ │handle_free│ │handle_carve│ │ │
|
||||||
|
│ │ └──────┬─────┘ └────┬─────┘ └─────┬────┘ │ │
|
||||||
|
│ │ ▼ ▼ ▼ │ │
|
||||||
|
│ │ ┌────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ BuddyAllocator │ │ │
|
||||||
|
│ │ │ [диапазон: 0x4000_0000 — 0x4000_xxxx] │ │ │
|
||||||
|
│ │ └────────────────────────────────────────┘ │ │
|
||||||
|
│ └────────────────────────┬──────────────────────┘ │
|
||||||
|
│ │ Vec<PMResponse> │
|
||||||
|
│ ▼ │
|
||||||
|
│ Через PMRouter.route_responses() │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура PMActor
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMActor {
|
||||||
|
pub actor_id: u64, // Уникальный ID
|
||||||
|
pub root_untyped: Capability, // Capability на весь диапазон
|
||||||
|
pub managed_range: (PhysAddr, PhysAddr), // [start, end)
|
||||||
|
queue: PMActorQueue, // MPSC inbox
|
||||||
|
buddy: BuddyAllocator, // Buddy-аллокатор
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `actor_id` — используется в `token_sig` для уникальной идентификации.
|
||||||
|
- `root_untyped` — Strong capability на весь диапазон (корень дерева).
|
||||||
|
- `managed_range` — физические границы (начало, конец).
|
||||||
|
- `queue` — lock-free MPSC кольцевой буфер.
|
||||||
|
- `buddy` — аллокатор для этого диапазона.
|
||||||
|
|
||||||
|
## PMActorQueue — lock-free MPSC кольцевой буфер
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMActorQueue {
|
||||||
|
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
|
||||||
|
_pad0: [u8; 64],
|
||||||
|
head: AtomicUsize, // читатель (актор)
|
||||||
|
_pad1: [u8; 64],
|
||||||
|
tail: AtomicUsize, // писатели (производители)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Зачем padding?
|
||||||
|
|
||||||
|
`_pad0` и `_pad1` размером 64 байта — это размер кэш-линии x86-64.
|
||||||
|
Разделение head и tail по разным кэш-линиям предотвращает **false sharing**
|
||||||
|
(когда два ядра пишут в разные переменные, но они на одной кэш-линии).
|
||||||
|
|
||||||
|
### send(req) — Multi-Producer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn send(&self, req: PMRequest) -> Result<(), &'static str> {
|
||||||
|
let packed = req.pack();
|
||||||
|
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
let head = self.head.load(Ordering::Acquire);
|
||||||
|
if tail.wrapping_sub(head) >= QUEUE_SIZE {
|
||||||
|
return Err("Queue overflow");
|
||||||
|
}
|
||||||
|
// CAS: пытаемся захватить слот
|
||||||
|
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.buffer[tail & QUEUE_MASK].store(packed, Release);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(actual) => tail = actual,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `wrapping_sub`: кольцевой буфер, переполнение при разнице >= QUEUE_SIZE.
|
||||||
|
- `compare_exchange_weak`: позволяет нескольким продюсерам соревноваться.
|
||||||
|
- Store с `Release`: гарантирует, что данные видны потребителю.
|
||||||
|
|
||||||
|
### pop() — Single-Consumer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn pop(&self) -> Option<PMRequest> {
|
||||||
|
let head = self.head.load(Ordering::Relaxed);
|
||||||
|
let tail = self.tail.load(Ordering::Acquire);
|
||||||
|
if head == tail { return None; }
|
||||||
|
let packed = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
|
||||||
|
self.head.store(head + 1, Ordering::Release);
|
||||||
|
Some(PMRequest::unpack(packed))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Только актор вызывает pop — никакой конкуренции.
|
||||||
|
- Acquire на tail: видим последнюю запись продюсера.
|
||||||
|
|
||||||
|
## Message Packing
|
||||||
|
|
||||||
|
Каждое сообщение упаковывается в одно `u64`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bit 63:56 OPCODE (8 bits) 1=Alloc, 2=Free, 3=Carve
|
||||||
|
Bit 55:40 CHANNEL_ID (16 bits) маршрутизация ответа; 0 = discard
|
||||||
|
Bit 39:20 ARG1 (20 bits) size_pages/local_frame_idx/offset_pages
|
||||||
|
Bit 19: 0 ARG2 (20 bits) token_sig (low 20 bits) / size_pages / order
|
||||||
|
```
|
||||||
|
|
||||||
|
Максимум: 2^20 = 1 048 576 страниц = 4 GiB на одну аллокацию.
|
||||||
|
|
||||||
|
## Типы сообщений (PMRequest)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum PMRequest {
|
||||||
|
Allocate {
|
||||||
|
size_pages: usize, // сколько страниц
|
||||||
|
token_sig: u32, // тег capability
|
||||||
|
channel_id: u16, // куда отправить ответ
|
||||||
|
},
|
||||||
|
Free {
|
||||||
|
local_frame_idx: usize, // относительный индекс
|
||||||
|
order: usize, // buddy order
|
||||||
|
},
|
||||||
|
Carve {
|
||||||
|
offset_pages: usize, // смещение от начала
|
||||||
|
size_pages: usize, // размер
|
||||||
|
channel_id: u16, // куда отправить ответ
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PMResponse — результат
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMResponse {
|
||||||
|
pub channel_id: u16,
|
||||||
|
pub result: PMResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PMResult {
|
||||||
|
Allocated { cap: Capability, order: usize },
|
||||||
|
OutOfMemory { size_pages: usize },
|
||||||
|
Carved { cap: Capability },
|
||||||
|
Freed { pages_returned: usize },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработка сообщений
|
||||||
|
|
||||||
|
### handle_allocate(size_pages, token_sig, channel_id)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_allocate(&mut self, size_pages, token_sig, channel_id) -> Option<PMResponse> {
|
||||||
|
match self.buddy.alloc_pages(size_pages) {
|
||||||
|
Some((rel_idx, order)) => {
|
||||||
|
let phys = PhysAddr(self.managed_range.0.0 + rel_idx * PAGE_SIZE);
|
||||||
|
let cap = Capability {
|
||||||
|
object: CapObject::Memory { phys, size_pages: 1 << order },
|
||||||
|
rights: CapRights::READ | WRITE | GRANT,
|
||||||
|
relation: Relation::Strong,
|
||||||
|
token_sig: (token_sig as u64) ^ self.actor_id ^ (rel_idx as u64),
|
||||||
|
};
|
||||||
|
Some(PMResponse { channel_id, result: Allocated { cap, order } })
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
Some(PMResponse { channel_id, result: OutOfMemory { size_pages } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Детали:**
|
||||||
|
- `rel_idx` — относительный индекс в buddy (от 0 до total_pages).
|
||||||
|
- Физический адрес: `managed_range.start + rel_idx * 4096`.
|
||||||
|
- `token_sig` — XOR от token_sig запроса, actor_id и rel_idx для уникальности.
|
||||||
|
- Размер в capability: `1 << order` (округляется до степени двойки).
|
||||||
|
|
||||||
|
### handle_free(local_frame_idx, order)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_free(&mut self, local_frame_idx, order) -> Option<PMResponse> {
|
||||||
|
// Валидация границ
|
||||||
|
let block_end = local_frame_idx.saturating_add(1usize << order);
|
||||||
|
if block_end > self.buddy.total_pages() {
|
||||||
|
debug_assert!(false, "Out of range");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.buddy.free(local_frame_idx, order);
|
||||||
|
None // Free не требует ответа
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### handle_carve(offset_pages, size_pages, channel_id)
|
||||||
|
|
||||||
|
Carve — выделение под-диапазона без участия buddy-аллокатора.
|
||||||
|
Используется для статических размещений (framebuffer alias).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_carve(&mut self, offset_pages, size_pages, channel_id) -> Option<PMResponse> {
|
||||||
|
let range_pages = self.buddy.total_pages();
|
||||||
|
// Проверка границ
|
||||||
|
if offset_pages >= range_pages || size_pages == 0 || ... {
|
||||||
|
return Some(PMResponse { channel_id, result: OutOfMemory { size_pages } });
|
||||||
|
}
|
||||||
|
let phys = PhysAddr(self.managed_range.0.0 + offset_pages * PAGE_SIZE);
|
||||||
|
let cap = Capability {
|
||||||
|
object: CapObject::Memory { phys, size_pages },
|
||||||
|
rights: CapRights::READ | WRITE | GRANT,
|
||||||
|
relation: Relation::Strong,
|
||||||
|
token_sig: self.actor_id ^ offset_pages,
|
||||||
|
};
|
||||||
|
Some(PMResponse { channel_id, result: Carved { cap } })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ballooning hook (TODO)
|
||||||
|
|
||||||
|
Когда `BuddyAllocator::alloc_pages` возвращает `None`, актор генерирует
|
||||||
|
`OutOfMemory`. Будущая подсистема ballooning будет перехватывать этот
|
||||||
|
ответ до того, как он дойдёт до запросившего, и договариваться о передаче
|
||||||
|
памяти от соседнего актора.
|
||||||
|
|
||||||
|
## Владение и capabilities
|
||||||
|
|
||||||
|
- PMActor держит Strong capability на весь свой диапазон.
|
||||||
|
- Все выделенные или вырезанные под-диапазоны — тоже Strong (дочерние).
|
||||||
|
- При освобождении через Free buddy-дерево объединяет блоки.
|
||||||
|
- Актёр **никогда** не обращается к глобальному BitmapPMM.
|
||||||
|
|
||||||
|
## Максимальное количество сообщений за вызов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const MAX_MESSAGES_PER_CALL: usize = 64;
|
||||||
|
```
|
||||||
|
|
||||||
|
Ограничение предотвращает голодание ядра, когда inbox глубок.
|
||||||
189
kernel/docs/memory/pm-router.md
Normal file
189
kernel/docs/memory/pm-router.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# PM Router: Lock-Free маршрутизация ответов
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**PM Router** — это центральный коммутатор, соединяющий исполнителей
|
||||||
|
запросов (PMActors) с потребителями результатов (процессы/потоки).
|
||||||
|
|
||||||
|
- Он предоставляет **65536 каналов** для асинхронной коммуникации.
|
||||||
|
- Каналы **lock-free**: alloc/release канала — CAS на атомарных счётчиках.
|
||||||
|
- Исключает блокировки между продюсером и консьюмером.
|
||||||
|
|
||||||
|
```
|
||||||
|
Процесс A PMActor #1
|
||||||
|
│ │
|
||||||
|
│ alloc_channel() │
|
||||||
|
│ ─────────► router ──────► │
|
||||||
|
│ │
|
||||||
|
│ submit_request(Alloc) │
|
||||||
|
│ ────────────────────────► │
|
||||||
|
│ │ process_messages()
|
||||||
|
│ │ ──────► buddy.alloc()
|
||||||
|
│ │
|
||||||
|
│ ◄─────────────── │
|
||||||
|
│ route_responses([Resp]) │
|
||||||
|
│ router │
|
||||||
|
│ │
|
||||||
|
│ wait_for_response(ch) │
|
||||||
|
│ ◄═══ RESULT ════ │
|
||||||
|
│ │
|
||||||
|
│ free_channel (неявно) │
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура канала
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(align(64))]
|
||||||
|
pub struct Channel {
|
||||||
|
state: AtomicU8, // FREE=0, PENDING=1, READY=2
|
||||||
|
next_free: AtomicU16, // указатель в стеке свободных каналов
|
||||||
|
result: UnsafeCell<Option<PMResult>>, // ячейка результата
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `align(64)` — каждая кэш-линия содержит ровно один канал.
|
||||||
|
- `state` — конечный автомат: FREE → PENDING → READY → FREE.
|
||||||
|
- `UnsafeCell` — потому что write происходит из route_responses,
|
||||||
|
read — из wait_for_response. Синхронизация через state.
|
||||||
|
|
||||||
|
## Структура PMRouter
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMRouter {
|
||||||
|
channels: Box<[Channel]>, // 65536 каналов, Box<[T]> в куче
|
||||||
|
free_head: AtomicU16, // стек свободных каналов
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Стек свободных каналов
|
||||||
|
|
||||||
|
Изначально все каналы свободны, `free_head = 1` (канал 0 зарезервирован
|
||||||
|
как «discard» — ответы на канал 0 игнорируются).
|
||||||
|
|
||||||
|
```
|
||||||
|
free_head ──► channel[1].next_free = 2
|
||||||
|
channel[2].next_free = 3
|
||||||
|
channel[3].next_free = 4
|
||||||
|
...
|
||||||
|
channel[65535].next_free = 0 (NULL)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### alloc_channel() — выделить канал
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc_channel(&self) -> Option<u16> {
|
||||||
|
let mut head = self.free_head.load(Acquire);
|
||||||
|
loop {
|
||||||
|
if head == 0 { return None; } // нет свободных
|
||||||
|
let next = self.channels[head].next_free.load(Relaxed);
|
||||||
|
// CAS: free_head = head → next
|
||||||
|
match self.free_head.compare_exchange_weak(head, next, AcqRel, Acquire) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.channels[head].state.store(STATE_PENDING, Release);
|
||||||
|
return Some(head);
|
||||||
|
}
|
||||||
|
Err(new) => head = new,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Lock-free: CAS на `free_head` позволяет нескольким продюсерам
|
||||||
|
конкурировать без блокировок.
|
||||||
|
|
||||||
|
### route_responses(responses) — запись результатов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn route_responses(&self, responses: Vec<PMResponse>) {
|
||||||
|
for resp in responses {
|
||||||
|
if resp.channel_id == 0 { continue; }
|
||||||
|
let channel = &self.channels[resp.channel_id as usize];
|
||||||
|
unsafe { *channel.result.get() = Some(resp.result); }
|
||||||
|
channel.state.store(STATE_READY, Release);
|
||||||
|
// TODO: Focus Mode — пробуждение ожидающего потока
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### wait_for_response(id) — ожидание результата
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn wait_for_response(&self, id: u16) -> PMResult {
|
||||||
|
let channel = &self.channels[id as usize];
|
||||||
|
// busy-wait с HLT
|
||||||
|
while channel.state.load(Acquire) != STATE_READY {
|
||||||
|
unsafe { asm!("hlt") }; // CPU остановка до прерывания
|
||||||
|
}
|
||||||
|
let result = unsafe { (*channel.result.get()).take().unwrap() };
|
||||||
|
channel.state.store(STATE_FREE, Release);
|
||||||
|
|
||||||
|
// Возвращаем канал в стек свободных
|
||||||
|
let mut head = self.free_head.load(Relaxed);
|
||||||
|
loop {
|
||||||
|
channel.next_free.store(head, Relaxed);
|
||||||
|
match self.free_head.compare_exchange_weak(head, id, Release, Relaxed) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(new) => head = new,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### request_and_wait — синхронная обёртка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn request_and_wait<F>(actor: &PMActor, req_builder: F) -> PMResult
|
||||||
|
where
|
||||||
|
F: FnOnce(u16) -> PMRequest
|
||||||
|
{
|
||||||
|
let router = get_router();
|
||||||
|
let channel_id = router.alloc_channel().expect("OOM");
|
||||||
|
let req = req_builder(channel_id);
|
||||||
|
actor.submit_request(req).expect("Inbox full");
|
||||||
|
router.wait_for_response(channel_id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static ROUTER: GlobalRouter = GlobalRouter {
|
||||||
|
is_ready: AtomicBool::new(false),
|
||||||
|
inner: UnsafeCell::new(None),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Double-checked initialization:
|
||||||
|
1. `init()` — сетап всех каналов, `is_ready = true`.
|
||||||
|
2. `get_router()` — проверяет `is_ready`, затем `unwrap_unchecked()`.
|
||||||
|
|
||||||
|
**Безопасность**: `is_ready` устанавливается один раз и никогда не
|
||||||
|
очищается, поэтому TOCTOU между проверкой и unwrap — безопасен.
|
||||||
|
|
||||||
|
## dispatch() — утилита
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn dispatch(responses: Vec<PMResponse>) {
|
||||||
|
get_router().route_responses(responses);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ключевые свойства
|
||||||
|
|
||||||
|
| Свойство | Значение |
|
||||||
|
|----------|----------|
|
||||||
|
| Количество каналов | 65536 |
|
||||||
|
| Размер канала | 64 байта (1 cache line) |
|
||||||
|
| alloc_channel | Lock-free, O(1) |
|
||||||
|
| route_responses | O(N), N = количество ответов |
|
||||||
|
| wait_for_response | Lock-free + HLT |
|
||||||
|
| Состояния | FREE → PENDING → READY → FREE |
|
||||||
|
|
||||||
|
## Где используется
|
||||||
|
|
||||||
|
- В `kmain()`: тестирование PMActor через Router.
|
||||||
|
- Будущие IPC и процесс-менеджер будут использовать Router для
|
||||||
|
асинхронного взаимодействия с PMActors.
|
||||||
267
kernel/docs/memory/vmm.md
Normal file
267
kernel/docs/memory/vmm.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
# Virtual Memory Manager: `vmm.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**AddressSpace** — это представление виртуального адресного пространства
|
||||||
|
процесса. Оно состоит из:
|
||||||
|
|
||||||
|
1. **Аппаратной части**: PML4 (Page Map Level 4) — корневая таблица страниц.
|
||||||
|
2. **Программной части**: VMA (Virtual Memory Area) список — отсортированный
|
||||||
|
перечень отображённых регионов с метаданными.
|
||||||
|
|
||||||
|
```
|
||||||
|
AddressSpace
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ asid: u16 (PCID = Process Context ID) │
|
||||||
|
│ pml4_phys: PhysAddr (адрес PML4) │
|
||||||
|
│ regions: Vec<VmaRegion> (сортирован по адресу)│
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────────┐
|
||||||
|
│ PML4 (P4) │ ◄── CR3
|
||||||
|
├───────────────┤
|
||||||
|
│ PDE (P3) │
|
||||||
|
├───────────────┤
|
||||||
|
│ PTE (P2) │
|
||||||
|
├───────────────┤
|
||||||
|
│ PTE (P1) │ ◄── 4 KiB страницы
|
||||||
|
└───────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## VmaRegion — регион виртуальной памяти
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct VmaRegion {
|
||||||
|
pub virt_start: VirtAddr, // начало (выровнено на 4k)
|
||||||
|
pub virt_end: VirtAddr, // конец (эксклюзивный)
|
||||||
|
pub flags: VmaFlags, // READ, WRITE, EXEC, USER, LAZY, SHARED, COW...
|
||||||
|
pub cap_token: u64, // токен capability (для revoke)
|
||||||
|
pub backing: VmaBacking, // откуда берутся физические страницы
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VmaBacking — источник физических страниц
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum VmaBacking {
|
||||||
|
Anonymous(Vec<Option<PhysAddr>>),
|
||||||
|
// ^ Owned: каждая страница выделена из PMM под этот VMA.
|
||||||
|
// Option: None = lazy (страница ещё не выделена).
|
||||||
|
|
||||||
|
Physical(PhysAddr),
|
||||||
|
// ^ Не-owned: всё отображение — непрерывный физический диапазон.
|
||||||
|
// Frame не освобождаются при unmap.
|
||||||
|
|
||||||
|
Shared { owner_cap: u64, phys_base: PhysAddr },
|
||||||
|
// ^ Zero-copy shared mapping: чужие физические страницы.
|
||||||
|
// Frame не освобождаются при unmap.
|
||||||
|
// При revoke owner_cap — все Shared VMA с этим токеном удаляются.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VmaFlags
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct VmaFlags: u32 {
|
||||||
|
const READ = 1 << 0;
|
||||||
|
const WRITE = 1 << 1;
|
||||||
|
const EXEC = 1 << 2;
|
||||||
|
const USER = 1 << 3;
|
||||||
|
const LAZY = 1 << 4; // Demand-paging: page fault = alloc frame
|
||||||
|
const SHARED = 1 << 5; // Zero-copy shared mapping
|
||||||
|
const PINNED = 1 << 6; // Защищён от revoke
|
||||||
|
const NOCACHE = 1 << 7; // Cache disabled (для MMIO)
|
||||||
|
const MMIO = 1 << 8; // Memory-mapped I/O
|
||||||
|
const COW = 1 << 9; // Copy-on-Write active
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`to_page_flags()` транслирует VmaFlags в PageTableFlags:
|
||||||
|
- PRESENT всегда
|
||||||
|
- WRITE → WRITABLE
|
||||||
|
- EXEC → NO_EXECUTE (инвертировано!)
|
||||||
|
- LAZY → PRESENT без фрейма (fault отлавливается)
|
||||||
|
- COW → COW (кастомный бит 9)
|
||||||
|
|
||||||
|
## ASID Allocator — выделение PCID
|
||||||
|
|
||||||
|
x86-64 PCID (Process Context Identifier) — 12-битный идентификатор (0-4095).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct AsidAllocator {
|
||||||
|
bitmap: [u32; 128], // 4096 бит
|
||||||
|
next_hint: u16, // хинт для амортизированного O(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- ASID 0: kernel (no PCID tagging) — всегда занят.
|
||||||
|
- ASID 4095: reserved by Intel spec — всегда занят.
|
||||||
|
- ASID 1–4094: пользовательские.
|
||||||
|
|
||||||
|
`alloc()` — Two-pass: от hint до 4094, затем от 1 до hint.
|
||||||
|
Пропускает полностью занятые 32-битные слова.
|
||||||
|
|
||||||
|
## Основные операции AddressSpace
|
||||||
|
|
||||||
|
### `new()` — создание пустого пространства
|
||||||
|
|
||||||
|
1. Аллоцирует фрейм из PMM для PML4.
|
||||||
|
2. Обнуляет его (все entry = not present).
|
||||||
|
3. Выделяет ASID из ASID_ALLOC.
|
||||||
|
4. Пустой список regions.
|
||||||
|
|
||||||
|
### `from_active(pml4_phys, asid)` — обёртка существующего PML4
|
||||||
|
|
||||||
|
Используется для kernel space (ASID 0).
|
||||||
|
|
||||||
|
### `map_region(virt, phys, size, flags, cap_token)` — отображение
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn map_region(&mut self, virt: VirtAddr, phys: Option<PhysAddr>,
|
||||||
|
size: u64, flags: VmaFlags, cap_token: u64) -> Result<VirtAddr, VmError>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `phys = Some(base)`: Physical mapping — непрерывный диапазон.
|
||||||
|
- `phys = None + LAZY`: Demand-paging — фреймы выделяются при fault'е.
|
||||||
|
- `phys = None + !LAZY`: Eager alloc — все фреймы выделяются сейчас.
|
||||||
|
|
||||||
|
Создаёт VmaRegion, вставляет в отсортированный список.
|
||||||
|
|
||||||
|
### `map_shared(virt, phys_base, page_count, flags, owner_cap)` — zero-copy
|
||||||
|
|
||||||
|
Как map_region, но:
|
||||||
|
- Не копирует фреймы (не владеет ими).
|
||||||
|
- Всегда eagerly отображает (адреса известны).
|
||||||
|
- Записывает `cap_token = owner_cap` для revoke.
|
||||||
|
|
||||||
|
### `unmap_region(virt)` — удаление VMA
|
||||||
|
|
||||||
|
1. Находит VMA по адресу.
|
||||||
|
2. Удаляет из списка.
|
||||||
|
3. Вызывает `do_unmap(pml4, region)`:
|
||||||
|
- unmap_page для каждой страницы.
|
||||||
|
- Если backing owns_frames (Anonymous) — free_frame.
|
||||||
|
4. TLB flush.
|
||||||
|
|
||||||
|
### `handle_fault(fault_addr, write)` — Page Fault handler
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Находит VMA, содержащий fault_addr.
|
||||||
|
2. Проверяет права (write + !WRITE = PermissionDenied).
|
||||||
|
3. Проверяет COW:
|
||||||
|
- Если write + COW: аллоцирует новый фрейм, копирует данные,
|
||||||
|
заменяет PTE, освобождает старый фрейм.
|
||||||
|
4. Проверяет LAZY:
|
||||||
|
- Если LAZY + page не выделена: alloc_frame, обнуление, map_page.
|
||||||
|
5. Иначе: UnexpectedFault → panic.
|
||||||
|
|
||||||
|
### `clone_for_fork(child_cap_token)` — fork адресного пространства
|
||||||
|
|
||||||
|
1. Создаёт пустое AddressSpace::new().
|
||||||
|
2. Для каждого VMA родителя:
|
||||||
|
- Physical/Shared: просто копирует PTE (та же физическая память).
|
||||||
|
- Anonymous (owned): COW.
|
||||||
|
* Устанавливает COW флаг на родительские PTE.
|
||||||
|
* Убирает WRITABLE с родительских PTE.
|
||||||
|
* Отображает те же фреймы в дочернее пространство (без WRITABLE).
|
||||||
|
* При записи → handle_fault → copy-on-write.
|
||||||
|
|
||||||
|
### `revoke_by_token(cap_token)` / `do_revoke_by_token(cap_token)` — отзыв
|
||||||
|
|
||||||
|
Удаляет все нем-PINNED VMA с заданным cap_token.
|
||||||
|
- Собирает индексы регионов, соответствующих токену.
|
||||||
|
- Удаляет в обратном порядке (чтобы индексы не сдвигались).
|
||||||
|
- TLB flush.
|
||||||
|
|
||||||
|
### `process_pending_revocations()` — обработка очереди отзыва
|
||||||
|
|
||||||
|
Дренирует глобальную `MMU_REVOCATION_QUEUE` и revoke-ит каждый токен.
|
||||||
|
Вызывается перед handle_fault при page fault'е, чтобы избежать deadlock
|
||||||
|
с capability subsystem.
|
||||||
|
|
||||||
|
## TLB Management
|
||||||
|
|
||||||
|
### `local_tlb_flush_asid(asid)` — сброс TLB для одного ASID
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn local_tlb_flush_asid(asid: u16) {
|
||||||
|
if INVPCID_SUPPORTED {
|
||||||
|
// INVPCID type 1 (single-context): сбрасывает только
|
||||||
|
// TLB entries, помеченные данным PCID.
|
||||||
|
asm!("invpcid {ty}, [{desc}]", ...);
|
||||||
|
} else {
|
||||||
|
// Fallback: полный сброс TLB (все контексты)
|
||||||
|
tlb_flush_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `tlb_flush_asid(asid)` — TLB shootdown (SMP)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn tlb_flush_asid(asid: u16) {
|
||||||
|
local_tlb_flush_asid(asid); // всегда сбрасываем локальный
|
||||||
|
|
||||||
|
let target_mask = active_cpus & !(1 << current_core);
|
||||||
|
if target_mask == 0 { return; }
|
||||||
|
|
||||||
|
// Lock SHOOTDOWN_LOCK → записываем ASID → broadcast IPI → wait ACK
|
||||||
|
SHOOTDOWN_ASID.store(asid);
|
||||||
|
SHOOTDOWN_ACK.store(0);
|
||||||
|
lapic::broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR);
|
||||||
|
|
||||||
|
while SHOOTDOWN_ACK.load() & target_mask != target_mask {
|
||||||
|
spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `tlb_flush_all()` — полный сброс
|
||||||
|
|
||||||
|
Перезапись CR3 без PCID NOFLUSH бита.
|
||||||
|
|
||||||
|
## AddressSpace::activate() — контекстный переключатель
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn activate(&self) {
|
||||||
|
let cr3 = self.pml4_phys.0 | u64::from(self.asid) | (1u64 << 63);
|
||||||
|
// ^ Биты 11:0 = ASID, бит 63 = NOFLUSH (не сбрасывать TLB)
|
||||||
|
asm!("mov cr3, {0}", in(reg) cr3);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный kernel space
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
||||||
|
```
|
||||||
|
|
||||||
|
Инициализируется в `init_kernel_space(pml4_phys)` после создания
|
||||||
|
первичных page tables.
|
||||||
|
|
||||||
|
## Drop для AddressSpace
|
||||||
|
|
||||||
|
При Drop:
|
||||||
|
1. unmap всех регионов (с освобождением owned frames).
|
||||||
|
2. free_frame(pml4_phys).
|
||||||
|
3. free_asid(asid).
|
||||||
|
|
||||||
|
## VmError — типы ошибок
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum VmError {
|
||||||
|
OutOfMemory, // Нет фреймов в PMM
|
||||||
|
RegionOverlap, // VMA пересекается с существующим
|
||||||
|
RegionNotFound, // Нет VMA по адресу
|
||||||
|
InvalidAlignment, // Адрес/размер не выровнены на 4k
|
||||||
|
InvalidRange, // Нулевой размер или переполнение
|
||||||
|
PermissionDenied, // Нет прав на запись
|
||||||
|
UnexpectedFault, // Fault в не-lazy, не-COW регионе
|
||||||
|
NonCanonical, // Некорректный адрес
|
||||||
|
AsidExhausted, // Закончились ASID
|
||||||
|
}
|
||||||
|
```
|
||||||
105
kernel/docs/overview/architecture.md
Normal file
105
kernel/docs/overview/architecture.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Архитектура ядра Elyz
|
||||||
|
|
||||||
|
## Высокоуровневая схема
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ kmain() — точка входа │
|
||||||
|
│ (инициализация: Limine, PMM, LAPIC, IDT, VMM, PM Router, Caps) │
|
||||||
|
└──────────────┬──────────────────────────────────────┬───────────────┘
|
||||||
|
│ │
|
||||||
|
┌──────────▼──────────┐ ┌─────────▼──────────────┐
|
||||||
|
│ memory subsystem │ │ capability system │
|
||||||
|
│ │ │ │
|
||||||
|
│ ┌───────────────┐ │ │ ┌──────────────────┐ │
|
||||||
|
│ │ BitmapPMM │ │ │ │ CapDescriptor │ │
|
||||||
|
│ │ (глобальный) │ │ │ │ (права + объект) │ │
|
||||||
|
│ └───────┬───────┘ │ │ └────────┬─────────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ ┌───────▼───────┐ │ │ ┌────────▼─────────┐ │
|
||||||
|
│ │ PM Actor + │ │ │ │ CNode │ │
|
||||||
|
│ │ BuddyAlloc │ │ │ │ (таблица caps) │ │
|
||||||
|
│ └───────┬───────┘ │ │ └──────────────────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ┌───────▼───────┐ │ └────────────────────────┘
|
||||||
|
│ │ PM Router │ │
|
||||||
|
│ │ (каналы) │ │
|
||||||
|
│ └───────┬───────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌───────▼───────┐ │
|
||||||
|
│ │ AddressSpace │ │
|
||||||
|
│ │ (VMM, PML4) │ │
|
||||||
|
│ └───────────────┘ │
|
||||||
|
└─────────────────────┘
|
||||||
|
|
||||||
|
┌──────────────────────┐ ┌──────────────────────┐
|
||||||
|
│ cpu subsystem │ │ debug subsystem │
|
||||||
|
│ ┌────────────────┐ │ │ ┌─────────────────┐ │
|
||||||
|
│ │ IDT + Interrupt│ │ │ │ Serial port │ │
|
||||||
|
│ │ Handlers │ │ │ │ (COM1) │ │
|
||||||
|
│ └────────────────┘ │ │ └─────────────────┘ │
|
||||||
|
│ ┌────────────────┐ │ │ ┌─────────────────┐ │
|
||||||
|
│ │ Local APIC │ │ │ │ Log macros │ │
|
||||||
|
│ └────────────────┘ │ │ │ (info!/warn!) │ │
|
||||||
|
└──────────────────────┘ │ └─────────────────┘ │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Порядок инициализации в `kmain()`
|
||||||
|
|
||||||
|
1. **Limine requests** — статические структуры, сообщающие bootloader'у,
|
||||||
|
что ядру нужны: framebuffer, memory map, HHDM offset, адрес ядра.
|
||||||
|
2. **BASE_REVISION проверка** — убеждаемся, что Limine совместим.
|
||||||
|
3. **HHDM инициализация** — сохраняем Higher-Half Direct Map offset.
|
||||||
|
4. **Framebuffer/TTY** — создаём консоль для вывода на экран.
|
||||||
|
5. **Serial port** — инициализируем COM1 для отладки.
|
||||||
|
6. **Early IDT** — загружаем базовые обработчики исключений (0–31).
|
||||||
|
7. **BitmapPMM::init()** — сканируем карту памяти, строим битовую карту.
|
||||||
|
8. **LAPIC::init()** — настраиваем Local APIC.
|
||||||
|
9. **Создание P4** — аллоцируем фрейм для корневой таблицы страниц.
|
||||||
|
10. **Identity map** — отображаем всю физическую память в HHDM
|
||||||
|
и (для non-reserved) в идентичное отображение.
|
||||||
|
11. **Map kernel** — отображаем образ ядра.
|
||||||
|
12. **Activate P4** — загружаем новую таблицу страниц в CR3.
|
||||||
|
13. **Heap init** — резервируем 8 MiB для slab-аллокатора.
|
||||||
|
14. **CPU features** — проверяем INVPCID.
|
||||||
|
15. **VMM init** — регистрируем AddressSpace ядра.
|
||||||
|
16. **IDT final** — перезагружаем IDT с Page Fault и TLB shootdown.
|
||||||
|
17. **PM Router init** — создаём 65536 каналов.
|
||||||
|
18. **STI** — разрешаем прерывания.
|
||||||
|
19. **Capability тесты** — тестируем CNode, mint, revoke.
|
||||||
|
20. **PMActor тесты** — создаём актор, тестируем Alloc/Free/Carve/OOM.
|
||||||
|
21. **Buddy тесты** — прямое тестирование аллокатора.
|
||||||
|
22. **PMM stress test** — alloc/free 64 фреймов.
|
||||||
|
23. **HCF** — гасим CPU.
|
||||||
|
|
||||||
|
## Модульная структура
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.rs — точка входа, инициализация, тесты
|
||||||
|
├── cap/ — capability система
|
||||||
|
│ ├── mod.rs — CNode (таблица дескрипторов)
|
||||||
|
│ ├── descriptor.rs — типы Capability, CapObject, CapRights, Relation
|
||||||
|
│ └── object.rs — KernelObject (ref-counted)
|
||||||
|
├── cpu/ — подсистема CPU
|
||||||
|
│ ├── mod.rs — реэкспорт модулей
|
||||||
|
│ ├── idt.rs — IDT entry/table/ptr
|
||||||
|
│ ├── interrupts.rs — обработчики исключений, TLB shootdown
|
||||||
|
│ └── lapic.rs — Local APIC драйвер
|
||||||
|
├── debug/ — подсистема отладки
|
||||||
|
│ ├── mod.rs — LogLevel, log!/info!/warn! макросы
|
||||||
|
│ └── serial.rs — COM1 serial port драйвер
|
||||||
|
├── events.rs — RevocationQueue (lock-free кольцевой буфер)
|
||||||
|
├── tty.rs — Framebuffer console (PSF2 шрифты)
|
||||||
|
├── mem/ — подсистема памяти
|
||||||
|
│ ├── mod.rs — реэкспорт модулей
|
||||||
|
│ ├── address.rs — PhysAddr, VirtAddr, HHDM offset
|
||||||
|
│ ├── allocator.rs — SlabAllocator + Locked<T> + глобальный аллокатор
|
||||||
|
│ ├── buddy.rs — BuddyAllocator (intrusive list, O(1))
|
||||||
|
│ ├── paging.rs — PageTable, 4-level page walk
|
||||||
|
│ ├── pmm.rs — BitmapPMM (tree bitmap с L1 ускорением)
|
||||||
|
│ ├── pm_manages.rs — PMActor + PMActorQueue (MPSC)
|
||||||
|
│ ├── pm_router.rs — PMRouter (65536 lock-free каналов)
|
||||||
|
│ └── vmm.rs — AddressSpace, VMA, COW, fork, TLB shootdown
|
||||||
|
```
|
||||||
72
kernel/docs/overview/introduction.md
Normal file
72
kernel/docs/overview/introduction.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Введение в ядро Elyz (LISA)
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
Elyz (LISA) — это модульное, capability-ориентированное ядро для x86-64,
|
||||||
|
спроектированное вокруг следующих архитектурных принципов:
|
||||||
|
|
||||||
|
### 1. Capability-ориентированная безопасность
|
||||||
|
|
||||||
|
Вместо традиционной модели «всё или ничего» (ring 0 vs ring 3), Elyz
|
||||||
|
использует систему **capabilities** (дескрипторов прав). Каждый дескриптор
|
||||||
|
представляет собой *неподделываемый токен*, дающий доступ к конкретному
|
||||||
|
объекту ядра (фрейму памяти, CNode, PMActor) с определёнными правами.
|
||||||
|
|
||||||
|
- Capability — это не просто число; это структура с полями `object`, `rights`,
|
||||||
|
`relation`, `token_sig`.
|
||||||
|
- Capability можно *создавать* (mint) с урезанными правами от родительского
|
||||||
|
дескриптора.
|
||||||
|
- Capability можно *отозвать* (revoke), что каскадно уничтожает всех потомков.
|
||||||
|
- Система гарантирует, что access — это всегда наличие capability.
|
||||||
|
|
||||||
|
### 2. Многоуровневое управление памятью
|
||||||
|
|
||||||
|
Управление физической памятью разделено на три уровня:
|
||||||
|
|
||||||
|
| Уровень | Компонент | Ответственность |
|
||||||
|
|---------|-----------|-----------------|
|
||||||
|
| 1 (глобальный) | `BitmapPMM` | Владение всей физической памятью, аллокация/освобождение по 4KiB фреймам. Инициализируется из карты памяти bootloader'а. |
|
||||||
|
| 2 (распределённый) | `PMActor` | Актёр физической памяти — владеет *диапазоном* физической памяти, использует `BuddyAllocator` для аллокации внутри этого диапазона. Каждый PMActor — изолированный аллокатор. |
|
||||||
|
| 3 (виртуальный) | `AddressSpace` (VMM) | Управляет виртуальными адресными пространствами (PML4 + VMA-деревья). Использует PMM для аллокации PT-страниц и lazy demand-paging. |
|
||||||
|
|
||||||
|
Эти уровни связывает **PM Router** — lock-free система каналов для асинхронной
|
||||||
|
пересылки запросов и ответов между исполнителями.
|
||||||
|
|
||||||
|
### 3. Асинхронная модель акторов
|
||||||
|
|
||||||
|
PMActor следует модели CSP (Communicating Sequential Processes):
|
||||||
|
|
||||||
|
- Каждый актор имеет lock-free MPSC-очередь входящих сообщений.
|
||||||
|
- Потоки/процессы отправляют запросы (Alloc/Free/Carve) в очередь актора.
|
||||||
|
- Когда актор получает CPU time, он вызывает `process_messages()` и
|
||||||
|
отправляет ответы через PM Router.
|
||||||
|
- PM Router использует 65536 предварительно выделенных каналов для
|
||||||
|
lock-free маршрутизации результатов.
|
||||||
|
|
||||||
|
### 4. Виртуальная память с Copy-on-Write
|
||||||
|
|
||||||
|
`AddressSpace` поддерживает:
|
||||||
|
- **Lazy demand-paging**: физическая страница выделяется только при
|
||||||
|
page fault'е.
|
||||||
|
- **Copy-on-Write (COW)**: при fork'е адресного пространства страницы
|
||||||
|
разделяются между родителем и потомком; копирование происходит
|
||||||
|
при первой записи.
|
||||||
|
- **Zero-copy shared mappings**: физические страницы, принадлежащие
|
||||||
|
capability, могут быть отображены в другое адресное пространство
|
||||||
|
без копирования. Владелец страниц — capability, а не VMA.
|
||||||
|
|
||||||
|
### 5. Отзыв (Revocation) через lock-free очередь
|
||||||
|
|
||||||
|
Система отзыва дескрипторов использует глобальную lock-free кольцевую
|
||||||
|
очередь `MMU_REVOCATION_QUEUE`. При отзыве capability:
|
||||||
|
1. Токен capability помещается в очередь.
|
||||||
|
2. При следующем page fault'е VMM обрабатывает накопленные отзывы.
|
||||||
|
3. Все VMA, связанные с отозванным токеном, аннулируются.
|
||||||
|
|
||||||
|
### 6. Минималистичный дизайн
|
||||||
|
|
||||||
|
- `#![no_std]` — никакой стандартной библиотеки.
|
||||||
|
- `#![no_main]` — точка входа `kmain`.
|
||||||
|
- Все структуры данных, аллокаторы и драйверы написаны с нуля.
|
||||||
|
- Единственные внешние зависимости: `limine` (bootloader протокол),
|
||||||
|
`embedded-graphics` (шрифты), `bitflags`.
|
||||||
150
kernel/docs/tty.md
Normal file
150
kernel/docs/tty.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Framebuffer Console: `tty.rs`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Драйвер графического вывода текста на framebuffer. Использует
|
||||||
|
PSF2 (PC Screen Font 2) шрифты для отрисовки символов.
|
||||||
|
|
||||||
|
Является основным визуальным каналом вывода ядра (через info! макрос).
|
||||||
|
|
||||||
|
## Структура Console
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Console<'a> {
|
||||||
|
framebuffer: &'a Framebuffer<'a>, // ссылка на Limine framebuffer
|
||||||
|
font: &'static [u8], // PSF2 шрифт
|
||||||
|
pub x: usize, // текущая позиция X (колонка)
|
||||||
|
pub y: usize, // текущая позиция Y (строка)
|
||||||
|
pub fg_color: u32, // цвет текста (RGB)
|
||||||
|
pub bg_color: u32, // цвет фона (RGB)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PSF2 Header
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct Psf2Header {
|
||||||
|
magic: u32, // 0x864AB572 (PSF2 magic)
|
||||||
|
version: u32,
|
||||||
|
header_size: u32, // размер заголовка
|
||||||
|
flags: u32,
|
||||||
|
num_glyphs: u32, // количество глифов
|
||||||
|
bytes_per_glyph: u32, // байт на глиф
|
||||||
|
height: u32, // высота глифа в пикселях
|
||||||
|
width: u32, // ширина глифа в пикселях
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Шрифт — `font.psf` (8×16, встроен через `include_bytes!`).
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### clear()
|
||||||
|
|
||||||
|
Заполняет весь framebuffer нулями. Сбрасывает позицию курсора в (0, 0).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_bytes(fb.addr(), 0, (fb.pitch() * fb.height()) as usize);
|
||||||
|
}
|
||||||
|
self.x = 0;
|
||||||
|
self.y = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### scroll()
|
||||||
|
|
||||||
|
Сдвигает содержимое framebuffer вверх на высоту шрифта.
|
||||||
|
Новые строки заполняются нулями.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn scroll(&mut self) {
|
||||||
|
let shift = font_height * pitch; // смещение на 1 строку
|
||||||
|
let size = pitch * (height - font_height); // сколько копировать
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(addr.add(shift), addr, size);
|
||||||
|
ptr::write_bytes(addr.add(size), 0, shift);
|
||||||
|
}
|
||||||
|
self.y -= font_height;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### draw_glyph(glyph_index, x, y)
|
||||||
|
|
||||||
|
Рисует глиф (символ) в заданной позиции.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn draw_glyph(&mut self, glyph_index: u32, x: usize, y: usize) {
|
||||||
|
let bytes_per_line = (width + 7) / 8; // пиксели → байты
|
||||||
|
let glyph_offset = header_size + (glyph_index * bytes_per_glyph);
|
||||||
|
|
||||||
|
for cy in 0..height {
|
||||||
|
let glyph_row = self.font[glyph_offset + cy * bytes_per_line];
|
||||||
|
for cx in 0..width {
|
||||||
|
if (glyph_row & (0x80 >> cx)) != 0 {
|
||||||
|
// Пиксель включён — рисуем fg_color
|
||||||
|
let offset = ((y + cy) * fb_pitch) + ((x + cx) * 4);
|
||||||
|
unsafe {
|
||||||
|
fb_addr.add(offset).cast::<u32>().write_volatile(self.fg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Если пиксель выключен — фон остаётся (нет очистки)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `width=8`, `bytes_per_line=1`: каждый ряд глифа = 1 байт.
|
||||||
|
- `(0x80 >> cx)`: проверка бита слева направо (MSB first).
|
||||||
|
- `write_volatile`: запрещает компилятору оптимизировать запись в MMIO.
|
||||||
|
|
||||||
|
### write_char(c)
|
||||||
|
|
||||||
|
Печатает символ:
|
||||||
|
- `\n` → переход на новую строку.
|
||||||
|
- Другие символы → draw_glyph. Если символ > num_glyphs → глиф 0.
|
||||||
|
- Перенос строки по ширине экрана.
|
||||||
|
- Скроллинг при достижении низа.
|
||||||
|
|
||||||
|
## fmt::Write реализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl fmt::Write for Console<'_> {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
for c in s.chars() {
|
||||||
|
self.write_char(c);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let fb = fb_res.framebuffers().next().expect("No framebuffer");
|
||||||
|
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||||
|
console.clear();
|
||||||
|
|
||||||
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
// ... остальные логи
|
||||||
|
|
||||||
|
let _ = writeln!(console, "{}", logo); // ASCII art при загрузке
|
||||||
|
```
|
||||||
|
|
||||||
|
## Детали реализации
|
||||||
|
|
||||||
|
- **Цвета**: write_volatile для 32-битного пикселя. Формат — RGBA?
|
||||||
|
(зависит от режима framebuffer).
|
||||||
|
- **Фон**: не перерисовывается. Только пиксели текста.
|
||||||
|
Для полной очистки — clear().
|
||||||
|
- **PSF2 magic**: `0x864AB572` — проверяется в заголовке.
|
||||||
|
- **Производительность**: draw_glyph простая, без буферизации.
|
||||||
|
Для скроллинга — memcpy всего framebuffer.
|
||||||
|
|
||||||
|
## Зависимости
|
||||||
|
|
||||||
|
- `KERNEL_FONT: &[u8]` — встроен в main.rs через `include_bytes!("font.psf")`.
|
||||||
|
- `limine::framebuffer::Framebuffer` — через HHDM отображённый framebuffer.
|
||||||
@@ -10,6 +10,7 @@ pub use descriptor::*;
|
|||||||
pub struct CNodeSlot {
|
pub struct CNodeSlot {
|
||||||
pub cap: Capability,
|
pub cap: Capability,
|
||||||
pub parent_idx: Option<usize>,
|
pub parent_idx: Option<usize>,
|
||||||
|
pub children: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CNode {
|
pub struct CNode {
|
||||||
@@ -23,6 +24,7 @@ impl CNode {
|
|||||||
slots.push(Locked::new(CNodeSlot {
|
slots.push(Locked::new(CNodeSlot {
|
||||||
cap: Capability::empty(),
|
cap: Capability::empty(),
|
||||||
parent_idx: None,
|
parent_idx: None,
|
||||||
|
children: Vec::new(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Self { slots }
|
Self { slots }
|
||||||
@@ -42,12 +44,10 @@ impl CNode {
|
|||||||
return Err("Index out of bounds");
|
return Err("Index out of bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Защита от дедлока: если src == dest, операция не имеет смысла
|
|
||||||
if src == dest {
|
if src == dest {
|
||||||
return Err("Source and destination slots must be different");
|
return Err("Source and destination slots must be different");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Захватываем блокировки в строгом порядке индексов для предотвращения инверсии блокировок (Lock Ranking)
|
|
||||||
let mut _guard_low;
|
let mut _guard_low;
|
||||||
let mut _guard_high;
|
let mut _guard_high;
|
||||||
|
|
||||||
@@ -68,72 +68,72 @@ impl CNode {
|
|||||||
return Err("Insufficient rights to mint (Missing GRANT flag)");
|
return Err("Insufficient rights to mint (Missing GRANT flag)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Права дочернего дескриптора не могут превышать права родительского
|
|
||||||
let final_rights = src_slot.cap.rights & rights;
|
let final_rights = src_slot.cap.rights & rights;
|
||||||
|
|
||||||
|
dest_slot.children.clear();
|
||||||
dest_slot.cap = src_slot.cap;
|
dest_slot.cap = src_slot.cap;
|
||||||
dest_slot.cap.rights = final_rights;
|
dest_slot.cap.rights = final_rights;
|
||||||
dest_slot.cap.relation = relation;
|
dest_slot.cap.relation = relation;
|
||||||
dest_slot.parent_idx = Some(src);
|
dest_slot.parent_idx = Some(src);
|
||||||
|
|
||||||
|
src_slot.children.push(dest);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Публичный метод отзыва прав.
|
|
||||||
/// Каскадно аннулирует все дочерние дескрипторы и отправляет их токены в lock-free очередь VMM.
|
|
||||||
pub fn revoke(&self, slot_idx: usize) -> Result<(), &'static str> {
|
pub fn revoke(&self, slot_idx: usize) -> Result<(), &'static str> {
|
||||||
if slot_idx >= self.slots.len() {
|
if slot_idx >= self.slots.len() {
|
||||||
return Err("Index out of bounds");
|
return Err("Index out of bounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запускаем рекурсивный отзыв
|
|
||||||
self.revoke_internal(slot_idx);
|
self.revoke_internal(slot_idx);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Внутренний метод каскадного удаления.
|
|
||||||
/// Вынесен отдельно, чтобы избежать удержания блокировок при переходе на следующий уровень рекурсии.
|
|
||||||
fn revoke_internal(&self, slot_idx: usize) {
|
fn revoke_internal(&self, slot_idx: usize) {
|
||||||
// 1. Сначала рекурсивно ищем и уничтожаем всех потомков данного слота
|
let children: Vec<usize> = {
|
||||||
for i in 0..self.slots.len() {
|
let slot = self.slots[slot_idx].lock();
|
||||||
let is_child = {
|
slot.children.clone()
|
||||||
let child = self.slots[i].lock();
|
|
||||||
child.parent_idx == Some(slot_idx)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_child {
|
for child in children {
|
||||||
// Рекурсивный спуск. Блокировка с child[i] к этому моменту уже снята, дедлока нет.
|
let is_valid_child = {
|
||||||
self.revoke_internal(i);
|
let c = self.slots[child].lock();
|
||||||
|
c.parent_idx == Some(slot_idx) && c.cap.is_valid()
|
||||||
|
};
|
||||||
|
if is_valid_child {
|
||||||
|
self.revoke_internal(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Теперь уничтожаем сам дескриптор в текущем слоте и отправляем его токен на отзыв в MMU
|
let (token_to_revoke, parent_idx) = {
|
||||||
let token_to_revoke = {
|
|
||||||
let mut slot = self.slots[slot_idx].lock();
|
let mut slot = self.slots[slot_idx].lock();
|
||||||
if slot.cap.is_valid() {
|
let parent = slot.parent_idx;
|
||||||
let token = slot.cap.token_sig;
|
let token = if slot.cap.is_valid() {
|
||||||
|
let t = slot.cap.token_sig;
|
||||||
// Перезаписываем пустой заглушкой (уничтожаем сильную ссылку ядра)
|
|
||||||
slot.cap = Capability::empty();
|
slot.cap = Capability::empty();
|
||||||
slot.parent_idx = None;
|
slot.parent_idx = None;
|
||||||
|
slot.children.clear();
|
||||||
Some(token)
|
Some(t)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
};
|
||||||
|
(token, parent)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Если слот содержал валидный токен, асинхронно уведомляем VMM актёра через lock-free кольцевой буфер
|
|
||||||
if let Some(token) = token_to_revoke {
|
if let Some(token) = token_to_revoke {
|
||||||
// В Elyz 0xDEAD_BEEF используется для сырого Untyped, его в MMU слать бессмысленно
|
|
||||||
if token != 0 && token != 0xDEAD_BEEF {
|
if token != 0 && token != 0xDEAD_BEEF {
|
||||||
if let Err(e) = MMU_REVOCATION_QUEUE.push(token) {
|
if let Err(e) = MMU_REVOCATION_QUEUE.push(token) {
|
||||||
// Переполнение очереди отзыва — критический сбой планировщика ресурсов ядра
|
|
||||||
panic!("FATAL: Критическое переполнение очереди отзыва MMU: {}", e);
|
panic!("FATAL: Критическое переполнение очереди отзыва MMU: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(parent) = parent_idx {
|
||||||
|
let mut parent_slot = self.slots[parent].lock();
|
||||||
|
parent_slot.children.retain(|&c| c != slot_idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cap(&self, slot: usize) -> Option<Capability> {
|
pub fn get_cap(&self, slot: usize) -> Option<Capability> {
|
||||||
|
|||||||
@@ -7,25 +7,29 @@ pub static mut IDT: crate::cpu::idt::InterruptDescriptorTable = crate::cpu::idt:
|
|||||||
|
|
||||||
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
||||||
|
|
||||||
global_asm!(
|
macro_rules! exception_stub {
|
||||||
".global page_fault_stub",
|
($name:ident, $handler:ident) => {
|
||||||
"page_fault_stub:",
|
concat!(
|
||||||
"push rax",
|
".global ", stringify!($name), "\n",
|
||||||
"push rcx",
|
stringify!($name), ":\n",
|
||||||
"push rdx",
|
"push rax\npush rcx\npush rdx\npush rbx\npush rbp\n",
|
||||||
"push rbx",
|
"push rsi\npush rdi\npush r8\npush r9\npush r10\n",
|
||||||
"push rbp",
|
"push r11\npush r12\npush r13\npush r14\npush r15\n",
|
||||||
"push rsi",
|
"mov rdi, [rsp + 15*8]\n",
|
||||||
"push rdi",
|
"call ", stringify!($handler), "\n",
|
||||||
"push r8",
|
"pop r15\npop r14\npop r13\npop r12\npop r11\n",
|
||||||
"push r9",
|
"pop r10\npop r9\npop r8\npop rdi\npop rsi\n",
|
||||||
"push r10",
|
"pop rbp\npop rbx\npop rdx\npop rcx\npop rax\n",
|
||||||
"push r11",
|
"add rsp, 8\n",
|
||||||
"push r12",
|
"iretq\n",
|
||||||
"push r13",
|
)
|
||||||
"push r14",
|
};
|
||||||
"push r15",
|
}
|
||||||
|
|
||||||
|
global_asm!(
|
||||||
|
exception_stub!(page_fault_stub, rust_page_fault_handler),
|
||||||
|
exception_stub!(gpf_stub, rust_gpf_handler),
|
||||||
|
exception_stub!(double_fault_stub, rust_double_fault_handler),
|
||||||
".global tlb_shootdown_stub",
|
".global tlb_shootdown_stub",
|
||||||
"tlb_shootdown_stub:",
|
"tlb_shootdown_stub:",
|
||||||
"push rax",
|
"push rax",
|
||||||
@@ -43,12 +47,7 @@ global_asm!(
|
|||||||
"push r13",
|
"push r13",
|
||||||
"push r14",
|
"push r14",
|
||||||
"push r15",
|
"push r15",
|
||||||
|
|
||||||
"call rust_tlb_shootdown_handler",
|
"call rust_tlb_shootdown_handler",
|
||||||
|
|
||||||
"mov rdi, [rsp + 15*8]",
|
|
||||||
"call rust_page_fault_handler",
|
|
||||||
|
|
||||||
"pop r15",
|
"pop r15",
|
||||||
"pop r14",
|
"pop r14",
|
||||||
"pop r13",
|
"pop r13",
|
||||||
@@ -64,36 +63,202 @@ global_asm!(
|
|||||||
"pop rdx",
|
"pop rdx",
|
||||||
"pop rcx",
|
"pop rcx",
|
||||||
"pop rax",
|
"pop rax",
|
||||||
|
|
||||||
"add rsp, 8",
|
|
||||||
"iretq"
|
"iretq"
|
||||||
);
|
);
|
||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn page_fault_stub();
|
fn page_fault_stub();
|
||||||
|
fn gpf_stub();
|
||||||
|
fn double_fault_stub();
|
||||||
fn tlb_shootdown_stub();
|
fn tlb_shootdown_stub();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_idt() {
|
pub fn init_early_exceptions() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let idt_mut_ptr = core::ptr::addr_of_mut!(IDT);
|
let idt = core::ptr::addr_of_mut!(IDT);
|
||||||
(*idt_mut_ptr).set_handler(14, page_fault_stub as u64);
|
let early: [u64; 32] = [
|
||||||
|
early_handler_0 as u64, early_handler_1 as u64,
|
||||||
|
early_handler_2 as u64, early_handler_3 as u64,
|
||||||
|
early_handler_4 as u64, early_handler_5 as u64,
|
||||||
|
early_handler_6 as u64, early_handler_7 as u64,
|
||||||
|
early_handler_8 as u64, early_handler_9 as u64,
|
||||||
|
early_handler_10 as u64, early_handler_11 as u64,
|
||||||
|
early_handler_12 as u64, early_handler_13 as u64,
|
||||||
|
early_handler_14 as u64, early_handler_15 as u64,
|
||||||
|
early_handler_16 as u64, early_handler_17 as u64,
|
||||||
|
early_handler_18 as u64, early_handler_19 as u64,
|
||||||
|
early_handler_20 as u64, early_handler_21 as u64,
|
||||||
|
early_handler_22 as u64, early_handler_23 as u64,
|
||||||
|
early_handler_24 as u64, early_handler_25 as u64,
|
||||||
|
early_handler_26 as u64, early_handler_27 as u64,
|
||||||
|
early_handler_28 as u64, early_handler_29 as u64,
|
||||||
|
early_handler_30 as u64, early_handler_31 as u64,
|
||||||
|
];
|
||||||
|
for (v, &handler) in early.iter().enumerate() {
|
||||||
|
(*idt).set_handler(v as u8, handler);
|
||||||
|
}
|
||||||
|
// Override with proper handlers for vectors 8, 13, 14 and TLB IPI.
|
||||||
|
(*idt).set_handler(8, double_fault_stub as u64);
|
||||||
|
(*idt).set_handler(13, gpf_stub as u64);
|
||||||
|
(*idt).set_handler(14, page_fault_stub as u64);
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
||||||
|
|
||||||
(*idt_mut_ptr).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
||||||
|
ptr.load();
|
||||||
let idt_static_ref: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
|
||||||
idt_static_ref.load();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_idt() {
|
||||||
pub fn process_deferred_mmu_events() {
|
// init_early_exceptions already loaded the IDT; this just overrides
|
||||||
let mut vmm_guard = KERNEL_SPACE.lock();
|
// vectors that the full kernel needs. Calling lidt again is harmless.
|
||||||
if let Some(space) = vmm_guard.as_mut() {
|
unsafe {
|
||||||
space.process_pending_revocations();
|
let idt = core::ptr::addr_of_mut!(IDT);
|
||||||
|
(*idt).set_handler(14, page_fault_stub as u64);
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
||||||
|
let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
||||||
|
ptr.load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_gpf_handler(error_code: u64) -> ! {
|
||||||
|
crate::debug::serial::write_global(format_args!(
|
||||||
|
"\n!!! GENERAL PROTECTION FAULT !!! error_code={:#x}\n\
|
||||||
|
CPU halted.\n",
|
||||||
|
error_code
|
||||||
|
));
|
||||||
|
loop {
|
||||||
|
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_double_fault_handler(error_code: u64) -> ! {
|
||||||
|
crate::debug::serial::write_global(format_args!(
|
||||||
|
"\n!!! DOUBLE FAULT !!! error_code={:#x}\n\
|
||||||
|
CPU halted.\n",
|
||||||
|
error_code
|
||||||
|
));
|
||||||
|
loop {
|
||||||
|
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_asm!(r#"
|
||||||
|
.altmacro
|
||||||
|
|
||||||
|
.macro early_stub vec
|
||||||
|
.globl early_handler_\vec
|
||||||
|
.balign 16
|
||||||
|
early_handler_\vec:
|
||||||
|
push 0 /* dummy error code */
|
||||||
|
push \vec /* vector number */
|
||||||
|
jmp early_common
|
||||||
|
.endm
|
||||||
|
|
||||||
|
early_stub 0
|
||||||
|
early_stub 1
|
||||||
|
early_stub 2
|
||||||
|
early_stub 3
|
||||||
|
early_stub 4
|
||||||
|
early_stub 5
|
||||||
|
early_stub 6
|
||||||
|
early_stub 7
|
||||||
|
early_stub 8
|
||||||
|
early_stub 9
|
||||||
|
early_stub 10
|
||||||
|
early_stub 11
|
||||||
|
early_stub 12
|
||||||
|
early_stub 13
|
||||||
|
early_stub 14
|
||||||
|
early_stub 15
|
||||||
|
early_stub 16
|
||||||
|
early_stub 17
|
||||||
|
early_stub 18
|
||||||
|
early_stub 19
|
||||||
|
early_stub 20
|
||||||
|
early_stub 21
|
||||||
|
early_stub 22
|
||||||
|
early_stub 23
|
||||||
|
early_stub 24
|
||||||
|
early_stub 25
|
||||||
|
early_stub 26
|
||||||
|
early_stub 27
|
||||||
|
early_stub 28
|
||||||
|
early_stub 29
|
||||||
|
early_stub 30
|
||||||
|
early_stub 31
|
||||||
|
|
||||||
|
early_common:
|
||||||
|
push rax
|
||||||
|
push rcx
|
||||||
|
push rdx
|
||||||
|
push rbx
|
||||||
|
push rbp
|
||||||
|
push rsi
|
||||||
|
push rdi
|
||||||
|
push r8
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
push r11
|
||||||
|
push r12
|
||||||
|
push r13
|
||||||
|
push r14
|
||||||
|
push r15
|
||||||
|
mov rdi, [rsp + 15*8] /* vector number */
|
||||||
|
mov rsi, [rsp + 16*8] /* error code (or dummy 0) */
|
||||||
|
call rust_early_exception_handler
|
||||||
|
/* never returns */
|
||||||
|
"#);
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn early_handler_0();
|
||||||
|
fn early_handler_1();
|
||||||
|
fn early_handler_2();
|
||||||
|
fn early_handler_3();
|
||||||
|
fn early_handler_4();
|
||||||
|
fn early_handler_5();
|
||||||
|
fn early_handler_6();
|
||||||
|
fn early_handler_7();
|
||||||
|
fn early_handler_8();
|
||||||
|
fn early_handler_9();
|
||||||
|
fn early_handler_10();
|
||||||
|
fn early_handler_11();
|
||||||
|
fn early_handler_12();
|
||||||
|
fn early_handler_13();
|
||||||
|
fn early_handler_14();
|
||||||
|
fn early_handler_15();
|
||||||
|
fn early_handler_16();
|
||||||
|
fn early_handler_17();
|
||||||
|
fn early_handler_18();
|
||||||
|
fn early_handler_19();
|
||||||
|
fn early_handler_20();
|
||||||
|
fn early_handler_21();
|
||||||
|
fn early_handler_22();
|
||||||
|
fn early_handler_23();
|
||||||
|
fn early_handler_24();
|
||||||
|
fn early_handler_25();
|
||||||
|
fn early_handler_26();
|
||||||
|
fn early_handler_27();
|
||||||
|
fn early_handler_28();
|
||||||
|
fn early_handler_29();
|
||||||
|
fn early_handler_30();
|
||||||
|
fn early_handler_31();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_early_exception_handler(vector: u64, _error_code: u64) -> ! {
|
||||||
|
crate::debug::serial::write_global(format_args!(
|
||||||
|
"\n!!! EARLY EXCEPTION !!! vector={} error_code={:#x}\n\
|
||||||
|
CPU halted.\n",
|
||||||
|
vector, _error_code
|
||||||
|
));
|
||||||
|
loop {
|
||||||
|
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
||||||
let fault_addr: u64;
|
let fault_addr: u64;
|
||||||
@@ -105,13 +270,18 @@ pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
|||||||
let present = (error_code & 0x1) != 0;
|
let present = (error_code & 0x1) != 0;
|
||||||
let virt_addr = VirtAddr(fault_addr);
|
let virt_addr = VirtAddr(fault_addr);
|
||||||
|
|
||||||
process_deferred_mmu_events();
|
// Acquire KERNEL_SPACE lock once for both deferred events and fault handling.
|
||||||
|
// TODO SMP: switch to RwLock so concurrent read-only faults are not
|
||||||
|
// serialised. `handle_fault` is read-only for COW faults but mutates
|
||||||
|
// the VMA tree on demand-paging — a lock-free VMA tree or per-region
|
||||||
|
// locks would be ideal.
|
||||||
let mut vmm_guard = KERNEL_SPACE.lock();
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
|
|
||||||
if let Some(space) = vmm_guard.as_mut() {
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
|
space.process_pending_revocations();
|
||||||
|
|
||||||
match space.handle_fault(virt_addr, write) {
|
match space.handle_fault(virt_addr, write) {
|
||||||
Ok(_) => return,
|
Ok(_) => {},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
panic!(
|
panic!(
|
||||||
"KERNEL PANIC: Unprocessed failure of virtual memory (Page Fault)!\n\
|
"KERNEL PANIC: Unprocessed failure of virtual memory (Page Fault)!\n\
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// src/cpu/lapic.rs
|
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU64, Ordering};
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use crate::mem::address::get_hhdm;
|
||||||
|
|
||||||
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
||||||
const LAPIC_EOI: u64 = 0x0B0;
|
const LAPIC_EOI: u64 = 0x0B0;
|
||||||
@@ -8,8 +7,8 @@ const LAPIC_ICR_LOW: u64 = 0x300;
|
|||||||
|
|
||||||
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
pub fn init(hhdm_offset: u64) {
|
pub fn init() {
|
||||||
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + hhdm_offset, Ordering::SeqCst);
|
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod idt;
|
pub mod idt;
|
||||||
pub mod interrupts;
|
pub mod interrupts;
|
||||||
pub mod lapic;
|
pub mod lapic;
|
||||||
|
pub mod smp;
|
||||||
|
|||||||
61
kernel/src/cpu/smp.rs
Normal file
61
kernel/src/cpu/smp.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use limine::mp;
|
||||||
|
use crate::mem::vmm::ACTIVE_CPUS_MASK;
|
||||||
|
|
||||||
|
static KERNEL_PML4: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn set_kernel_pml4(phys: u64) {
|
||||||
|
KERNEL_PML4.store(phys, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_aps(mp_res: &limine::response::MpResponse) {
|
||||||
|
let bsp_lapic = mp_res.bsp_lapic_id();
|
||||||
|
|
||||||
|
let mut ap_count = 0;
|
||||||
|
for cpu in mp_res.cpus() {
|
||||||
|
if cpu.lapic_id == bsp_lapic {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cpu.goto_address.write(ap_entry);
|
||||||
|
ap_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ap_count == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_mask: u64 = mp_res.cpus().iter()
|
||||||
|
.filter(|cpu| cpu.lapic_id != bsp_lapic && (cpu.lapic_id as u64) < 64)
|
||||||
|
.fold(0, |mask, cpu| mask | (1u64 << cpu.lapic_id));
|
||||||
|
|
||||||
|
while ACTIVE_CPUS_MASK.load(Ordering::Acquire) & target_mask != target_mask {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn ap_entry(cpu: &mp::Cpu) -> ! {
|
||||||
|
let pml4 = KERNEL_PML4.load(Ordering::Acquire);
|
||||||
|
if pml4 != 0 {
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!("mov cr3, {0}", in(reg) pml4,
|
||||||
|
options(nostack, preserves_flags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(*core::ptr::addr_of!(crate::cpu::interrupts::IDT)).load();
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::cpu::lapic::init();
|
||||||
|
|
||||||
|
let lapic_id = cpu.lapic_id;
|
||||||
|
if (lapic_id as u64) < 64 {
|
||||||
|
ACTIVE_CPUS_MASK.fetch_or(1u64 << lapic_id, Ordering::AcqRel);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { core::arch::asm!("sti", options(nomem, nostack, preserves_flags)); }
|
||||||
|
|
||||||
|
loop {
|
||||||
|
unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,15 +36,13 @@ macro_rules! log {
|
|||||||
let _ = writeln!($console, $($arg)*);
|
let _ = writeln!($console, $($arg)*);
|
||||||
|
|
||||||
// Serial debug output
|
// Serial debug output
|
||||||
let mut sp = unsafe { $crate::debug::serial::SerialPort::init() };
|
$crate::debug::serial::write_global(format_args!(
|
||||||
let _ = writeln!(
|
"{}[{:>5}]\x1b[0m {:<8} | {}\n",
|
||||||
sp,
|
|
||||||
"{}[{:>5}]\x1b[0m {:<8} | {}",
|
|
||||||
$level.serial_color_code(),
|
$level.serial_color_code(),
|
||||||
"LOG",
|
"LOG",
|
||||||
$module,
|
$module,
|
||||||
format_args!($($arg)*)
|
format_args!($($arg)*)
|
||||||
);
|
));
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
use core::fmt::Write;
|
||||||
|
use crate::mem::allocator::Locked;
|
||||||
|
|
||||||
pub struct SerialPort(u16);
|
pub struct SerialPort(u16);
|
||||||
|
|
||||||
@@ -7,16 +9,14 @@ impl SerialPort {
|
|||||||
|
|
||||||
pub unsafe fn init() -> Self {
|
pub unsafe fn init() -> Self {
|
||||||
let port = Self::COM1;
|
let port = Self::COM1;
|
||||||
// В новых версиях Rust даже внутри unsafe fn
|
|
||||||
// вызовы других unsafe функций требуют явного блока
|
|
||||||
unsafe {
|
unsafe {
|
||||||
outb(port + 1, 0x00); // Disable interrupts
|
|
||||||
outb(port + 3, 0x80); // Enable DLAB
|
|
||||||
outb(port + 0, 0x03); // Divisor 3 (38400 baud)
|
|
||||||
outb(port + 1, 0x00);
|
outb(port + 1, 0x00);
|
||||||
outb(port + 3, 0x03); // 8 bits, no parity, 1 stop bit
|
outb(port + 3, 0x80);
|
||||||
outb(port + 2, 0xC7); // Enable FIFO
|
outb(port + 0, 0x03);
|
||||||
outb(port + 4, 0x0B); // IRQs enabled, RTS/DSR set
|
outb(port + 1, 0x00);
|
||||||
|
outb(port + 3, 0x03);
|
||||||
|
outb(port + 2, 0xC7);
|
||||||
|
outb(port + 4, 0x0B);
|
||||||
}
|
}
|
||||||
SerialPort(port)
|
SerialPort(port)
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,20 @@ impl core::fmt::Write for SerialPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SERIAL_PORT: Locked<Option<SerialPort>> = Locked::new(None);
|
||||||
|
|
||||||
|
pub fn init_global() {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
*guard = Some(unsafe { SerialPort::init() });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_global(args: core::fmt::Arguments) {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
if let Some(ref mut sp) = *guard {
|
||||||
|
let _ = sp.write_fmt(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn outb(port: u16, val: u8) {
|
unsafe fn outb(port: u16, val: u8) {
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags));
|
asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags));
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ use core::fmt::{self, Write};
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use limine::BaseRevision;
|
use limine::BaseRevision;
|
||||||
use limine::request::{FramebufferRequest, RequestsEndMarker, RequestsStartMarker, HhdmRequest, MemoryMapRequest, ExecutableAddressRequest};
|
use limine::request::{FramebufferRequest, RequestsEndMarker, RequestsStartMarker, HhdmRequest, MemoryMapRequest, ExecutableAddressRequest, MpRequest};
|
||||||
|
|
||||||
use crate::mem::paging::{PageTable, PageTableFlags};
|
use crate::mem::paging::{PageTable, PageTableFlags};
|
||||||
use crate::mem::address::{PhysAddr, VirtAddr};
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
||||||
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
||||||
|
use crate::mem::buddy::BuddyAllocator;
|
||||||
|
|
||||||
pub mod cap;
|
pub mod cap;
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
@@ -160,6 +161,9 @@ static BASE_REVISION: BaseRevision = BaseRevision::new();
|
|||||||
#[used] #[unsafe(link_section = ".requests")]
|
#[used] #[unsafe(link_section = ".requests")]
|
||||||
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
|
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
|
||||||
|
|
||||||
|
#[used] #[unsafe(link_section = ".requests")]
|
||||||
|
static MP_REQUEST: MpRequest = MpRequest::new();
|
||||||
|
|
||||||
#[used] #[unsafe(link_section = ".requests_start_marker")]
|
#[used] #[unsafe(link_section = ".requests_start_marker")]
|
||||||
static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
|
static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
|
||||||
|
|
||||||
@@ -175,28 +179,42 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let hhdm_res = HHDM_REQUEST.get_response().expect("Limine: No HHDM");
|
let hhdm_res = HHDM_REQUEST.get_response().expect("Limine: No HHDM");
|
||||||
let kaddr_res = KERNEL_ADDR_REQUEST.get_response().expect("Limine: No Kernel Address");
|
let kaddr_res = KERNEL_ADDR_REQUEST.get_response().expect("Limine: No Kernel Address");
|
||||||
let hhdm_offset = hhdm_res.offset();
|
let hhdm_offset = hhdm_res.offset();
|
||||||
|
crate::mem::address::init_hhdm(hhdm_offset);
|
||||||
|
|
||||||
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
||||||
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||||
console.clear();
|
console.clear();
|
||||||
|
|
||||||
|
debug::serial::init_global();
|
||||||
|
|
||||||
|
cpu::interrupts::init_early_exceptions();
|
||||||
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
|
||||||
unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); }
|
unsafe { mem::pmm::BitmapPMM::init(&mmap_res); }
|
||||||
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
||||||
|
|
||||||
|
cpu::lapic::init();
|
||||||
|
info!(console, "LAPIC", "Local APIC initialized.");
|
||||||
|
|
||||||
|
// debug: locate the free page
|
||||||
|
info!(console, "BOOT", "alloc_frame...");
|
||||||
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
||||||
let p4 = unsafe { &mut *p4_phys.to_virt(hhdm_offset).as_mut_ptr::<PageTable>() };
|
info!(console, "BOOT", "alloc_frame ok: phys=0x{:x}", p4_phys.0);
|
||||||
|
let virt = p4_phys.to_virt();
|
||||||
|
info!(console, "BOOT", "virt=0x{:x}", virt.0);
|
||||||
|
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
|
||||||
|
info!(console, "BOOT", "zeroing page...");
|
||||||
unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); }
|
unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); }
|
||||||
|
info!(console, "BOOT", "zero done");
|
||||||
|
|
||||||
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
|
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
|
||||||
|
|
||||||
for entry in mmap_res.entries() {
|
for (i, entry) in mmap_res.entries().iter().enumerate() {
|
||||||
let phys = PhysAddr(entry.base);
|
let phys = PhysAddr(entry.base);
|
||||||
let virt_hhdm = phys.to_virt(hhdm_offset);
|
let virt_hhdm = phys.to_virt();
|
||||||
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
p4.map_region(virt_hhdm, phys, entry.length, flags);
|
||||||
if entry.entry_type != limine::memory_map::EntryType::RESERVED {
|
if entry.entry_type != limine::memory_map::EntryType::RESERVED {
|
||||||
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags, hhdm_offset);
|
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,17 +223,17 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
PhysAddr(kaddr_res.physical_base()),
|
PhysAddr(kaddr_res.physical_base()),
|
||||||
0x1000 * 1024,
|
0x1000 * 1024,
|
||||||
flags,
|
flags,
|
||||||
hhdm_offset
|
|
||||||
);
|
);
|
||||||
|
|
||||||
info!(console, "MMU", "Activating Kernel Page Tables...");
|
info!(console, "MMU", "Activating Kernel Page Tables...");
|
||||||
unsafe { p4.activate(p4_phys); }
|
unsafe { p4.activate(p4_phys); }
|
||||||
|
cpu::smp::set_kernel_pml4(p4_phys.0);
|
||||||
|
|
||||||
let heap_start = 0xFFFF_9000_0000_0000;
|
let heap_start = 0xFFFF_9000_0000_0000;
|
||||||
let heap_size = 8 * 1024 * 1024;
|
let heap_size = 8 * 1024 * 1024;
|
||||||
for i in (0..heap_size).step_by(4096) {
|
for i in (0..heap_size).step_by(4096) {
|
||||||
let frame = mem::pmm::alloc_frame().expect("OOM: Heap allocation failed");
|
let frame = mem::pmm::alloc_frame().expect("OOM: Heap allocation failed");
|
||||||
p4.map_page(VirtAddr(heap_start + i as u64), frame, flags, hhdm_offset);
|
p4.map_page(VirtAddr(heap_start + i as u64), frame, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -225,7 +243,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
||||||
|
|
||||||
mem::init_cpu_features();
|
mem::init_cpu_features();
|
||||||
mem::vmm::init_kernel_space(p4_phys, hhdm_offset);
|
mem::vmm::init_kernel_space(p4_phys);
|
||||||
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
||||||
|
|
||||||
cpu::interrupts::init_idt();
|
cpu::interrupts::init_idt();
|
||||||
@@ -236,6 +254,19 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
|
|
||||||
unsafe { core::arch::asm!("sti", options(nomem, nostack, preserves_flags)); }
|
unsafe { core::arch::asm!("sti", options(nomem, nostack, preserves_flags)); }
|
||||||
|
|
||||||
|
if let Some(mp_res) = MP_REQUEST.get_response() {
|
||||||
|
let cpu_ct = mp_res.cpus().len();
|
||||||
|
let bsp_id = mp_res.bsp_lapic_id();
|
||||||
|
info!(console, "SMP", "Detected {} CPU(s), BSP APIC ID: {}", cpu_ct, bsp_id);
|
||||||
|
if cpu_ct > 1 {
|
||||||
|
cpu::smp::start_aps(mp_res);
|
||||||
|
let active = crate::mem::vmm::ACTIVE_CPUS_MASK.load(core::sync::atomic::Ordering::Relaxed);
|
||||||
|
info!(console, "SMP", "All APs online. Active mask: {:#018x}", active);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!(console, "SMP", "No MP response — running single-core");
|
||||||
|
}
|
||||||
|
|
||||||
let root_cnode = cap::CNode::new(256);
|
let root_cnode = cap::CNode::new(256);
|
||||||
if let Some(frame) = mem::pmm::alloc_frame() {
|
if let Some(frame) = mem::pmm::alloc_frame() {
|
||||||
let mem_cap = Capability {
|
let mem_cap = Capability {
|
||||||
@@ -354,6 +385,72 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!(console, "PM", "=-= Buddy Allocator Direct Test (Intrusive List) =-=");
|
||||||
|
|
||||||
|
let buddy_test_pages = 128;
|
||||||
|
let buddy_test_base = PhysAddr(0x5000_0000);
|
||||||
|
let mut buddy = BuddyAllocator::new(buddy_test_pages, buddy_test_base.0);
|
||||||
|
|
||||||
|
let b1 = buddy.alloc(0).expect("buddy: alloc order 0");
|
||||||
|
let b2 = buddy.alloc(0).expect("buddy: alloc order 0 #2");
|
||||||
|
buddy.free(b1, 0);
|
||||||
|
buddy.free(b2, 0);
|
||||||
|
assert_eq!(buddy.free_pages(), buddy_test_pages, "buddy: pages not fully recovered after simple alloc/free");
|
||||||
|
info!(console, "PM", " [OK] Simple alloc/free");
|
||||||
|
|
||||||
|
let b3 = buddy.alloc(1).expect("buddy: alloc order 1");
|
||||||
|
assert_eq!(b3, 0, "buddy: first order 1 block at 0");
|
||||||
|
let b4 = buddy.alloc(1).expect("buddy: alloc order 1 #2");
|
||||||
|
assert_eq!(b4, 2, "buddy: second order 1 block at 2");
|
||||||
|
buddy.free(b3, 1);
|
||||||
|
buddy.free(b4, 1);
|
||||||
|
let b5 = buddy.alloc(2).expect("buddy: order 2 after coalescing");
|
||||||
|
assert_eq!(b5, 0, "buddy: coalesced block at 0");
|
||||||
|
buddy.free(b5, 2);
|
||||||
|
info!(console, "PM", " [OK] Coalescing across orders (O(1) intrusive list)");
|
||||||
|
|
||||||
|
let mut allocs = Vec::new();
|
||||||
|
loop {
|
||||||
|
match buddy.alloc(0) {
|
||||||
|
Some(idx) => allocs.push(idx),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(buddy.is_exhausted(), "buddy: should be exhausted");
|
||||||
|
info!(console, "PM", " [OK] Exhaustion after {} allocs", allocs.len());
|
||||||
|
|
||||||
|
for (i, idx) in allocs.iter().enumerate() {
|
||||||
|
buddy.free(*idx, 0);
|
||||||
|
}
|
||||||
|
assert_eq!(buddy.free_pages(), buddy_test_pages, "buddy: not fully recovered");
|
||||||
|
info!(console, "PM", " [OK] Full recovery after freeing {} pages", allocs.len());
|
||||||
|
|
||||||
|
info!(console, "PM", "~) BUDDY DIRECT TEST PASSED (~");
|
||||||
|
|
||||||
|
info!(console, "PM", "=-= PMM Tree Bitmap Alloc/Free Stress Test =-=");
|
||||||
|
|
||||||
|
const PMM_TEST_COUNT: usize = 64;
|
||||||
|
let mut frames = [PhysAddr(0); PMM_TEST_COUNT];
|
||||||
|
for i in 0..PMM_TEST_COUNT {
|
||||||
|
frames[i] = mem::pmm::alloc_frame().expect("PMM tree: OOM during alloc test");
|
||||||
|
}
|
||||||
|
info!(console, "PM", " [OK] Allocated {} frames", PMM_TEST_COUNT);
|
||||||
|
|
||||||
|
for f in &frames {
|
||||||
|
mem::pmm::free_frame(*f);
|
||||||
|
}
|
||||||
|
info!(console, "PM", " [OK] Freed {} frames back", PMM_TEST_COUNT);
|
||||||
|
|
||||||
|
for i in 0..PMM_TEST_COUNT {
|
||||||
|
frames[i] = mem::pmm::alloc_frame().expect("PMM tree: OOM after free cycle");
|
||||||
|
}
|
||||||
|
info!(console, "PM", " [OK] Re-allocated {} frames after free cycle", PMM_TEST_COUNT);
|
||||||
|
|
||||||
|
for f in &frames {
|
||||||
|
mem::pmm::free_frame(*f);
|
||||||
|
}
|
||||||
|
info!(console, "PM", "~) PMM TREE BITMAP TEST PASSED (~");
|
||||||
|
|
||||||
let logo = r#"
|
let logo = r#"
|
||||||
###########
|
###########
|
||||||
##################
|
##################
|
||||||
@@ -379,6 +476,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hcf() -> ! {
|
fn hcf() -> ! {
|
||||||
|
unsafe { asm!("cli", options(nomem, nostack, preserves_flags)); }
|
||||||
loop {
|
loop {
|
||||||
unsafe { asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
unsafe { asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
}
|
}
|
||||||
@@ -386,7 +484,6 @@ fn hcf() -> ! {
|
|||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn rust_panic(info: &core::panic::PanicInfo) -> ! {
|
fn rust_panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
let mut sp = unsafe { debug::serial::SerialPort::init() };
|
debug::serial::write_global(format_args!("KERNEL PANIC: {:?}\n", info));
|
||||||
let _ = writeln!(sp, "KERNEL PANIC: {:?}", info);
|
|
||||||
hcf();
|
hcf();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Store the Higher-Half Direct Map offset obtained from the bootloader.
|
||||||
|
/// Must be called once during early boot, before any address translation.
|
||||||
|
pub fn init_hhdm(offset: u64) {
|
||||||
|
HHDM_OFFSET.store(offset, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the HHDM offset (kernel virtual base for physical memory).
|
||||||
|
#[inline]
|
||||||
|
pub fn get_hhdm() -> u64 {
|
||||||
|
HHDM_OFFSET.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct PhysAddr(pub u64);
|
pub struct PhysAddr(pub u64);
|
||||||
@@ -8,8 +24,8 @@ pub struct VirtAddr(pub u64);
|
|||||||
|
|
||||||
impl PhysAddr {
|
impl PhysAddr {
|
||||||
/// Convert physical address to virtual via HHDM offset
|
/// Convert physical address to virtual via HHDM offset
|
||||||
pub fn to_virt(self, hhdm_offset: u64) -> VirtAddr {
|
pub fn to_virt(self) -> VirtAddr {
|
||||||
VirtAddr(self.0 + hhdm_offset)
|
VirtAddr(self.0 + get_hhdm())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -24,9 +40,10 @@ impl PhysAddr {
|
|||||||
|
|
||||||
impl VirtAddr {
|
impl VirtAddr {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn to_phys(self, hhdm_offset: u64) -> Option<PhysAddr> {
|
pub fn to_phys(self) -> Option<PhysAddr> {
|
||||||
if self.0 < hhdm_offset { return None; }
|
let hhdm = get_hhdm();
|
||||||
Some(PhysAddr(self.0 - hhdm_offset))
|
if self.0 < hhdm { return None; }
|
||||||
|
Some(PhysAddr(self.0 - hhdm))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -55,8 +55,14 @@ struct ListNode {
|
|||||||
|
|
||||||
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||||
|
|
||||||
|
struct LargeBlockNode {
|
||||||
|
size: usize,
|
||||||
|
next: Option<&'static mut LargeBlockNode>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SlabAllocator {
|
pub struct SlabAllocator {
|
||||||
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
|
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
|
||||||
|
large_block_free: Option<&'static mut LargeBlockNode>,
|
||||||
heap_start: usize,
|
heap_start: usize,
|
||||||
heap_end: usize,
|
heap_end: usize,
|
||||||
next_bump: usize,
|
next_bump: usize,
|
||||||
@@ -66,6 +72,7 @@ impl SlabAllocator {
|
|||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list_heads: [None, None, None, None, None, None, None, None, None],
|
list_heads: [None, None, None, None, None, None, None, None, None],
|
||||||
|
large_block_free: None,
|
||||||
heap_start: 0,
|
heap_start: 0,
|
||||||
heap_end: 0,
|
heap_end: 0,
|
||||||
next_bump: 0,
|
next_bump: 0,
|
||||||
@@ -84,11 +91,28 @@ impl SlabAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
||||||
|
let size = layout.size().max(layout.align());
|
||||||
|
|
||||||
|
if size > 2048 {
|
||||||
|
let mut field: *mut Option<&'static mut LargeBlockNode> = &mut self.large_block_free;
|
||||||
|
unsafe {
|
||||||
|
while let Some(ref mut node) = *field {
|
||||||
|
if node.size >= size {
|
||||||
|
let node_ptr: *mut LargeBlockNode = *node;
|
||||||
|
let next = node.next.take();
|
||||||
|
*field = next;
|
||||||
|
return node_ptr as *mut u8;
|
||||||
|
}
|
||||||
|
field = &mut node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
||||||
let alloc_end = alloc_start.checked_add(layout.size()).unwrap_or(self.heap_end + 1);
|
let alloc_end = alloc_start.checked_add(layout.size()).unwrap_or(self.heap_end + 1);
|
||||||
|
|
||||||
if alloc_end > self.heap_end {
|
if alloc_end > self.heap_end {
|
||||||
null_mut() // Out of memory
|
null_mut()
|
||||||
} else {
|
} else {
|
||||||
self.next_bump = alloc_end;
|
self.next_bump = alloc_end;
|
||||||
alloc_start as *mut u8
|
alloc_start as *mut u8
|
||||||
@@ -139,6 +163,17 @@ unsafe impl GlobalAlloc for Locked<SlabAllocator> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Large block: add to free list for reuse
|
||||||
|
let size = layout.size().max(layout.align());
|
||||||
|
let new_node = LargeBlockNode {
|
||||||
|
size,
|
||||||
|
next: allocator.large_block_free.take(),
|
||||||
|
};
|
||||||
|
let new_node_ptr = ptr as *mut LargeBlockNode;
|
||||||
|
unsafe {
|
||||||
|
new_node_ptr.write(new_node);
|
||||||
|
allocator.large_block_free = Some(&mut *new_node_ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +1,99 @@
|
|||||||
//! # Per-Actor Buddy Allocator (`src/mem/buddy.rs`)
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
//!
|
|
||||||
//! A power-of-two page-block allocator for a fixed, pre-committed physical
|
|
||||||
//! region. Designed for **single-consumer** use inside a [`crate::mem::pm_manages::PMActor`]:
|
|
||||||
//! the owning actor is the only entity that ever mutates its buddy allocator, so
|
|
||||||
//! no locking is needed — the entire subsystem is inherently lock-free from the
|
|
||||||
//! consumer's perspective.
|
|
||||||
//!
|
|
||||||
//! ## Block Layout
|
|
||||||
//!
|
|
||||||
//! The region is subdivided into blocks of `2^order` pages (order 0 … MAX_ORDER).
|
|
||||||
//! Every block at order *k* is aligned to `2^k` pages within the region.
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! order 0 → 1 page = 4 KiB
|
|
||||||
//! order 1 → 2 pages = 8 KiB
|
|
||||||
//! …
|
|
||||||
//! order 11 → 2 048 pages = 8 MiB (MAX_ORDER)
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## Complexity
|
|
||||||
//!
|
|
||||||
//! | Operation | Amortised | Worst-case |
|
|
||||||
//! |-----------|-----------|------------|
|
|
||||||
//! | `alloc` | O(log N) | O(MAX_ORDER · Lₖ) |
|
|
||||||
//! | `free` | O(log N) | O(MAX_ORDER · Lₖ) |
|
|
||||||
//!
|
|
||||||
//! where Lₖ = `free_lists[k].len()` ≤ `total_pages / 2^k`.
|
|
||||||
//!
|
|
||||||
//! ## Production note
|
|
||||||
//!
|
|
||||||
//! For managed ranges > 1 GiB (> 256 K pages), replace the per-order `Vec<usize>`
|
|
||||||
//! free lists with a radix tree or interval tree to bound Lₖ. For current
|
|
||||||
//! PMActor capital sizes (typically ≤ 128 MiB = 32 K pages at order 0), the
|
|
||||||
//! `Vec`-based implementation is fully adequate.
|
|
||||||
|
|
||||||
extern crate alloc;
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
|
|
||||||
// Constants & helpers
|
|
||||||
/// Maximum allocation order.
|
|
||||||
/// `2^11 × 4 096 bytes = 8 MiB` per single allocation.
|
|
||||||
pub const MAX_ORDER: usize = 11;
|
pub const MAX_ORDER: usize = 11;
|
||||||
|
|
||||||
/// ⌈log2(n)⌉ — the minimum order whose block size covers `page_count` pages.
|
const NEXT_SENTINEL: usize = usize::MAX;
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// order_for(1) = 0 (2^0 = 1)
|
|
||||||
/// order_for(2) = 1 (2^1 = 2)
|
|
||||||
/// order_for(3) = 2 (2^2 = 4 ≥ 3)
|
|
||||||
/// order_for(2048) = 11
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn order_for(page_count: usize) -> usize {
|
pub fn order_for(page_count: usize) -> usize {
|
||||||
if page_count <= 1 {
|
if page_count <= 1 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
// Number of bits needed to represent (page_count - 1).
|
|
||||||
usize::BITS as usize - (page_count - 1).leading_zeros() as usize
|
usize::BITS as usize - (page_count - 1).leading_zeros() as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuddyAllocator
|
|
||||||
/// Buddy allocator over a contiguous, pre-committed range of physical pages.
|
|
||||||
///
|
|
||||||
/// All page indices stored in free lists are **relative to the start of the
|
|
||||||
/// managed range**. The owner (`PMActor`) translates to absolute `PhysAddr`
|
|
||||||
/// by adding `managed_range.0`.
|
|
||||||
///
|
|
||||||
/// # Invariants
|
|
||||||
/// - `free_pages ≤ total_pages` at all times.
|
|
||||||
/// - Every block in `free_lists[k]` is aligned to `2^k` pages (i.e.
|
|
||||||
/// `idx % (1 << k) == 0`).
|
|
||||||
/// - No block appears in more than one order's free list simultaneously.
|
|
||||||
pub struct BuddyAllocator {
|
pub struct BuddyAllocator {
|
||||||
/// `free_lists[k]` = relative page indices of free 2^k-page blocks.
|
free_heads: [usize; MAX_ORDER + 1],
|
||||||
/// Ordering within each list is irrelevant; `pop()` / `swap_remove()` are used.
|
|
||||||
free_lists: [Vec<usize>; MAX_ORDER + 1],
|
|
||||||
/// Total pages in the managed range (need not be a power of two).
|
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
/// Running count of free pages. Always equals `Σ (2^k × free_lists[k].len())`.
|
|
||||||
free_pages: usize,
|
free_pages: usize,
|
||||||
|
base_phys: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuddyAllocator {
|
impl BuddyAllocator {
|
||||||
//Construction
|
#[inline]
|
||||||
/// Create a new allocator over `total_pages` pages, **all initially free**.
|
fn page_virt(&self, idx: usize) -> VirtAddr {
|
||||||
///
|
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
|
||||||
/// Uses a greedy largest-first decomposition to build the initial free lists
|
}
|
||||||
/// in O(total_pages / 2^MAX_ORDER) iterations — essentially O(1) for
|
|
||||||
/// power-of-two sizes.
|
|
||||||
///
|
|
||||||
/// Example: 7 pages → blocks [4, 2, 1] → free_lists[2]=[0], [1]=[4], [0]=[6].
|
|
||||||
pub fn new(total_pages: usize) -> Self {
|
|
||||||
// core::array::from_fn is the idiomatic way to init a non-Copy array.
|
|
||||||
let free_lists: [Vec<usize>; MAX_ORDER + 1] =
|
|
||||||
core::array::from_fn(|_| Vec::new());
|
|
||||||
|
|
||||||
|
unsafe fn write_node(&self, idx: usize, next: usize, prev: usize) {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.page_virt(idx).as_mut_ptr::<usize>();
|
||||||
|
ptr.write(next);
|
||||||
|
ptr.add(1).write(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_next(&self, idx: usize) -> usize {
|
||||||
|
unsafe { self.page_virt(idx).as_ptr::<usize>().read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_prev(&self, idx: usize) -> usize {
|
||||||
|
unsafe { self.page_virt(idx).as_ptr::<usize>().add(1).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_push(&mut self, order: usize, idx: usize) {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
unsafe {
|
||||||
|
self.write_node(idx, head, NEXT_SENTINEL);
|
||||||
|
if head != NEXT_SENTINEL {
|
||||||
|
self.write_node(head, self.read_next(head), idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.free_heads[order] = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_remove(&mut self, order: usize, idx: usize) {
|
||||||
|
unsafe {
|
||||||
|
let next = self.read_next(idx);
|
||||||
|
let prev = self.read_prev(idx);
|
||||||
|
|
||||||
|
self.write_node(idx, NEXT_SENTINEL, NEXT_SENTINEL);
|
||||||
|
|
||||||
|
if prev != NEXT_SENTINEL {
|
||||||
|
self.write_node(prev, next, self.read_prev(prev));
|
||||||
|
} else {
|
||||||
|
self.free_heads[order] = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if next != NEXT_SENTINEL {
|
||||||
|
self.write_node(next, self.read_next(next), prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_pop(&mut self, order: usize) -> Option<usize> {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
if head == NEXT_SENTINEL {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.flist_remove(order, head);
|
||||||
|
Some(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_contains(&self, order: usize, idx: usize) -> bool {
|
||||||
|
if self.free_heads[order] == idx {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
unsafe { self.read_prev(idx) != NEXT_SENTINEL }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(total_pages: usize, base_phys: u64) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
free_lists,
|
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
|
||||||
total_pages,
|
total_pages,
|
||||||
free_pages: 0,
|
free_pages: 0,
|
||||||
|
base_phys,
|
||||||
};
|
};
|
||||||
|
|
||||||
if total_pages == 0 {
|
if total_pages == 0 {
|
||||||
@@ -110,22 +104,19 @@ impl BuddyAllocator {
|
|||||||
while idx < total_pages {
|
while idx < total_pages {
|
||||||
let remaining = total_pages - idx;
|
let remaining = total_pages - idx;
|
||||||
|
|
||||||
// Alignment constraint: block at `idx` must be aligned to 2^order.
|
|
||||||
// trailing_zeros(0) is u32::MAX, so we clamp to MAX_ORDER.
|
|
||||||
let align_order = if idx == 0 {
|
let align_order = if idx == 0 {
|
||||||
MAX_ORDER
|
MAX_ORDER
|
||||||
} else {
|
} else {
|
||||||
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Size constraint: 2^order ≤ remaining → order ≤ ⌊log₂(remaining)⌋.
|
|
||||||
let size_order = (usize::BITS as usize - 1)
|
let size_order = (usize::BITS as usize - 1)
|
||||||
- remaining.leading_zeros() as usize; // ⌊log2(remaining)⌋
|
- remaining.leading_zeros() as usize;
|
||||||
|
|
||||||
let order = MAX_ORDER.min(align_order).min(size_order);
|
let order = MAX_ORDER.min(align_order).min(size_order);
|
||||||
let block_size = 1usize << order;
|
let block_size = 1usize << order;
|
||||||
|
|
||||||
this.free_lists[order].push(idx);
|
this.flist_push(order, idx);
|
||||||
this.free_pages += block_size;
|
this.free_pages += block_size;
|
||||||
idx += block_size;
|
idx += block_size;
|
||||||
}
|
}
|
||||||
@@ -133,60 +124,30 @@ impl BuddyAllocator {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
//Allocation
|
|
||||||
/// Allocate a 2^`order`-page block.
|
|
||||||
///
|
|
||||||
/// Returns the **relative** page index of the block's first page, or `None`
|
|
||||||
/// if insufficient contiguous memory remains.
|
|
||||||
///
|
|
||||||
/// The returned index can be converted to a physical address:
|
|
||||||
/// ```text
|
|
||||||
/// phys = actor.managed_range.0.0 + (idx as u64) * PAGE_SIZE
|
|
||||||
/// ```
|
|
||||||
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
||||||
if order > MAX_ORDER {
|
if order > MAX_ORDER {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the smallest available order ≥ requested.
|
|
||||||
let found_order = (order..=MAX_ORDER)
|
let found_order = (order..=MAX_ORDER)
|
||||||
.find(|&o| !self.free_lists[o].is_empty())?;
|
.find(|&o| self.free_heads[o] != NEXT_SENTINEL)?;
|
||||||
|
|
||||||
// Consume one block from the found order.
|
let block_idx = self.flist_pop(found_order)
|
||||||
let block_idx = self.free_lists[found_order]
|
.expect("buddy: free_list non-empty but flist_pop returned None");
|
||||||
.pop()
|
|
||||||
.expect("buddy: free_list non-empty but pop() returned None");
|
|
||||||
|
|
||||||
// Debit: we removed a 2^found_order block from free.
|
|
||||||
self.free_pages -= 1 << found_order;
|
self.free_pages -= 1 << found_order;
|
||||||
|
|
||||||
// Split down to the requested order, putting buddies back into free lists.
|
|
||||||
//
|
|
||||||
// Invariant at each iteration:
|
|
||||||
// `block_idx` is the lower half of a 2^cur_order block.
|
|
||||||
// The upper half (= `block_idx + 2^(cur_order-1)`) is returned to the
|
|
||||||
// free list as an order-(cur_order-1) block.
|
|
||||||
let mut cur_order = found_order;
|
let mut cur_order = found_order;
|
||||||
while cur_order > order {
|
while cur_order > order {
|
||||||
cur_order -= 1;
|
cur_order -= 1;
|
||||||
let buddy_idx = block_idx + (1 << cur_order);
|
let buddy_idx = block_idx + (1 << cur_order);
|
||||||
self.free_lists[cur_order].push(buddy_idx);
|
self.flist_push(cur_order, buddy_idx);
|
||||||
self.free_pages += 1 << cur_order;
|
self.free_pages += 1 << cur_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Net accounting: removed 2^found_order, added (2^found_order − 2^order).
|
|
||||||
// Result: free_pages decreased by exactly 2^order. ✓
|
|
||||||
Some(block_idx)
|
Some(block_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate `page_count` pages, rounding up to the nearest power of two.
|
|
||||||
///
|
|
||||||
/// Returns `(relative_page_idx, actual_order)`. The caller **must** pass the
|
|
||||||
/// same `order` to [`BuddyAllocator::free`] — mismatched orders corrupt the
|
|
||||||
/// allocator.
|
|
||||||
///
|
|
||||||
/// The wasted "rounding up" pages remain unusable until the block is freed.
|
|
||||||
/// For tightly-packed allocations, callers should use `alloc(order)` directly.
|
|
||||||
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
||||||
if page_count == 0 {
|
if page_count == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -198,17 +159,6 @@ impl BuddyAllocator {
|
|||||||
self.alloc(order).map(|idx| (idx, order))
|
self.alloc(order).map(|idx| (idx, order))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Deallocation
|
|
||||||
|
|
||||||
/// Return a 2^`order`-page block at **relative** index `block_idx` to the
|
|
||||||
/// free pool, coalescing with free buddies up the order chain.
|
|
||||||
///
|
|
||||||
/// # Panics (debug builds only)
|
|
||||||
/// - `order > MAX_ORDER`
|
|
||||||
/// - `block_idx + 2^order > total_pages`
|
|
||||||
///
|
|
||||||
/// In release builds the checks are elided for performance; passing incorrect
|
|
||||||
/// arguments causes undefined bookkeeping, not UB (no unsafe indexing).
|
|
||||||
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
order <= MAX_ORDER,
|
order <= MAX_ORDER,
|
||||||
@@ -224,15 +174,9 @@ impl BuddyAllocator {
|
|||||||
self.total_pages
|
self.total_pages
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to coalesce with our buddy at each order.
|
|
||||||
//
|
|
||||||
// The buddy of a block at relative index `i` of order `k` is at:
|
|
||||||
// buddy_idx = i XOR 2^k
|
|
||||||
// This works because aligned buddy pairs always differ in exactly bit k.
|
|
||||||
while order < MAX_ORDER {
|
while order < MAX_ORDER {
|
||||||
let buddy_idx = block_idx ^ (1 << order);
|
let buddy_idx = block_idx ^ (1 << order);
|
||||||
|
|
||||||
// Buddy must lie entirely within the managed range.
|
|
||||||
let buddy_end = match buddy_idx.checked_add(1 << order) {
|
let buddy_end = match buddy_idx.checked_add(1 << order) {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => break,
|
None => break,
|
||||||
@@ -241,50 +185,49 @@ impl BuddyAllocator {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for the buddy in the free list for this order.
|
if self.flist_contains(order, buddy_idx) {
|
||||||
// `swap_remove` is O(1) and order-preserving is not required.
|
self.flist_remove(order, buddy_idx);
|
||||||
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
|
self.free_pages -= 1 << order;
|
||||||
self.free_lists[order].swap_remove(pos);
|
|
||||||
self.free_pages -= 1 << order; // buddy was in free count; remove it
|
|
||||||
|
|
||||||
// Merged block starts at the lower address of the two.
|
|
||||||
block_idx = block_idx.min(buddy_idx);
|
block_idx = block_idx.min(buddy_idx);
|
||||||
order += 1;
|
order += 1;
|
||||||
// Continue trying to merge at the next level.
|
|
||||||
} else {
|
} else {
|
||||||
break; // buddy is allocated or out-of-range; stop coalescing
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the (possibly merged) block onto the appropriate free list.
|
|
||||||
self.free_pages += 1 << order;
|
self.free_pages += 1 << order;
|
||||||
self.free_lists[order].push(block_idx);
|
self.flist_push(order, block_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Introspection
|
|
||||||
|
|
||||||
/// Number of pages currently available for allocation.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn free_pages(&self) -> usize {
|
pub fn free_pages(&self) -> usize {
|
||||||
self.free_pages
|
self.free_pages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total pages in the managed range.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn total_pages(&self) -> usize {
|
pub fn total_pages(&self) -> usize {
|
||||||
self.total_pages
|
self.total_pages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `true` if the allocator has no free pages.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_exhausted(&self) -> bool {
|
pub fn is_exhausted(&self) -> bool {
|
||||||
self.free_pages == 0
|
self.free_pages == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dump free-list statistics for each order (useful in panic handlers).
|
|
||||||
///
|
|
||||||
/// Returns an array `[(order, free_block_count); MAX_ORDER + 1]`.
|
|
||||||
pub fn stats(&self) -> [(usize, usize); MAX_ORDER + 1] {
|
pub fn stats(&self) -> [(usize, usize); MAX_ORDER + 1] {
|
||||||
core::array::from_fn(|o| (o, self.free_lists[o].len()))
|
core::array::from_fn(|o| {
|
||||||
|
let count = if self.free_heads[o] == NEXT_SENTINEL {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let mut cnt = 0;
|
||||||
|
let mut cur = self.free_heads[o];
|
||||||
|
while cur != NEXT_SENTINEL {
|
||||||
|
cnt += 1;
|
||||||
|
cur = unsafe { self.read_next(cur) };
|
||||||
|
}
|
||||||
|
cnt
|
||||||
|
};
|
||||||
|
(o, count)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//! `src/mem/mod.rs` — memory subsystem root
|
|
||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
pub mod allocator;
|
pub mod allocator;
|
||||||
pub mod buddy;
|
pub mod buddy;
|
||||||
@@ -9,27 +7,18 @@ pub mod pmm;
|
|||||||
pub mod vmm;
|
pub mod vmm;
|
||||||
pub mod pm_router;
|
pub mod pm_router;
|
||||||
|
|
||||||
/// Initialise the physical memory manager.
|
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse) {
|
||||||
///
|
unsafe { pmm::BitmapPMM::init(memmap); }
|
||||||
/// Must be called before any heap allocation or VMM operation.
|
|
||||||
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
|
||||||
unsafe { pmm::BitmapPMM::init(memmap, hhdm_offset); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialise CPU features required by the VMM (INVPCID detection).
|
|
||||||
///
|
|
||||||
/// Must be called before `init_vmm` and any `AddressSpace::activate()`.
|
|
||||||
pub fn init_cpu_features() {
|
pub fn init_cpu_features() {
|
||||||
vmm::init_cpu_features();
|
vmm::init_cpu_features();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialise the kernel address space record (after PMM, heap, and the
|
pub fn init_vmm(pml4_phys: address::PhysAddr) {
|
||||||
/// initial P4 page table have been set up in `kmain`).
|
vmm::init_kernel_space(pml4_phys);
|
||||||
pub fn init_vmm(pml4_phys: address::PhysAddr, hhdm_offset: u64) {
|
|
||||||
vmm::init_kernel_space(pml4_phys, hhdm_offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Physical memory statistics: `(used_pages, total_pages)`.
|
|
||||||
pub fn get_stats() -> (usize, usize) {
|
pub fn get_stats() -> (usize, usize) {
|
||||||
pmm::get_stats()
|
pmm::get_stats()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,16 +40,15 @@ impl PageTable {
|
|||||||
phys: PhysAddr,
|
phys: PhysAddr,
|
||||||
size: u64,
|
size: u64,
|
||||||
flags: PageTableFlags,
|
flags: PageTableFlags,
|
||||||
hhdm: u64,
|
|
||||||
) {
|
) {
|
||||||
let pages = size.div_ceil(4096);
|
let pages = size.div_ceil(4096);
|
||||||
for i in 0..pages {
|
for i in 0..pages {
|
||||||
let offset = i * 4096;
|
let offset = i * 4096;
|
||||||
self.map_page(VirtAddr(virt.0 + offset), PhysAddr(phys.0 + offset), flags, hhdm);
|
self.map_page(VirtAddr(virt.0 + offset), PhysAddr(phys.0 + offset), flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_flags(&self, virt: VirtAddr, hhdm: u64) -> Option<PageTableFlags> {
|
pub fn get_flags(&self, virt: VirtAddr) -> Option<PageTableFlags> {
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
@@ -59,7 +58,7 @@ impl PageTable {
|
|||||||
($entry:expr) => {{
|
($entry:expr) => {{
|
||||||
let e = $entry;
|
let e = $entry;
|
||||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt().as_mut_ptr::<PageTable>() }
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +81,8 @@ impl PageTable {
|
|||||||
Some(PageTableFlags::from_bits_truncate(p1e))
|
Some(PageTableFlags::from_bits_truncate(p1e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags, hhdm: u64) -> Result<(), ()> {
|
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags) -> Result<(), ()> {
|
||||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false) else { return Err(()); };
|
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return Err(()); };
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
let entry = p1.entries[p1_idx];
|
let entry = p1.entries[p1_idx];
|
||||||
|
|
||||||
@@ -107,10 +106,9 @@ impl PageTable {
|
|||||||
virt: VirtAddr,
|
virt: VirtAddr,
|
||||||
phys: PhysAddr,
|
phys: PhysAddr,
|
||||||
flags: PageTableFlags,
|
flags: PageTableFlags,
|
||||||
hhdm: u64,
|
|
||||||
) {
|
) {
|
||||||
let p1 = self
|
let p1 = self
|
||||||
.walk_to_p1_mut(virt, hhdm, true /* create */)
|
.walk_to_p1_mut(virt, true /* create */)
|
||||||
.expect("map_page: OOM allocating intermediate page-table pages");
|
.expect("map_page: OOM allocating intermediate page-table pages");
|
||||||
|
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
@@ -126,8 +124,8 @@ impl PageTable {
|
|||||||
/// Does **not** free the underlying physical frame — that is the caller's
|
/// Does **not** free the underlying physical frame — that is the caller's
|
||||||
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
||||||
/// Silently returns if any level is not present (idempotent).
|
/// Silently returns if any level is not present (idempotent).
|
||||||
pub fn unmap_page(&mut self, virt: VirtAddr, hhdm: u64) {
|
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false /* no create */) else {
|
let Some(p1) = self.walk_to_p1_mut(virt, false /* no create */) else {
|
||||||
return; // already absent — nothing to do
|
return; // already absent — nothing to do
|
||||||
};
|
};
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
@@ -144,7 +142,7 @@ impl PageTable {
|
|||||||
///
|
///
|
||||||
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
||||||
/// Returns `None` if any level is absent or the page is not present.
|
/// Returns `None` if any level is absent or the page is not present.
|
||||||
pub fn translate(&self, virt: VirtAddr, hhdm: u64) -> Option<PhysAddr> {
|
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
@@ -155,7 +153,7 @@ impl PageTable {
|
|||||||
($entry:expr) => {{
|
($entry:expr) => {{
|
||||||
let e = $entry;
|
let e = $entry;
|
||||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt().as_mut_ptr::<PageTable>() }
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,16 +207,16 @@ impl PageTable {
|
|||||||
///
|
///
|
||||||
/// If `create` is `false` and any intermediate entry is absent, returns `None`.
|
/// If `create` is `false` and any intermediate entry is absent, returns `None`.
|
||||||
/// If `create` is `true`, allocates missing intermediate pages from the PMM.
|
/// If `create` is `true`, allocates missing intermediate pages from the PMM.
|
||||||
fn walk_to_p1_mut(&mut self, virt: VirtAddr, hhdm: u64, create: bool)
|
fn walk_to_p1_mut(&mut self, virt: VirtAddr, create: bool)
|
||||||
-> Option<&mut Self>
|
-> Option<&mut Self>
|
||||||
{
|
{
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
|
|
||||||
let p3 = self.get_or_create_next_table(p4_idx, hhdm, create)?;
|
let p3 = self.get_or_create_next_table(p4_idx, create)?;
|
||||||
let p2 = p3.get_or_create_next_table(p3_idx, hhdm, create)?;
|
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
|
||||||
p2.get_or_create_next_table(p2_idx, hhdm, create)
|
p2.get_or_create_next_table(p2_idx, create)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the next-level table at `index`.
|
/// Return a mutable reference to the next-level table at `index`.
|
||||||
@@ -229,7 +227,6 @@ impl PageTable {
|
|||||||
fn get_or_create_next_table(
|
fn get_or_create_next_table(
|
||||||
&mut self,
|
&mut self,
|
||||||
index: usize,
|
index: usize,
|
||||||
hhdm: u64,
|
|
||||||
create: bool,
|
create: bool,
|
||||||
) -> Option<&mut Self> {
|
) -> Option<&mut Self> {
|
||||||
let entry = self.entries[index];
|
let entry = self.entries[index];
|
||||||
@@ -237,7 +234,7 @@ impl PageTable {
|
|||||||
if !create { return None; }
|
if !create { return None; }
|
||||||
|
|
||||||
let pt_phys = pmm_alloc().expect("VMM: OOM allocating page-table page");
|
let pt_phys = pmm_alloc().expect("VMM: OOM allocating page-table page");
|
||||||
let pt_virt = pt_phys.to_virt(hhdm);
|
let pt_virt = pt_phys.to_virt();
|
||||||
unsafe { core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096); }
|
unsafe { core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096); }
|
||||||
|
|
||||||
// Install with USER so both kernel and user pages can live under it;
|
// Install with USER so both kernel and user pages can live under it;
|
||||||
@@ -247,7 +244,7 @@ impl PageTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
||||||
Some(unsafe { &mut *next_phys.to_virt(hhdm).as_mut_ptr::<Self>() })
|
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//! # PM Actor (`src/mem/pm_manages.rs`)
|
|
||||||
//!
|
//!
|
||||||
//! Distributed physical-memory actor that owns a fixed physical capital range
|
//! Distributed physical-memory actor that owns a fixed physical capital range
|
||||||
//! and processes allocation requests asynchronously via a lock-free MPSC queue.
|
//! and processes allocation requests asynchronously via a lock-free MPSC queue.
|
||||||
@@ -94,7 +93,6 @@ pub enum PMRequest {
|
|||||||
size_pages: usize,
|
size_pages: usize,
|
||||||
channel_id: u16,
|
channel_id: u16,
|
||||||
},
|
},
|
||||||
/// Sentinel — never pushed onto the queue; result of unpack(0).
|
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,30 +158,18 @@ impl PMRequest {
|
|||||||
|
|
||||||
// Response type
|
// Response type
|
||||||
|
|
||||||
/// Result returned by `PMActor::process_messages()` for each completed request.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PMResponse {
|
pub struct PMResponse {
|
||||||
/// Channel to route this response to. `0` = discard.
|
|
||||||
pub channel_id: u16,
|
pub channel_id: u16,
|
||||||
/// The actual outcome.
|
|
||||||
pub result: PMResult,
|
pub result: PMResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outcome of a single PM operation.
|
/// Outcome of a single PM operation.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum PMResult {
|
pub enum PMResult {
|
||||||
/// Memory was allocated. `cap` is the strong capability to the region.
|
|
||||||
/// `order` is the buddy order — **must** be passed back to `Free`.
|
|
||||||
Allocated { cap: Capability, order: usize },
|
Allocated { cap: Capability, order: usize },
|
||||||
|
|
||||||
/// The actor had insufficient free pages.
|
|
||||||
/// Future: ballooning subsystem intercepts this and retries.
|
|
||||||
OutOfMemory { size_pages: usize },
|
OutOfMemory { size_pages: usize },
|
||||||
|
|
||||||
/// A fixed sub-region was carved successfully.
|
|
||||||
Carved { cap: Capability },
|
Carved { cap: Capability },
|
||||||
|
|
||||||
/// Free completed (no capability issued — memory returned to buddy pool).
|
|
||||||
Freed { pages_returned: usize },
|
Freed { pages_returned: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +180,6 @@ pub enum PMResult {
|
|||||||
/// `pop` — exactly one consumer, no locking on the read side.
|
/// `pop` — exactly one consumer, no locking on the read side.
|
||||||
pub struct PMActorQueue {
|
pub struct PMActorQueue {
|
||||||
buffer: [AtomicU64; QUEUE_SIZE],
|
buffer: [AtomicU64; QUEUE_SIZE],
|
||||||
/// Pad to separate producer-written `tail` from consumer-read `head`.
|
|
||||||
_pad0: [u8; 64],
|
_pad0: [u8; 64],
|
||||||
head: AtomicUsize,
|
head: AtomicUsize,
|
||||||
_pad1: [u8; 64],
|
_pad1: [u8; 64],
|
||||||
@@ -269,15 +254,10 @@ impl PMActorQueue {
|
|||||||
/// Only `process_messages()` ever mutates `buddy` and `queue.head`.
|
/// Only `process_messages()` ever mutates `buddy` and `queue.head`.
|
||||||
/// This is enforced by taking `&mut self` on `process_messages`.
|
/// This is enforced by taking `&mut self` on `process_messages`.
|
||||||
pub struct PMActor {
|
pub struct PMActor {
|
||||||
/// Unique identity within the actor federation.
|
|
||||||
pub actor_id: u64,
|
pub actor_id: u64,
|
||||||
/// Root strong capability over the entire managed physical range.
|
|
||||||
pub root_untyped: Capability,
|
pub root_untyped: Capability,
|
||||||
/// `(inclusive_start, exclusive_end)` physical addresses.
|
|
||||||
pub managed_range: (PhysAddr, PhysAddr),
|
pub managed_range: (PhysAddr, PhysAddr),
|
||||||
/// Inbox — producers write here, actor reads here.
|
|
||||||
queue: PMActorQueue,
|
queue: PMActorQueue,
|
||||||
/// Local buddy allocator. Only ever touched in `process_messages`.
|
|
||||||
buddy: BuddyAllocator,
|
buddy: BuddyAllocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +278,7 @@ impl PMActor {
|
|||||||
root_untyped: root_cap,
|
root_untyped: root_cap,
|
||||||
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
||||||
queue: PMActorQueue::new(),
|
queue: PMActorQueue::new(),
|
||||||
buddy: BuddyAllocator::new(total_pages),
|
buddy: BuddyAllocator::new(total_pages, start.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,10 +305,16 @@ impl PMActor {
|
|||||||
/// `buddy` and `queue.head` are mutated — no other thread touches them.
|
/// `buddy` and `queue.head` are mutated — no other thread touches them.
|
||||||
/// The only shared state is the queue's `tail`, which is written by producers
|
/// The only shared state is the queue's `tail`, which is written by producers
|
||||||
/// via `AtomicUsize::compare_exchange_weak`, never by this path.
|
/// via `AtomicUsize::compare_exchange_weak`, never by this path.
|
||||||
|
/// Maximum requests to process in a single `process_messages` call.
|
||||||
|
/// Prevents kernel starvation when the inbox is deep.
|
||||||
|
const MAX_MESSAGES_PER_CALL: usize = 64;
|
||||||
|
|
||||||
pub fn process_messages(&mut self) -> Vec<PMResponse> {
|
pub fn process_messages(&mut self) -> Vec<PMResponse> {
|
||||||
let mut responses = Vec::new();
|
let mut responses = Vec::new();
|
||||||
|
let mut remaining = Self::MAX_MESSAGES_PER_CALL;
|
||||||
|
|
||||||
while let Some(req) = self.queue.pop() {
|
while let Some(req) = self.queue.pop() {
|
||||||
|
remaining -= 1;
|
||||||
let resp = match req {
|
let resp = match req {
|
||||||
PMRequest::Allocate { size_pages, token_sig, channel_id } => {
|
PMRequest::Allocate { size_pages, token_sig, channel_id } => {
|
||||||
self.handle_allocate(size_pages, token_sig, channel_id)
|
self.handle_allocate(size_pages, token_sig, channel_id)
|
||||||
@@ -342,12 +328,13 @@ impl PMActor {
|
|||||||
PMRequest::None => continue,
|
PMRequest::None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only push responses that need routing.
|
|
||||||
// Free responses (channel_id == 0) are still pushed so callers can
|
|
||||||
// audit completion if needed; they may simply drop them.
|
|
||||||
if let Some(r) = resp {
|
if let Some(r) = resp {
|
||||||
responses.push(r);
|
responses.push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remaining == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses
|
responses
|
||||||
@@ -434,9 +421,6 @@ impl PMActor {
|
|||||||
|
|
||||||
self.buddy.free(local_frame_idx, order);
|
self.buddy.free(local_frame_idx, order);
|
||||||
|
|
||||||
// Free never needs routing — return None to skip Vec push, or push with
|
|
||||||
// channel_id=0 for audit purposes. We skip to avoid unnecessary allocation.
|
|
||||||
// Callers that need a Free-complete signal should use a separate mechanism.
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,7 +430,6 @@ impl PMActor {
|
|||||||
size_pages: usize,
|
size_pages: usize,
|
||||||
channel_id: u16,
|
channel_id: u16,
|
||||||
) -> Option<PMResponse> {
|
) -> Option<PMResponse> {
|
||||||
// Validate offset + size within range.
|
|
||||||
let range_pages = self.buddy.total_pages();
|
let range_pages = self.buddy.total_pages();
|
||||||
if offset_pages >= range_pages
|
if offset_pages >= range_pages
|
||||||
|| size_pages == 0
|
|| size_pages == 0
|
||||||
|
|||||||
@@ -69,13 +69,22 @@ pub fn init() {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_router() -> &'static PMRouter {
|
pub fn get_router() -> &'static PMRouter {
|
||||||
|
// INVARIANT: `is_ready` is set once in `init()` and never cleared.
|
||||||
|
// The Acquire load on `is_ready` synchronises-with the Release store in
|
||||||
|
// `init()`, making the `Option::Some` write visible. Because nothing
|
||||||
|
// ever writes `None` or clears `is_ready`, the TOCTOU window between
|
||||||
|
// the load and the `unwrap_unchecked` is safe.
|
||||||
if ROUTER.is_ready.load(Ordering::Acquire) {
|
if ROUTER.is_ready.load(Ordering::Acquire) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*ROUTER.inner.get()).as_ref().unwrap_unchecked()
|
(*ROUTER.inner.get()).as_ref().unwrap_unchecked()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
#[cold]
|
||||||
|
fn not_initialized() -> ! {
|
||||||
panic!("FATAL: PMRouter is accessed before initialization!")
|
panic!("FATAL: PMRouter is accessed before initialization!")
|
||||||
}
|
}
|
||||||
|
not_initialized()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PMRouter {
|
impl PMRouter {
|
||||||
@@ -131,9 +140,10 @@ impl PMRouter {
|
|||||||
let channel = &self.channels[id as usize];
|
let channel = &self.channels[id as usize];
|
||||||
|
|
||||||
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||||
core::hint::spin_loop();
|
// HLT-ожидание вместо busy-wait: CPU останавливается до ближайшего
|
||||||
// TODO: Для "Focus Mode" и полноценного планировщика:
|
// прерывания (timer tick, IPI от route_responses и т.д.).
|
||||||
// scheduler::yield_to_actor();
|
// Когда появится планировщик — заменить на yield_to_actor().
|
||||||
|
unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
|
|||||||
@@ -1,20 +1,36 @@
|
|||||||
use crate::mem::address::PhysAddr;
|
use crate::mem::address::{PhysAddr, get_hhdm};
|
||||||
use crate::mem::allocator::Locked;
|
use crate::mem::allocator::Locked;
|
||||||
|
|
||||||
pub const PAGE_SIZE: u64 = 4096;
|
pub const PAGE_SIZE: u64 = 4096;
|
||||||
|
|
||||||
pub struct BitmapPMM {
|
pub struct BitmapPMM {
|
||||||
bitmap: &'static mut [u8],
|
bitmap: &'static mut [u8],
|
||||||
|
l1_bitmap: &'static mut [u64],
|
||||||
ref_counts: &'static mut [u16],
|
ref_counts: &'static mut [u16],
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
used_pages: usize,
|
used_pages: usize,
|
||||||
/// Byte index hint: next search starts here to amortise O(N) scans.
|
last_word: usize,
|
||||||
last_byte: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
||||||
|
|
||||||
impl BitmapPMM {
|
impl BitmapPMM {
|
||||||
|
unsafe fn read_word(&self, word_idx: usize) -> u64 {
|
||||||
|
let byte_off = word_idx * 8;
|
||||||
|
unsafe { (self.bitmap.as_ptr().add(byte_off) as *const u64).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l1_update_word(&mut self, word_idx: usize) {
|
||||||
|
let word = unsafe { self.read_word(word_idx) };
|
||||||
|
let l1_idx = word_idx / 64;
|
||||||
|
let l1_bit = word_idx % 64;
|
||||||
|
if word != !0u64 {
|
||||||
|
self.l1_bitmap[l1_idx] |= 1 << l1_bit;
|
||||||
|
} else {
|
||||||
|
self.l1_bitmap[l1_idx] &= !(1 << l1_bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn used_pages(&self) -> usize { self.used_pages }
|
pub fn used_pages(&self) -> usize { self.used_pages }
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -22,7 +38,7 @@ impl BitmapPMM {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
||||||
|
|
||||||
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
|
||||||
let max_addr = mmap.entries().iter()
|
let max_addr = mmap.entries().iter()
|
||||||
.map(|e| e.base + e.length)
|
.map(|e| e.base + e.length)
|
||||||
.max()
|
.max()
|
||||||
@@ -32,7 +48,10 @@ impl BitmapPMM {
|
|||||||
|
|
||||||
let bitmap_size = total_pages.div_ceil(8);
|
let bitmap_size = total_pages.div_ceil(8);
|
||||||
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
||||||
let total_meta_size = bitmap_size + ref_counts_size;
|
let num_words = total_pages.div_ceil(64);
|
||||||
|
let l1_u64_count = num_words.div_ceil(64);
|
||||||
|
let l1_byte_size = l1_u64_count * 8;
|
||||||
|
let total_meta_size = bitmap_size + ref_counts_size + l1_byte_size;
|
||||||
|
|
||||||
let meta_phys = mmap.entries().iter()
|
let meta_phys = mmap.entries().iter()
|
||||||
.find(|e| {
|
.find(|e| {
|
||||||
@@ -42,8 +61,10 @@ impl BitmapPMM {
|
|||||||
.map(|e| e.base)
|
.map(|e| e.base)
|
||||||
.expect("PMM: no usable region large enough for metadata");
|
.expect("PMM: no usable region large enough for metadata");
|
||||||
|
|
||||||
let bitmap_ptr = (meta_phys + hhdm_offset) as *mut u8;
|
let hhdm = get_hhdm();
|
||||||
let ref_counts_ptr = (meta_phys + hhdm_offset + bitmap_size as u64) as *mut u16;
|
let bitmap_ptr = (meta_phys + hhdm) as *mut u8;
|
||||||
|
let ref_counts_ptr = (meta_phys + hhdm + bitmap_size as u64) as *mut u16;
|
||||||
|
let l1_ptr = (meta_phys + hhdm + bitmap_size as u64 + ref_counts_size as u64) as *mut u64;
|
||||||
|
|
||||||
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
||||||
bitmap.fill(0xFF);
|
bitmap.fill(0xFF);
|
||||||
@@ -51,12 +72,16 @@ impl BitmapPMM {
|
|||||||
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
||||||
ref_counts.fill(1);
|
ref_counts.fill(1);
|
||||||
|
|
||||||
|
let l1_bitmap = unsafe { core::slice::from_raw_parts_mut(l1_ptr, l1_u64_count) };
|
||||||
|
l1_bitmap.fill(0);
|
||||||
|
|
||||||
let mut pmm = Self {
|
let mut pmm = Self {
|
||||||
bitmap,
|
bitmap,
|
||||||
|
l1_bitmap,
|
||||||
ref_counts,
|
ref_counts,
|
||||||
total_pages,
|
total_pages,
|
||||||
used_pages: total_pages,
|
used_pages: total_pages,
|
||||||
last_byte: 0,
|
last_word: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in mmap.entries() {
|
for entry in mmap.entries() {
|
||||||
@@ -77,8 +102,6 @@ impl BitmapPMM {
|
|||||||
*PMM.lock() = Some(pmm);
|
*PMM.lock() = Some(pmm);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Core operations
|
|
||||||
|
|
||||||
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||||
if idx >= self.total_pages { return; }
|
if idx >= self.total_pages { return; }
|
||||||
@@ -92,7 +115,10 @@ impl BitmapPMM {
|
|||||||
if self.ref_counts[idx] == 0 {
|
if self.ref_counts[idx] == 0 {
|
||||||
self.bitmap[byte] &= !(1 << bit);
|
self.bitmap[byte] &= !(1 << bit);
|
||||||
self.used_pages -= 1;
|
self.used_pages -= 1;
|
||||||
if byte < self.last_byte { self.last_byte = byte; }
|
|
||||||
|
let word_idx = idx / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
|
if word_idx < self.last_word { self.last_word = word_idx; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +137,9 @@ impl BitmapPMM {
|
|||||||
} else if self.ref_counts[idx] == 0 {
|
} else if self.ref_counts[idx] == 0 {
|
||||||
self.ref_counts[idx] = 1;
|
self.ref_counts[idx] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let word_idx = idx / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
@@ -126,33 +155,47 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
||||||
let len = self.bitmap.len();
|
let l1_len = self.l1_bitmap.len();
|
||||||
|
|
||||||
for pass in 0..2usize {
|
for pass in 0..2usize {
|
||||||
let (from, to) = if pass == 0 {
|
let (from, to) = if pass == 0 {
|
||||||
(self.last_byte, len)
|
(self.last_word / 64, l1_len)
|
||||||
} else {
|
} else {
|
||||||
(0, self.last_byte)
|
(0, self.last_word / 64)
|
||||||
};
|
};
|
||||||
|
|
||||||
for byte_idx in from..to {
|
for l1_idx in from..to {
|
||||||
if self.bitmap[byte_idx] == 0xFF { continue; }
|
let l1_word = self.l1_bitmap[l1_idx];
|
||||||
|
if l1_word == 0 { continue; }
|
||||||
|
|
||||||
for bit in 0..8u8 {
|
let word_offset = l1_word.trailing_zeros() as usize;
|
||||||
if self.bitmap[byte_idx] & (1 << bit) == 0 {
|
let word_idx = l1_idx * 64 + word_offset;
|
||||||
let page_idx = byte_idx * 8 + bit as usize;
|
|
||||||
if page_idx >= self.total_pages { return None; }
|
|
||||||
|
|
||||||
self.bitmap[byte_idx] |= 1 << bit;
|
let word = unsafe { self.read_word(word_idx) };
|
||||||
|
if word == !0u64 {
|
||||||
|
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let free_bit = (!word).trailing_zeros() as usize;
|
||||||
|
let page_idx = word_idx * 64 + free_bit;
|
||||||
|
if page_idx >= self.total_pages { continue; }
|
||||||
|
|
||||||
|
let byte = page_idx / 8;
|
||||||
|
let bit = page_idx % 8;
|
||||||
|
|
||||||
|
self.bitmap[byte] |= 1 << bit;
|
||||||
self.ref_counts[page_idx] = 1;
|
self.ref_counts[page_idx] = 1;
|
||||||
self.used_pages += 1;
|
self.used_pages += 1;
|
||||||
self.last_byte = byte_idx;
|
self.last_word = word_idx;
|
||||||
|
|
||||||
|
if unsafe { self.read_word(word_idx) } == !0u64 {
|
||||||
|
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||||
|
}
|
||||||
|
|
||||||
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -175,9 +218,11 @@ impl BitmapPMM {
|
|||||||
for i in run_start..run_start + count {
|
for i in run_start..run_start + count {
|
||||||
self.bitmap[i / 8] |= 1 << (i % 8);
|
self.bitmap[i / 8] |= 1 << (i % 8);
|
||||||
self.ref_counts[i] = 1;
|
self.ref_counts[i] = 1;
|
||||||
|
let word_idx = i / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
}
|
}
|
||||||
self.used_pages += count;
|
self.used_pages += count;
|
||||||
self.last_byte = run_start / 8;
|
self.last_word = run_start / 64;
|
||||||
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -189,8 +234,6 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Module-level convenience functions
|
|
||||||
|
|
||||||
pub fn alloc_frame() -> Option<PhysAddr> {
|
pub fn alloc_frame() -> Option<PhysAddr> {
|
||||||
PMM.lock().as_mut()?.alloc_frame()
|
PMM.lock().as_mut()?.alloc_frame()
|
||||||
}
|
}
|
||||||
@@ -218,7 +261,3 @@ pub fn get_stats() -> (usize, usize) {
|
|||||||
(0, 0)
|
(0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fn local_tlb_flush_asid(asid: u16) {
|
|||||||
unsafe {
|
unsafe {
|
||||||
core::arch::asm!(
|
core::arch::asm!(
|
||||||
"invpcid {ty}, [{desc}]",
|
"invpcid {ty}, [{desc}]",
|
||||||
ty = in(reg) 1u64, // type 1 = single-context flush
|
ty = in(reg) 1u64,
|
||||||
desc = in(reg) &desc,
|
desc = in(reg) &desc,
|
||||||
options(nostack, preserves_flags),
|
options(nostack, preserves_flags),
|
||||||
);
|
);
|
||||||
@@ -69,24 +69,14 @@ fn local_tlb_flush_asid(asid: u16) {
|
|||||||
// Error type
|
// Error type
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum VmError {
|
pub enum VmError {
|
||||||
/// PMM returned `None` — no physical frames available.
|
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
/// The requested virtual range overlaps an existing VMA.
|
|
||||||
RegionOverlap,
|
RegionOverlap,
|
||||||
/// No VMA covers the given address.
|
|
||||||
RegionNotFound,
|
RegionNotFound,
|
||||||
/// Address or size is not a multiple of 4096.
|
|
||||||
InvalidAlignment,
|
InvalidAlignment,
|
||||||
/// Size is zero, or `virt + size` would overflow.
|
|
||||||
InvalidRange,
|
InvalidRange,
|
||||||
/// Write fault on a read-only VMA, or exec fault on a NX VMA.
|
|
||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
/// Page-fault in a non-lazy (already-eager or fixed) region — hardware bug
|
|
||||||
/// or an exploit attempt; the faulting task must be killed.
|
|
||||||
UnexpectedFault,
|
UnexpectedFault,
|
||||||
/// Address outside the x86-64 canonical range.
|
|
||||||
NonCanonical,
|
NonCanonical,
|
||||||
/// ASID pool exhausted (> 4094 simultaneous address spaces).
|
|
||||||
AsidExhausted,
|
AsidExhausted,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,23 +133,10 @@ impl VmaFlags {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VmaBacking {
|
pub enum VmaBacking {
|
||||||
/// Anonymous pages (stack, heap, BSS).
|
|
||||||
/// Index `i` → frame for `virt_start + i * 4096`. `None` = not yet faulted in.
|
|
||||||
Anonymous(Vec<Option<PhysAddr>>),
|
Anonymous(Vec<Option<PhysAddr>>),
|
||||||
|
|
||||||
/// Fixed physical range. Frames are **not** freed on unmap.
|
|
||||||
/// Used for MMIO, identity-mapped RAM, framebuffer, DMA buffers.
|
|
||||||
Physical(PhysAddr),
|
Physical(PhysAddr),
|
||||||
|
|
||||||
/// Zero-copy borrow of another actor's frames.
|
|
||||||
///
|
|
||||||
/// `phys_base` is the physical address of the first page; the mapping covers
|
|
||||||
/// exactly `(virt_end - virt_start) / 4096` pages.
|
|
||||||
/// Frames are owned by `owner_cap` and **never** freed by this VMA.
|
|
||||||
Shared {
|
Shared {
|
||||||
/// Token of the capability that owns the frames.
|
|
||||||
owner_cap: u64,
|
owner_cap: u64,
|
||||||
/// Physical base (first page of the shared region).
|
|
||||||
phys_base: PhysAddr,
|
phys_base: PhysAddr,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -251,13 +228,13 @@ impl AsidAllocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None // pool truly exhausted
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an ASID to the pool.
|
/// Return an ASID to the pool.
|
||||||
fn free(&mut self, asid: u16) {
|
fn free(&mut self, asid: u16) {
|
||||||
if asid == 0 || asid >= 4095 {
|
if asid == 0 || asid >= 4095 {
|
||||||
return; // sentinel values — never freed
|
return;
|
||||||
}
|
}
|
||||||
let word = asid as usize / 32;
|
let word = asid as usize / 32;
|
||||||
let bit = asid as usize % 32;
|
let bit = asid as usize % 32;
|
||||||
@@ -329,14 +306,12 @@ pub struct AddressSpace {
|
|||||||
pub pml4_phys: PhysAddr,
|
pub pml4_phys: PhysAddr,
|
||||||
/// Sorted (by virt_start), non-overlapping VMA list.
|
/// Sorted (by virt_start), non-overlapping VMA list.
|
||||||
regions: Vec<VmaRegion>,
|
regions: Vec<VmaRegion>,
|
||||||
/// HHDM offset for dereferencing page-table pages.
|
|
||||||
hhdm: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddressSpace {
|
impl AddressSpace {
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn pml4_raw(&self) -> *mut PageTable {
|
unsafe fn pml4_raw(&self) -> *mut PageTable {
|
||||||
self.pml4_phys.to_virt(self.hhdm).as_mut_ptr::<PageTable>()
|
self.pml4_phys.to_virt().as_mut_ptr::<PageTable>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_overlap(&self, start: VirtAddr, end: VirtAddr) -> Result<(), VmError> {
|
fn check_overlap(&self, start: VirtAddr, end: VirtAddr) -> Result<(), VmError> {
|
||||||
@@ -349,10 +324,9 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_for_fork(&mut self, child_cap_token: u64) -> Result<Self, VmError> {
|
pub fn clone_for_fork(&mut self, child_cap_token: u64) -> Result<Self, VmError> {
|
||||||
let mut child = AddressSpace::new(self.hhdm)?;
|
let mut child = AddressSpace::new()?;
|
||||||
let child_pml4 = unsafe { &mut *child.pml4_raw() };
|
let child_pml4 = unsafe { &mut *child.pml4_raw() };
|
||||||
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
for region in &mut self.regions {
|
for region in &mut self.regions {
|
||||||
let mut child_region = VmaRegion {
|
let mut child_region = VmaRegion {
|
||||||
@@ -373,10 +347,10 @@ impl AddressSpace {
|
|||||||
|
|
||||||
match &mut region.backing {
|
match &mut region.backing {
|
||||||
VmaBacking::Physical(base) => {
|
VmaBacking::Physical(base) => {
|
||||||
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags(), hhdm);
|
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags());
|
||||||
}
|
}
|
||||||
VmaBacking::Shared { phys_base, .. } => {
|
VmaBacking::Shared { phys_base, .. } => {
|
||||||
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags(), hhdm);
|
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags());
|
||||||
}
|
}
|
||||||
VmaBacking::Anonymous(frames) => {
|
VmaBacking::Anonymous(frames) => {
|
||||||
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
||||||
@@ -400,10 +374,10 @@ impl AddressSpace {
|
|||||||
page_flags.remove(PageTableFlags::WRITABLE);
|
page_flags.remove(PageTableFlags::WRITABLE);
|
||||||
page_flags.insert(PageTableFlags::COW);
|
page_flags.insert(PageTableFlags::COW);
|
||||||
|
|
||||||
let _ = parent_pml4.update_flags(virt, page_flags, hhdm);
|
let _ = parent_pml4.update_flags(virt, page_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
child_pml4.map_page(virt, *frame, page_flags, hhdm);
|
child_pml4.map_page(virt, *frame, page_flags);
|
||||||
child_frames[i] = Some(*frame);
|
child_frames[i] = Some(*frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,12 +405,12 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unmap pages + free frames (if owned) for one region.
|
/// Unmap pages + free frames (if owned) for one region.
|
||||||
fn do_unmap(pml4: *mut PageTable, region: &VmaRegion, hhdm: u64) {
|
fn do_unmap(pml4: *mut PageTable, region: &VmaRegion) {
|
||||||
let pml4 = unsafe { &mut *pml4 };
|
let pml4 = unsafe { &mut *pml4 };
|
||||||
let own = region.backing.owns_frames();
|
let own = region.backing.owns_frames();
|
||||||
for i in 0..region.pages() {
|
for i in 0..region.pages() {
|
||||||
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
||||||
pml4.unmap_page(virt, hhdm);
|
pml4.unmap_page(virt);
|
||||||
if own {
|
if own {
|
||||||
if let Some(frame) = region.backing.phys_for_page(i) {
|
if let Some(frame) = region.backing.phys_for_page(i) {
|
||||||
pmm::free_frame(frame);
|
pmm::free_frame(frame);
|
||||||
@@ -446,7 +420,6 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
|
|
||||||
let indices: Vec<usize> = self.regions
|
let indices: Vec<usize> = self.regions
|
||||||
@@ -458,7 +431,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
for idx in indices.into_iter().rev() {
|
for idx in indices.into_iter().rev() {
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,34 +452,24 @@ impl AddressSpace {
|
|||||||
|
|
||||||
impl AddressSpace {
|
impl AddressSpace {
|
||||||
/// Allocate a fresh, empty address space with a zeroed PML4.
|
/// Allocate a fresh, empty address space with a zeroed PML4.
|
||||||
pub fn new(hhdm: u64) -> Result<Self, VmError> {
|
pub fn new() -> Result<Self, VmError> {
|
||||||
let pml4_phys = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let pml4_phys = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(pml4_phys.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
core::ptr::write_bytes(pml4_phys.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
asid: alloc_asid()?,
|
asid: alloc_asid()?,
|
||||||
pml4_phys,
|
pml4_phys,
|
||||||
regions: Vec::new(),
|
regions: Vec::new(),
|
||||||
hhdm,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
||||||
/// ASID 0 = kernel "no PCID tagging"; it is never returned to the pool.
|
/// ASID 0 = kernel "no PCID tagging"; it is never returned to the pool.
|
||||||
pub fn from_active(pml4_phys: PhysAddr, asid: u16, hhdm: u64) -> Self {
|
pub fn from_active(pml4_phys: PhysAddr, asid: u16) -> Self {
|
||||||
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
|
Self { asid, pml4_phys, regions: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Anonymous / fixed mapping ─────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Map `size` bytes of virtual space starting at `virt`.
|
|
||||||
///
|
|
||||||
/// | `phys` | `VmaFlags::LAZY` | Behaviour |
|
|
||||||
/// |-----------|------------------|-----------------------------------------------|
|
|
||||||
/// | `Some(p)` | any | Fixed physical (MMIO / identity / DMA) |
|
|
||||||
/// | `None` | not set | Eager anonymous — allocate + zero + map now |
|
|
||||||
/// | `None` | set | Lazy anonymous — map frames on first fault |
|
|
||||||
pub fn map_region(
|
pub fn map_region(
|
||||||
&mut self,
|
&mut self,
|
||||||
virt: VirtAddr,
|
virt: VirtAddr,
|
||||||
@@ -526,11 +489,10 @@ impl AddressSpace {
|
|||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let page_flags = flags.to_page_flags();
|
let page_flags = flags.to_page_flags();
|
||||||
let page_count = (size / 4096) as usize;
|
let page_count = (size / 4096) as usize;
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
let backing = match phys {
|
let backing = match phys {
|
||||||
Some(base) => {
|
Some(base) => {
|
||||||
pml4.map_region(virt, base, size, page_flags, hhdm);
|
pml4.map_region(virt, base, size, page_flags);
|
||||||
VmaBacking::Physical(base)
|
VmaBacking::Physical(base)
|
||||||
}
|
}
|
||||||
None if flags.contains(VmaFlags::LAZY) => {
|
None if flags.contains(VmaFlags::LAZY) => {
|
||||||
@@ -543,13 +505,12 @@ impl AddressSpace {
|
|||||||
for i in 0..page_count {
|
for i in 0..page_count {
|
||||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(frame.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
core::ptr::write_bytes(frame.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||||
}
|
}
|
||||||
pml4.map_page(
|
pml4.map_page(
|
||||||
VirtAddr(virt.0 + i as u64 * 4096),
|
VirtAddr(virt.0 + i as u64 * 4096),
|
||||||
frame,
|
frame,
|
||||||
page_flags,
|
page_flags,
|
||||||
hhdm,
|
|
||||||
);
|
);
|
||||||
frames.push(Some(frame));
|
frames.push(Some(frame));
|
||||||
}
|
}
|
||||||
@@ -589,7 +550,6 @@ impl AddressSpace {
|
|||||||
|
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let page_flags = flags.to_page_flags();
|
let page_flags = flags.to_page_flags();
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
// Eagerly map all pages — the physical addresses are already known.
|
// Eagerly map all pages — the physical addresses are already known.
|
||||||
for i in 0..page_count {
|
for i in 0..page_count {
|
||||||
@@ -597,7 +557,6 @@ impl AddressSpace {
|
|||||||
VirtAddr(virt.0 + i as u64 * 4096),
|
VirtAddr(virt.0 + i as u64 * 4096),
|
||||||
PhysAddr(phys_base.0 + i as u64 * 4096),
|
PhysAddr(phys_base.0 + i as u64 * 4096),
|
||||||
page_flags,
|
page_flags,
|
||||||
hhdm,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +576,6 @@ impl AddressSpace {
|
|||||||
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
||||||
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
||||||
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError> {
|
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError> {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
||||||
|
|
||||||
let (virt_start, region_flags) = {
|
let (virt_start, region_flags) = {
|
||||||
@@ -633,7 +591,7 @@ impl AddressSpace {
|
|||||||
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
|
|
||||||
let current_pte_flags = pml4.get_flags(page_virt, hhdm);
|
let current_pte_flags = pml4.get_flags(page_virt);
|
||||||
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
||||||
|
|
||||||
if write && is_cow {
|
if write && is_cow {
|
||||||
@@ -645,10 +603,12 @@ impl AddressSpace {
|
|||||||
let old_frame = frames[page_idx].expect("COW fault on unmapped page");
|
let old_frame = frames[page_idx].expect("COW fault on unmapped page");
|
||||||
let new_frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let new_frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
|
|
||||||
|
debug_assert!(old_frame != new_frame, "COW: old and new frame are the same!");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::copy_nonoverlapping(
|
core::ptr::copy_nonoverlapping(
|
||||||
old_frame.to_virt(hhdm).as_ptr::<u8>(),
|
old_frame.to_virt().as_ptr::<u8>(),
|
||||||
new_frame.to_virt(hhdm).as_mut_ptr::<u8>(),
|
new_frame.to_virt().as_mut_ptr::<u8>(),
|
||||||
4096,
|
4096,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -659,7 +619,7 @@ impl AddressSpace {
|
|||||||
target_flags.remove(PageTableFlags::COW);
|
target_flags.remove(PageTableFlags::COW);
|
||||||
target_flags.insert(PageTableFlags::WRITABLE);
|
target_flags.insert(PageTableFlags::WRITABLE);
|
||||||
|
|
||||||
pml4.map_page(page_virt, new_frame, target_flags, hhdm);
|
pml4.map_page(page_virt, new_frame, target_flags);
|
||||||
|
|
||||||
pmm::free_frame(old_frame);
|
pmm::free_frame(old_frame);
|
||||||
|
|
||||||
@@ -681,7 +641,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(frame.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
core::ptr::write_bytes(frame.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||||
}
|
}
|
||||||
frames[page_idx] = Some(frame);
|
frames[page_idx] = Some(frame);
|
||||||
|
|
||||||
@@ -690,7 +650,7 @@ impl AddressSpace {
|
|||||||
target_flags.remove(PageTableFlags::WRITABLE);
|
target_flags.remove(PageTableFlags::WRITABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
pml4.map_page(page_virt, frame, target_flags, hhdm);
|
pml4.map_page(page_virt, frame, target_flags);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -699,7 +659,7 @@ impl AddressSpace {
|
|||||||
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
Self::do_unmap(pml4, ®ion, self.hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
tlb_flush_asid(self.asid);
|
tlb_flush_asid(self.asid);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -709,7 +669,6 @@ impl AddressSpace {
|
|||||||
/// Hardware access is terminated before this function returns.
|
/// Hardware access is terminated before this function returns.
|
||||||
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
||||||
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
|
|
||||||
let indices: Vec<usize> = self.regions
|
let indices: Vec<usize> = self.regions
|
||||||
@@ -723,7 +682,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
for idx in indices.into_iter().rev() {
|
for idx in indices.into_iter().rev() {
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
|
|
||||||
tlb_flush_asid(self.asid);
|
tlb_flush_asid(self.asid);
|
||||||
@@ -733,7 +692,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
/// Walk the live page table to translate `virt` → physical address.
|
/// Walk the live page table to translate `virt` → physical address.
|
||||||
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||||
unsafe { (*self.pml4_raw()).translate(virt, self.hhdm) }
|
unsafe { (*self.pml4_raw()).translate(virt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
//Activation
|
//Activation
|
||||||
@@ -767,10 +726,9 @@ impl AddressSpace {
|
|||||||
|
|
||||||
impl Drop for AddressSpace {
|
impl Drop for AddressSpace {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
while let Some(region) = self.regions.pop() {
|
while let Some(region) = self.regions.pop() {
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
pmm::free_frame(self.pml4_phys);
|
pmm::free_frame(self.pml4_phys);
|
||||||
// Return the ASID to the pool so it can be reused by future processes.
|
// Return the ASID to the pool so it can be reused by future processes.
|
||||||
@@ -793,7 +751,15 @@ pub fn tlb_flush_asid(asid: u16) {
|
|||||||
|
|
||||||
let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire);
|
let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire);
|
||||||
let current_core = crate::cpu::lapic::current_core_id();
|
let current_core = crate::cpu::lapic::current_core_id();
|
||||||
let target_mask = active_cpus & !(1u64 << current_core);
|
// u64 can only represent cores 0–63. APIC IDs may be >= 64 (CPUID
|
||||||
|
// returns up to 255), so guard the shift to avoid UB (panic in debug,
|
||||||
|
// wrap in release). When SMP with >64 cores is implemented, switch to
|
||||||
|
// a wider mask or a dynamic list of active APIC IDs.
|
||||||
|
let target_mask = if current_core < 64 {
|
||||||
|
active_cpus & !(1u64 << current_core)
|
||||||
|
} else {
|
||||||
|
active_cpus
|
||||||
|
};
|
||||||
|
|
||||||
if target_mask == 0 {
|
if target_mask == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -830,7 +796,12 @@ pub fn handle_tlb_shootdown_ipi() {
|
|||||||
local_tlb_flush_asid(asid);
|
local_tlb_flush_asid(asid);
|
||||||
|
|
||||||
let current_core = crate::cpu::lapic::current_core_id();
|
let current_core = crate::cpu::lapic::current_core_id();
|
||||||
|
// Only cores < 64 can ACK in a u64 mask. Cores >= 64 are not
|
||||||
|
// representable; their ACK would wrap and corrupt the mask.
|
||||||
|
// TODO: widen to u128 or use per-core ACK slots for >64 core SMP.
|
||||||
|
if current_core < 64 {
|
||||||
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
|
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global kernel address space
|
// Global kernel address space
|
||||||
@@ -840,6 +811,6 @@ pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
|||||||
///
|
///
|
||||||
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
||||||
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
||||||
pub fn init_kernel_space(pml4_phys: PhysAddr, hhdm: u64) {
|
pub fn init_kernel_space(pml4_phys: PhysAddr) {
|
||||||
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0, hhdm));
|
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user