• Operating System: Three Easy Pieces --- Limited Directed Execution (Note)


    In order to virtualize the CPU, the operating system needs to somehow share the physical CPU

    among many jobs running seemingly at the same time. The basic idea is simple: run one process

    for a little while, then run another one, and so forth. By time sharing the CPU in this manner, 

    virtualization is achieved. There are a few challenges, however, in building such virtualization

    machinery. The first is performance: how can we implement virtualization withour adding excessive

    overhead to the system? The second is control: how can we run processes efficiently while retaining

    control over the CPU? Control is particularly important to the OS, as it is in charge of resources;

    without control, a process could simply run forever and take over the machine, or access information

    that it should not be allowed to access. Attaining performance while maintaining control is thus one

    of the central challenges in building an operating system.

    Basic Technique: Limited Direct Execution

    To make a program runs as fast as one might expect, not surprisingly OS developers came up with

    a technique, which we called Limited Direct Execution. The "direct execution" part of the idea is simple:

    just run the program directly on the CPU. Thus, when the OS wished to start a program, it creates a process

    entry for it in a process list, allocates some memory for it, loads the program code into memory (from

    disk), locates its entry point (i.e. the main() routine or something similar), jumps to it, and starts running

    the user's code. Sounds simple, no? But this approach gives rise to a few problems in our quest to 

    virtualize the CPU. The first is simple: if we just run a program, how can the OS make sure the program

    does not do anything that we do not want it to do, while still running it efficiently? The second: when 

    we are running a process, how does the operating system stop it from running and switch to another

    process; thus implementing the time sharing we require to virtualize the CPU? In answering these questions

    below, we will get a much better sense of what is needed to virtualize the CPU. In developing these

    techniques, we will also see where the "limited" part of the name arises from; without limits on running

    program the OS would not be in control of anything and thus would be "just a library" --- a very sad state

    of affairs for an aspiring operating system.

    Restricted Operations

    Direct execution has the obvious advantage of being fast, the program runs natively on the hardware CPU

    and thus executes as quickly as one would expects. But running on the CPU introducing a problem: what if

    the process wishes to perform some kind of restricted operation, such as issuing an I/O request to a disk,

    or gaining access to more resources such as CPU or memory?

    Tip: Use Protected Control Transfer

    The hardware assists the OS by providing different modes of execution. In user mode, applications do not

    have full access to hardware resources. In kernel mode, the OS has access to the full resources of the

    machine. Special instructions to trap into the kernel and return-from-trap back to user mode programs

    are also provided, as well instructions that allow the OS to tell the hardware where the trap table in the

    memory.

    One approach would simply be to let any process do whatever it wants in terms of I/O and other related

    operations. However, doing so would prevent the construction of many kinds of systems that are desired.

    For example, if we wish to build a file system that checks permissions before granting access to a file, we

    can not simply let user process issue I/O to the disk; If we did, a process could  simply read or write the

    entire disk and thus all protections would be lost.

    Thus, the approach we take to is introduce a new processor

    mode, known as user mode; code that runs in user mode is restricted in what it can do. For example,

    when running in user mode, a process can not issue I/O request; doing so would result in the processor

    raising an exception; the OS would then likely kill the process.

    In contrast to user mode is kernel mode, which the operating system (or kernel) runs in. In this mode,

    code that runs can do what it likes, including privileged operations such as issuing I/O requests and 

    executing all types of restricted instructions.

    We are still left with a challenge, however, what should a user process do when it wishes to perform some

    kind of privileged operation, such as reading from disk? To enable this, virtually all modern hardware

    provides the ability for user programs to perfrom a system call. Pioneered on ancient machines such as the

    Atlas, system calls allow the kernel to carefully expose certain key pieces of functionality to user programs,

    such as accessing the file system, creating and destroying processes, communicating with other processes, 

    and allocating more memory. Most operating systems provide a few hundred of calls; early Unix systems

    exposed a more concise subset of around twenty calls.

    To execute a system call, a program must execute a special trap instruction. This instruction simultaneously

    jumps into the kernel and raises the privilege level to kernel mode; once in the kernel, the system can now

    perfrom whatever privileged operations are needed (if allowed), and thus do the required work for the calling

    process. When finished, the OS calls a special return-from-trap instruction, which, as you might expect, returns

    into the calling user program while simultaneously reducing the privilege level back to user mode.

    The hardware needs to be a bit careful when executing a trap, in that it must make sure to save enough of the

    caller's registers in order to be able to return correctly when the OS issues the return-from-trap instruction.

    On x86, for example, the processor will push the program counter, flags, and a few other registers onto a

    per-process kernel stack; the return-from-trap will pop these values off the stack and resume execution of 

    the user-mode program. Other hardware systems use different conventions, but the basic concept are similar

    across platforms.

    There is one important detail left out of this discussion: how does the trap konw which code to run inside the

    OS? Clearly, the calling process can't specify an address to jump to (as you would when making a procedure

    call); doing so would allow programs to jump anywhere into the kernel which clearly is a bad idea (imagine

    jumping into code to access a file, but just after a permission check; in fact, it is likely such an ability would

    enable a wily programmer to get the kernel to run arbitrary code sequence). Thus the kernel must carefully

    control what code executes upon a trap.

    The kernel does so by setting up a trap table at boot time. When the machine boots up, it does so in privileged

    kernel code, and thus is free to configure machine hardware as need be. One of the first things the OS thus

    does is to tell the hardware what code to run when certain exceptional events occur. For example, what code

    should run when a hard-disk interrupt takes place, when a keyboard interrupt occurs, or when program makes

    a system call? The OS informs the hardware of the locations of these trap handlers, usually with some kind of

    special instruction. Once the hardware is informed, it remembers the location of these handlers until the machine

    is next rebooted, and thus the hardware knows what to do (what code to jump to) when system calls and other

    exceptional events take place.

    One last aside: being able to execute the instruction to tell the hardware where the trap tables are is a very

    powerful capability. Thus, as you might have guessed, it is also a privileged operation. If you try to execute this

    instruction in user mode, the hardware won't let you, and you can probably guess what will happen.

    There are two phases in the LDE protocal. In the first (at boot time), the kernel initializes the trap table, and

    the CPU remembers its location for subsequent use. The kernel does so via a privileged instruction. In the second (

    when running a process), the kernel sets up a few things (e.g., allocating a node on the process list, allocating

    memory) before using a return-from-trap instruction to start the execution of the process; this switches the cpu

    to user mode and begins running the process. When the process wishes to issue a system call, it traps back into

    the OS, which handles it and once again returns control via a return-from-trap to the process. The process then

    completes its work, and returns from main(); this usually will return to the stub code which will properly exit the

    program (say, by calling the exit() system call, which traps into the OS). At this point, the OS cleans up and we 

    are done.

  • 相关阅读:
    topK问题 前K个高频元素 leetcode692
    反转链表 leetcode206
    关于IO多路复用的简单整理
    两数之和 leetcode1
    使用 jenkins 发布 前端 项目
    CentOS7 部署 nacos 集群
    JWT
    keepalived 的 unicast 单播模式
    使用 keepalived 高可用 nginx
    翻转二叉树 leetcode226
  • 原文地址:https://www.cnblogs.com/miaoyong/p/4884967.html
Copyright © 2020-2023  润新知