内核是任何计算机系统的“核心”:它是允许用户共享计算机资源的“软件”。
内核可以被认为是操作系统 (OS) 的主要软件,它也可能包括图形管理。
例如,在 Linux (像其他类 Unix 操作系统一样) 下,XWindow 环境不属于 Linux 内核,因为它只管理图形操作 (它使用用户模式 I/O 来访问显卡设备)。
相比之下,Windows 环境 (Win9x, WinME, WinNT, Win2K, WinXP 等等) 是图形环境和内核的混合体。
许多年前,当计算机像房间一样大时,用户运行他们的应用程序非常困难,有时,他们的应用程序会使计算机崩溃。
为了避免应用程序不断崩溃,较新的操作系统被设计为具有 2 种不同的操作模式
| Applications /|\ | ______________ | | | User Mode | | | ______________ | | | | Implementation | _______ _______ | Abstraction Detail | | Kernel Mode | | | _______________ | | | | | | | | | | \|/ Hardware |
内核模式“阻止”用户模式应用程序损坏系统或其功能。
现代微处理器在硬件中至少实现了 2 种不同的状态。例如在 Intel 下,4 种状态决定了 PL (特权级别)。可以使用 0,1,2,3 状态,其中 0 用于内核模式。
Unix 操作系统只需要 2 个特权级别,我们将使用这种范例作为参考点。
一旦我们理解了存在 2 种不同的模式,我们就必须知道我们什么时候从一种模式切换到另一种模式。
通常,有 2 个切换点
系统调用就像管理驻留在内核模式中的操作系统例程的特殊函数。
当我们
| | ------->| System Call i | (Accessing Devices) | | | | [sys_read()] | | ... | | | | | system_call(i) |-------- | | | [read()] | | | | ... | | | | system_call(j) |-------- | | | [get_pid()] | | | | | ... | ------->| System Call j | (Accessing kernel data structures) | | | [sys_getpid()]| | | USER MODE KERNEL MODE Unix System Calls Working
系统调用几乎是用户模式与底层资源 (硬件) 通信的唯一接口。此语句的唯一例外是当进程使用 ''ioperm'' 系统调用时。在这种情况下,用户模式进程可以直接访问设备 (不能使用 IRQ)。
注意:并非每个 ''C'' 函数都是系统调用,只有一部分是。
以下是 Linux Kernel 2.4.17 下的系统调用列表,来自 [ arch/i386/kernel/entry.S ]
.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/ .long SYMBOL_NAME(sys_exit) .long SYMBOL_NAME(sys_fork) .long SYMBOL_NAME(sys_read) .long SYMBOL_NAME(sys_write) .long SYMBOL_NAME(sys_open) /* 5 */ .long SYMBOL_NAME(sys_close) .long SYMBOL_NAME(sys_waitpid) .long SYMBOL_NAME(sys_creat) .long SYMBOL_NAME(sys_link) .long SYMBOL_NAME(sys_unlink) /* 10 */ .long SYMBOL_NAME(sys_execve) .long SYMBOL_NAME(sys_chdir) .long SYMBOL_NAME(sys_time) .long SYMBOL_NAME(sys_mknod) .long SYMBOL_NAME(sys_chmod) /* 15 */ .long SYMBOL_NAME(sys_lchown16) .long SYMBOL_NAME(sys_ni_syscall) /* old break syscall holder */ .long SYMBOL_NAME(sys_stat) .long SYMBOL_NAME(sys_lseek) .long SYMBOL_NAME(sys_getpid) /* 20 */ .long SYMBOL_NAME(sys_mount) .long SYMBOL_NAME(sys_oldumount) .long SYMBOL_NAME(sys_setuid16) .long SYMBOL_NAME(sys_getuid16) .long SYMBOL_NAME(sys_stime) /* 25 */ .long SYMBOL_NAME(sys_ptrace) .long SYMBOL_NAME(sys_alarm) .long SYMBOL_NAME(sys_fstat) .long SYMBOL_NAME(sys_pause) .long SYMBOL_NAME(sys_utime) /* 30 */ .long SYMBOL_NAME(sys_ni_syscall) /* old stty syscall holder */ .long SYMBOL_NAME(sys_ni_syscall) /* old gtty syscall holder */ .long SYMBOL_NAME(sys_access) .long SYMBOL_NAME(sys_nice) .long SYMBOL_NAME(sys_ni_syscall) /* 35 */ /* old ftime syscall holder */ .long SYMBOL_NAME(sys_sync) .long SYMBOL_NAME(sys_kill) .long SYMBOL_NAME(sys_rename) .long SYMBOL_NAME(sys_mkdir) .long SYMBOL_NAME(sys_rmdir) /* 40 */ .long SYMBOL_NAME(sys_dup) .long SYMBOL_NAME(sys_pipe) .long SYMBOL_NAME(sys_times) .long SYMBOL_NAME(sys_ni_syscall) /* old prof syscall holder */ .long SYMBOL_NAME(sys_brk) /* 45 */ .long SYMBOL_NAME(sys_setgid16) .long SYMBOL_NAME(sys_getgid16) .long SYMBOL_NAME(sys_signal) .long SYMBOL_NAME(sys_geteuid16) .long SYMBOL_NAME(sys_getegid16) /* 50 */ .long SYMBOL_NAME(sys_acct) .long SYMBOL_NAME(sys_umount) /* recycled never used phys() */ .long SYMBOL_NAME(sys_ni_syscall) /* old lock syscall holder */ .long SYMBOL_NAME(sys_ioctl) .long SYMBOL_NAME(sys_fcntl) /* 55 */ .long SYMBOL_NAME(sys_ni_syscall) /* old mpx syscall holder */ .long SYMBOL_NAME(sys_setpgid) .long SYMBOL_NAME(sys_ni_syscall) /* old ulimit syscall holder */ .long SYMBOL_NAME(sys_olduname) .long SYMBOL_NAME(sys_umask) /* 60 */ .long SYMBOL_NAME(sys_chroot) .long SYMBOL_NAME(sys_ustat) .long SYMBOL_NAME(sys_dup2) .long SYMBOL_NAME(sys_getppid) .long SYMBOL_NAME(sys_getpgrp) /* 65 */ .long SYMBOL_NAME(sys_setsid) .long SYMBOL_NAME(sys_sigaction) .long SYMBOL_NAME(sys_sgetmask) .long SYMBOL_NAME(sys_ssetmask) .long SYMBOL_NAME(sys_setreuid16) /* 70 */ .long SYMBOL_NAME(sys_setregid16) .long SYMBOL_NAME(sys_sigsuspend) .long SYMBOL_NAME(sys_sigpending) .long SYMBOL_NAME(sys_sethostname) .long SYMBOL_NAME(sys_setrlimit) /* 75 */ .long SYMBOL_NAME(sys_old_getrlimit) .long SYMBOL_NAME(sys_getrusage) .long SYMBOL_NAME(sys_gettimeofday) .long SYMBOL_NAME(sys_settimeofday) .long SYMBOL_NAME(sys_getgroups16) /* 80 */ .long SYMBOL_NAME(sys_setgroups16) .long SYMBOL_NAME(old_select) .long SYMBOL_NAME(sys_symlink) .long SYMBOL_NAME(sys_lstat) .long SYMBOL_NAME(sys_readlink) /* 85 */ .long SYMBOL_NAME(sys_uselib) .long SYMBOL_NAME(sys_swapon) .long SYMBOL_NAME(sys_reboot) .long SYMBOL_NAME(old_readdir) .long SYMBOL_NAME(old_mmap) /* 90 */ .long SYMBOL_NAME(sys_munmap) .long SYMBOL_NAME(sys_truncate) .long SYMBOL_NAME(sys_ftruncate) .long SYMBOL_NAME(sys_fchmod) .long SYMBOL_NAME(sys_fchown16) /* 95 */ .long SYMBOL_NAME(sys_getpriority) .long SYMBOL_NAME(sys_setpriority) .long SYMBOL_NAME(sys_ni_syscall) /* old profil syscall holder */ .long SYMBOL_NAME(sys_statfs) .long SYMBOL_NAME(sys_fstatfs) /* 100 */ .long SYMBOL_NAME(sys_ioperm) .long SYMBOL_NAME(sys_socketcall) .long SYMBOL_NAME(sys_syslog) .long SYMBOL_NAME(sys_setitimer) .long SYMBOL_NAME(sys_getitimer) /* 105 */ .long SYMBOL_NAME(sys_newstat) .long SYMBOL_NAME(sys_newlstat) .long SYMBOL_NAME(sys_newfstat) .long SYMBOL_NAME(sys_uname) .long SYMBOL_NAME(sys_iopl) /* 110 */ .long SYMBOL_NAME(sys_vhangup) .long SYMBOL_NAME(sys_ni_syscall) /* old "idle" system call */ .long SYMBOL_NAME(sys_vm86old) .long SYMBOL_NAME(sys_wait4) .long SYMBOL_NAME(sys_swapoff) /* 115 */ .long SYMBOL_NAME(sys_sysinfo) .long SYMBOL_NAME(sys_ipc) .long SYMBOL_NAME(sys_fsync) .long SYMBOL_NAME(sys_sigreturn) .long SYMBOL_NAME(sys_clone) /* 120 */ .long SYMBOL_NAME(sys_setdomainname) .long SYMBOL_NAME(sys_newuname) .long SYMBOL_NAME(sys_modify_ldt) .long SYMBOL_NAME(sys_adjtimex) .long SYMBOL_NAME(sys_mprotect) /* 125 */ .long SYMBOL_NAME(sys_sigprocmask) .long SYMBOL_NAME(sys_create_module) .long SYMBOL_NAME(sys_init_module) .long SYMBOL_NAME(sys_delete_module) .long SYMBOL_NAME(sys_get_kernel_syms) /* 130 */ .long SYMBOL_NAME(sys_quotactl) .long SYMBOL_NAME(sys_getpgid) .long SYMBOL_NAME(sys_fchdir) .long SYMBOL_NAME(sys_bdflush) .long SYMBOL_NAME(sys_sysfs) /* 135 */ .long SYMBOL_NAME(sys_personality) .long SYMBOL_NAME(sys_ni_syscall) /* for afs_syscall */ .long SYMBOL_NAME(sys_setfsuid16) .long SYMBOL_NAME(sys_setfsgid16) .long SYMBOL_NAME(sys_llseek) /* 140 */ .long SYMBOL_NAME(sys_getdents) .long SYMBOL_NAME(sys_select) .long SYMBOL_NAME(sys_flock) .long SYMBOL_NAME(sys_msync) .long SYMBOL_NAME(sys_readv) /* 145 */ .long SYMBOL_NAME(sys_writev) .long SYMBOL_NAME(sys_getsid) .long SYMBOL_NAME(sys_fdatasync) .long SYMBOL_NAME(sys_sysctl) .long SYMBOL_NAME(sys_mlock) /* 150 */ .long SYMBOL_NAME(sys_munlock) .long SYMBOL_NAME(sys_mlockall) .long SYMBOL_NAME(sys_munlockall) .long SYMBOL_NAME(sys_sched_setparam) .long SYMBOL_NAME(sys_sched_getparam) /* 155 */ .long SYMBOL_NAME(sys_sched_setscheduler) .long SYMBOL_NAME(sys_sched_getscheduler) .long SYMBOL_NAME(sys_sched_yield) .long SYMBOL_NAME(sys_sched_get_priority_max) .long SYMBOL_NAME(sys_sched_get_priority_min) /* 160 */ .long SYMBOL_NAME(sys_sched_rr_get_interval) .long SYMBOL_NAME(sys_nanosleep) .long SYMBOL_NAME(sys_mremap) .long SYMBOL_NAME(sys_setresuid16) .long SYMBOL_NAME(sys_getresuid16) /* 165 */ .long SYMBOL_NAME(sys_vm86) .long SYMBOL_NAME(sys_query_module) .long SYMBOL_NAME(sys_poll) .long SYMBOL_NAME(sys_nfsservctl) .long SYMBOL_NAME(sys_setresgid16) /* 170 */ .long SYMBOL_NAME(sys_getresgid16) .long SYMBOL_NAME(sys_prctl) .long SYMBOL_NAME(sys_rt_sigreturn) .long SYMBOL_NAME(sys_rt_sigaction) .long SYMBOL_NAME(sys_rt_sigprocmask) /* 175 */ .long SYMBOL_NAME(sys_rt_sigpending) .long SYMBOL_NAME(sys_rt_sigtimedwait) .long SYMBOL_NAME(sys_rt_sigqueueinfo) .long SYMBOL_NAME(sys_rt_sigsuspend) .long SYMBOL_NAME(sys_pread) /* 180 */ .long SYMBOL_NAME(sys_pwrite) .long SYMBOL_NAME(sys_chown16) .long SYMBOL_NAME(sys_getcwd) .long SYMBOL_NAME(sys_capget) .long SYMBOL_NAME(sys_capset) /* 185 */ .long SYMBOL_NAME(sys_sigaltstack) .long SYMBOL_NAME(sys_sendfile) .long SYMBOL_NAME(sys_ni_syscall) /* streams1 */ .long SYMBOL_NAME(sys_ni_syscall) /* streams2 */ .long SYMBOL_NAME(sys_vfork) /* 190 */ .long SYMBOL_NAME(sys_getrlimit) .long SYMBOL_NAME(sys_mmap2) .long SYMBOL_NAME(sys_truncate64) .long SYMBOL_NAME(sys_ftruncate64) .long SYMBOL_NAME(sys_stat64) /* 195 */ .long SYMBOL_NAME(sys_lstat64) .long SYMBOL_NAME(sys_fstat64) .long SYMBOL_NAME(sys_lchown) .long SYMBOL_NAME(sys_getuid) .long SYMBOL_NAME(sys_getgid) /* 200 */ .long SYMBOL_NAME(sys_geteuid) .long SYMBOL_NAME(sys_getegid) .long SYMBOL_NAME(sys_setreuid) .long SYMBOL_NAME(sys_setregid) .long SYMBOL_NAME(sys_getgroups) /* 205 */ .long SYMBOL_NAME(sys_setgroups) .long SYMBOL_NAME(sys_fchown) .long SYMBOL_NAME(sys_setresuid) .long SYMBOL_NAME(sys_getresuid) .long SYMBOL_NAME(sys_setresgid) /* 210 */ .long SYMBOL_NAME(sys_getresgid) .long SYMBOL_NAME(sys_chown) .long SYMBOL_NAME(sys_setuid) .long SYMBOL_NAME(sys_setgid) .long SYMBOL_NAME(sys_setfsuid) /* 215 */ .long SYMBOL_NAME(sys_setfsgid) .long SYMBOL_NAME(sys_pivot_root) .long SYMBOL_NAME(sys_mincore) .long SYMBOL_NAME(sys_madvise) .long SYMBOL_NAME(sys_getdents64) /* 220 */ .long SYMBOL_NAME(sys_fcntl64) .long SYMBOL_NAME(sys_ni_syscall) /* reserved for TUX */ .long SYMBOL_NAME(sys_ni_syscall) /* Reserved for Security */ .long SYMBOL_NAME(sys_gettid) .long SYMBOL_NAME(sys_readahead) /* 225 */
当 IRQ 发生时,正在运行的任务会被中断,以便服务 IRQ 处理程序。
在 IRQ 被处理后,控制权会精确地返回到中断点,就像什么都没发生一样。
Running Task |-----------| (3) NORMAL | | | [break execution] IRQ Handler EXECUTION (1)| | | ------------->|---------| | \|/ | | | does | IRQ (2)---->| .. |-----> | some | | | |<----- | work | BACK TO | | | | | ..(4). | NORMAL (6)| \|/ | <-------------|_________| EXECUTION |___________| [return to code] (5) USER MODE KERNEL MODE User->Kernel Mode Transition caused by IRQ event
以下编号的步骤指的是上面图表中的事件序列
定时器 IRQ 非常重要,它每 TIMER 毫秒发生一次,用于管理
现代操作系统的关键点是“任务”。任务是在内存中运行的应用程序,与其他任务共享所有资源 (包括 CPU 和内存)。
这种“资源共享”由“多任务处理机制”管理。多任务处理机制在“时间片”时间后从一个任务切换到另一个任务。用户有一种“错觉”,认为他们拥有所有资源。我们也可以想象一个单用户场景,用户可以“错觉”同时运行多个任务。
为了实现这种多任务处理,任务使用“状态”变量,它可以是
任务状态由其在相关列表中的存在来管理:READY 列表和 BLOCKED 列表。
从一个任务到另一个任务的移动称为“任务切换”。许多计算机都有硬件指令自动执行此操作。任务切换发生在以下情况下
* 我们调度另一个任务以防止“忙等待”,这种情况发生在当我们等待设备而不是执行其他工作时。
任务切换由“调度”实体管理。
Timer | | IRQ | | Schedule | | | ________________________ |----->| Task 1 |<------------------>|(1)Chooses a Ready Task | | | | |(2)Task Switching | | |___________| |________________________| | | | /|\ | | | | | | | | | | | | | | | | |----->| Task 2 |<-------------------------------| | | | | | |___________| | . . . . . . . . . . . . . . . | | | | | | | | ------>| Task N |<-------------------------------- | | |___________| Task Switching based on TimeSlice
Linux 的典型时间片约为 10 毫秒。
| | | | Resource _____________________________ | Task 1 |----------->|(1) Enqueue Resource request | | | Access |(2) Mark Task as blocked | | | |(3) Choose a Ready Task | |___________| |(4) Task Switching | |_____________________________| | | | | | | | | | Task 2 |<------------------------- | | | | |___________| Task Switching based on Waiting for a Resource
到目前为止,我们已经了解了所谓的单内核操作系统,但还有另一种操作系统:''微内核''。
微内核操作系统使用任务,不仅用于用户模式进程,还用作真正的内核管理器,例如 Floppy-Task、HDD-Task、Net-Task 等等。一些例子是 Amoeba 和 Mach。
优点
缺点
我个人的观点是,微内核是一个很好的教学示例 (如 Minix),但它们不是“最优的”,因此不太适用。Linux 使用一些称为“内核线程”的任务来实现一个小的微内核结构 (例如 kswapd,它用于从大容量存储中检索内存页)。在这种情况下,性能没有问题,因为交换是一个非常缓慢的工作。
标准 ISO-OSI 描述了一个具有以下层级的网络架构
上面列出的前 2 层通常在硬件中实现。接下来的层在软件中 (或路由器的固件中)。
操作系统使用许多协议:其中之一是 TCP/IP (最重要的位于 3-4 层)。
内核对 ISO-OSI 的前 2 层一无所知 (只知道地址)。
在 RX 中
frames packets sockets NIC ---------> Kernel ----------> Application | packets --------------> Forward - RX -
在 TX 阶段
sockets packets frames Application ---------> Kernel ----------> NIC packets /|\ Forward ------------------- - TX -
分段是解决内存分配问题的第一个方法:它允许您编译源代码,而无需关心应用程序将放置在内存中的哪个位置。事实上,此功能帮助应用程序开发人员以独立于操作系统和硬件的方式进行开发。
| Stack | | | | | \|/ | | Free | | /|\ | Segment <---> Process | | | | Heap | | Data uninitialized | | Data initialized | | Code | |____________________| Segment
我们可以说,段是应用程序的逻辑实体,或者应用程序在内存中的映像。
在编程时,我们不关心我们的数据放在内存中的哪个位置,我们只关心段 (我们的应用程序) 内部的偏移量。
我们过去常常为每个进程分配一个段,反之亦然。但在 Linux 中,情况并非如此。Linux 仅为内核和所有进程使用 4 个段。
____________________ ----->| |-----> | IN | Segment A | OUT ____________________ | |____________________| | |____| | | | Segment B | | Segment B | | |____ | | |____________________| | |____________________| | | Segment C | | |____________________| ----->| Segment D |-----> IN |____________________| OUT Segmentation problem
在上面的图中,我们想要退出进程 A 和 D 并进入进程 B。正如我们所见,B 有足够的空间,但我们不能将其分成 2 块,因此我们无法加载它 (内存不足)。
出现此问题的原因是纯段是连续区域 (因为它们是逻辑区域),并且不能被拆分。
____________________ | Page 1 | |____________________| | Page 2 | |____________________| | .. | Segment <---> Process |____________________| | Page n | |____________________| | | |____________________| | | |____________________| Segment
分页将内存分成“n”块,每块都有固定的长度。
一个进程可以加载到一个或多个页中。当内存被释放时,所有页都被释放 (参见之前的分段问题)。
分页还用于另一个重要目的,“交换”。如果页不存在于物理内存中,则会生成一个异常,这将使内核在存储内存中搜索新页。这种机制允许操作系统加载比仅物理内存允许的更多的应用程序。
____________________ Page X | Process Y | |____________________| | | | WASTE | | SPACE | |____________________| Pagination Problem
在上面的图中,我们可以看到分页策略的错误之处:当进程 Y 加载到页 X 中时,页的所有内存空间都被分配,因此页末尾的剩余空间被浪费了。
我们如何解决分段和分页问题?使用这两种策略。
| .. | |____________________| ----->| Page 1 | | |____________________| | | .. | ____________________ | |____________________| | | |---->| Page 2 | | Segment X | ----| |____________________| | | | | .. | |____________________| | |____________________| | | .. | | |____________________| |---->| Page 3 | |____________________| | .. |
进程 X,由段 X 标识,被分成 3 块,并且每一块都加载到一个页中。
我们没有
| | | | | | Offset2 | Value | | | /|\| | Offset1 | |----- | | | /|\ | | | | | | | | | | \|/| | | | | ------>| | \|/ | | | | Base Paging Address ---->| | | | | ....... | | ....... | | | | | Hierarchical Paging