diff --git a/操作系统/2.1 进程的基本概念.md b/操作系统/2.1 进程的基本概念.md index 779d4514..9d90d569 100644 --- a/操作系统/2.1 进程的基本概念.md +++ b/操作系统/2.1 进程的基本概念.md @@ -41,7 +41,7 @@ * 结构特征:由程序段、数据段和 PCB 三部分便构成了进程实体。 * 并发性:这是指多个进程实体同存于内存中,且能在一段时间内同时运行。 -* 动态性:进程的实质是进程实体的一次执行过程,包含创建、调度、撤销。进程具有一定的声明周期。程序是一组指令的集合,是静态的。 +* 动态性:进程的实质是进程实体的一次执行过程,包含创建、调度、撤销。进程具有一定的生命周期。程序是一组指令的集合,是静态的。 * 独立性:独立性是指进程实体是一个能独立运行、独立分配资源和独立接受调度的基本单位。 * 异步性:这是指进程按各自独立的、 不可预知的速度向前推进,或说进程实体按异步方式运行。 @@ -49,7 +49,7 @@ ### 进程的构成 -1. (OS管理程序的)数据结构PCP +1. (OS管理程序的)数据结构PCB 2. (运行程序的)内存代码段Code 3. (运行程序的)内存数据段Data 4. (运行程序的)通用寄存器信息Register @@ -136,3 +136,332 @@ ![](image/2021-03-30-12-58-03.png) +```C +struct task_struct { + volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ + void *stack; + atomic_t usage; + unsigned int flags; /* per process flags, defined below */ + unsigned int ptrace; + + int lock_depth; /* BKL lock depth */ + +#ifdef CONFIG_SMP +#ifdef __ARCH_WANT_UNLOCKED_CTXSW + int oncpu; +#endif +#endif + + int prio, static_prio, normal_prio; + unsigned int rt_priority; + const struct sched_class *sched_class; + struct sched_entity se; + struct sched_rt_entity rt; + +#ifdef CONFIG_PREEMPT_NOTIFIERS + /* list of struct preempt_notifier: */ + struct hlist_head preempt_notifiers; +#endif + + /* + * fpu_counter contains the number of consecutive context switches + * that the FPU is used. If this is over a threshold, the lazy fpu + * saving becomes unlazy to save the trap. This is an unsigned char + * so that after 256 times the counter wraps and the behavior turns + * lazy again; this to deal with bursty apps that only use FPU for + * a short time + */ + unsigned char fpu_counter; +#ifdef CONFIG_BLK_DEV_IO_TRACE + unsigned int btrace_seq; +#endif + + unsigned int policy; + cpumask_t cpus_allowed; + +#ifdef CONFIG_TREE_PREEMPT_RCU + int rcu_read_lock_nesting; + char rcu_read_unlock_special; + struct rcu_node *rcu_blocked_node; + struct list_head rcu_node_entry; +#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */ + +#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) + struct sched_info sched_info; +#endif + + struct list_head tasks; + struct plist_node pushable_tasks; + + struct mm_struct *mm, *active_mm; + +/* task state */ + int exit_state; + int exit_code, exit_signal; + int pdeath_signal; /* The signal sent when the parent dies */ + unsigned int personality; + unsigned did_exec:1; + unsigned in_execve:1; /* Tell the LSMs that the process is doing an + * execve */ + unsigned in_iowait:1; + + + /* Revert to default priority/policy when forking */ + unsigned sched_reset_on_fork:1; + + pid_t pid; + pid_t tgid; + +#ifdef CONFIG_CC_STACKPROTECTOR + /* Canary value for the -fstack-protector gcc feature */ + unsigned long stack_canary; +#endif + + /* + * pointers to (original) parent process, youngest child, younger sibling, + * older sibling, respectively. (p->father can be replaced with + * p->real_parent->pid) + */ + struct task_struct *real_parent; /* real parent process */ + struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */ + /* + * children/sibling forms the list of my natural children + */ + struct list_head children; /* list of my children */ + struct list_head sibling; /* linkage in my parent's children list */ + struct task_struct *group_leader; /* threadgroup leader */ + + /* + * ptraced is the list of tasks this task is using ptrace on. + * This includes both natural children and PTRACE_ATTACH targets. + * p->ptrace_entry is p's link on the p->parent->ptraced list. + */ + struct list_head ptraced; + struct list_head ptrace_entry; + + /* + * This is the tracer handle for the ptrace BTS extension. + * This field actually belongs to the ptracer task. + */ + struct bts_context *bts; + + /* PID/PID hash table linkage. */ + struct pid_link pids[PIDTYPE_MAX]; + struct list_head thread_group; + + struct completion *vfork_done; /* for vfork() */ + int __user *set_child_tid; /* CLONE_CHILD_SETTID */ + int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */ + + cputime_t utime, stime, utimescaled, stimescaled; + cputime_t gtime; + cputime_t prev_utime, prev_stime; + unsigned long nvcsw, nivcsw; /* context switch counts */ + struct timespec start_time; /* monotonic time */ + struct timespec real_start_time; /* boot based time */ +/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ + unsigned long min_flt, maj_flt; + + struct task_cputime cputime_expires; + struct list_head cpu_timers[3]; + +/* process credentials */ + const struct cred *real_cred; /* objective and real subjective task + * credentials (COW) */ + const struct cred *cred; /* effective (overridable) subjective task + * credentials (COW) */ + struct mutex cred_guard_mutex; /* guard against foreign influences on + * credential calculations + * (notably. ptrace) */ + struct cred *replacement_session_keyring; /* for KEYCTL_SESSION_TO_PARENT */ + + char comm[TASK_COMM_LEN]; /* executable name excluding path + - access with [gs]et_task_comm (which lock + it with task_lock()) + - initialized normally by flush_old_exec */ +/* file system info */ + int link_count, total_link_count; +#ifdef CONFIG_SYSVIPC +/* ipc stuff */ + struct sysv_sem sysvsem; +#endif +#ifdef CONFIG_DETECT_HUNG_TASK +/* hung task detection */ + unsigned long last_switch_count; +#endif +/* CPU-specific state of this task */ + struct thread_struct thread; +/* filesystem information */ + struct fs_struct *fs; +/* open file information */ + struct files_struct *files; +/* namespaces */ + struct nsproxy *nsproxy; +/* signal handlers */ + struct signal_struct *signal; + struct sighand_struct *sighand; + + sigset_t blocked, real_blocked; + sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ + struct sigpending pending; + + unsigned long sas_ss_sp; + size_t sas_ss_size; + int (*notifier)(void *priv); + void *notifier_data; + sigset_t *notifier_mask; + struct audit_context *audit_context; +#ifdef CONFIG_AUDITSYSCALL + uid_t loginuid; + unsigned int sessionid; +#endif + seccomp_t seccomp; + +/* Thread group tracking */ + u32 parent_exec_id; + u32 self_exec_id; +/* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed, + * mempolicy */ + spinlock_t alloc_lock; + +#ifdef CONFIG_GENERIC_HARDIRQS + /* IRQ handler threads */ + struct irqaction *irqaction; +#endif + + /* Protection of the PI data structures: */ + spinlock_t pi_lock; + +#ifdef CONFIG_RT_MUTEXES + /* PI waiters blocked on a rt_mutex held by this task */ + struct plist_head pi_waiters; + /* Deadlock detection and priority inheritance handling */ + struct rt_mutex_waiter *pi_blocked_on; +#endif + +#ifdef CONFIG_DEBUG_MUTEXES + /* mutex deadlock detection */ + struct mutex_waiter *blocked_on; +#endif +#ifdef CONFIG_TRACE_IRQFLAGS + unsigned int irq_events; + int hardirqs_enabled; + unsigned long hardirq_enable_ip; + unsigned int hardirq_enable_event; + unsigned long hardirq_disable_ip; + unsigned int hardirq_disable_event; + int softirqs_enabled; + unsigned long softirq_disable_ip; + unsigned int softirq_disable_event; + unsigned long softirq_enable_ip; + unsigned int softirq_enable_event; + int hardirq_context; + int softirq_context; +#endif +#ifdef CONFIG_LOCKDEP +# define MAX_LOCK_DEPTH 48UL + u64 curr_chain_key; + int lockdep_depth; + unsigned int lockdep_recursion; + struct held_lock held_locks[MAX_LOCK_DEPTH]; + gfp_t lockdep_reclaim_gfp; +#endif + +/* journalling filesystem info */ + void *journal_info; + +/* stacked block device info */ + struct bio *bio_list, **bio_tail; + +/* VM state */ + struct reclaim_state *reclaim_state; + + struct backing_dev_info *backing_dev_info; + + struct io_context *io_context; + + unsigned long ptrace_message; + siginfo_t *last_siginfo; /* For ptrace use. */ + struct task_io_accounting ioac; +#if defined(CONFIG_TASK_XACCT) + u64 acct_rss_mem1; /* accumulated rss usage */ + u64 acct_vm_mem1; /* accumulated virtual memory usage */ + cputime_t acct_timexpd; /* stime + utime since last update */ +#endif +#ifdef CONFIG_CPUSETS + nodemask_t mems_allowed; /* Protected by alloc_lock */ + int cpuset_mem_spread_rotor; +#endif +#ifdef CONFIG_CGROUPS + /* Control Group info protected by css_set_lock */ + struct css_set *cgroups; + /* cg_list protected by css_set_lock and tsk->alloc_lock */ + struct list_head cg_list; +#endif +#ifdef CONFIG_FUTEX + struct robust_list_head __user *robust_list; +#ifdef CONFIG_COMPAT + struct compat_robust_list_head __user *compat_robust_list; +#endif + struct list_head pi_state_list; + struct futex_pi_state *pi_state_cache; +#endif +#ifdef CONFIG_PERF_EVENTS + struct perf_event_context *perf_event_ctxp; + struct mutex perf_event_mutex; + struct list_head perf_event_list; +#endif +#ifdef CONFIG_NUMA + struct mempolicy *mempolicy; /* Protected by alloc_lock */ + short il_next; +#endif + atomic_t fs_excl; /* holding fs exclusive resources */ + struct rcu_head rcu; + + /* + * cache last used pipe for splice + */ + struct pipe_inode_info *splice_pipe; +#ifdef CONFIG_TASK_DELAY_ACCT + struct task_delay_info *delays; +#endif +#ifdef CONFIG_FAULT_INJECTION + int make_it_fail; +#endif + struct prop_local_single dirties; +#ifdef CONFIG_LATENCYTOP + int latency_record_count; + struct latency_record latency_record[LT_SAVECOUNT]; +#endif + /* + * time slack values; these are used to round up poll() and + * select() etc timeout values. These are in nanoseconds. + */ + unsigned long timer_slack_ns; + unsigned long default_timer_slack_ns; + + struct list_head *scm_work_list; +#ifdef CONFIG_FUNCTION_GRAPH_TRACER + /* Index of current stored adress in ret_stack */ + int curr_ret_stack; + /* Stack of return addresses for return function tracing */ + struct ftrace_ret_stack *ret_stack; + /* time stamp for last schedule */ + unsigned long long ftrace_timestamp; + /* + * Number of functions that haven't been traced + * because of depth overrun. + */ + atomic_t trace_overrun; + /* Pause for the tracing */ + atomic_t tracing_graph_pause; +#endif +#ifdef CONFIG_TRACING + /* state flags for use by tracers */ + unsigned long trace; + /* bitmask of trace recursion */ + unsigned long trace_recursion; +#endif /* CONFIG_TRACING */ + unsigned long stack_start; +}; +``` \ No newline at end of file diff --git a/操作系统/2.3 进程同步.md b/操作系统/2.3 进程同步.md index ed747f25..944e20ee 100644 --- a/操作系统/2.3 进程同步.md +++ b/操作系统/2.3 进程同步.md @@ -5,7 +5,7 @@ ### 两种制约关系 > 针对两种制约关系,合作制约关系和互斥制约关系,需要通过同步机制实现。 -- 直接制约关系(同步)。由于多个进程相互合作产生,使得进程有一定的先后执行关系。 +- 直接制约关系(合作)。由于多个进程相互合作产生,使得进程有一定的先后执行关系。 - 间接制约关系(互斥)。由于多个进程资源共享产生,多个进程在同一时刻只有一个进程能进入临界区。 @@ -79,6 +79,7 @@ void P2() { } ``` +### 进程同步机制——条件变量 ### 进程同步机制——信号 diff --git a/操作系统/2.4 进程通信.md b/操作系统/2.4 进程通信.md index 62bfae91..c076f678 100644 --- a/操作系统/2.4 进程通信.md +++ b/操作系统/2.4 进程通信.md @@ -7,7 +7,7 @@ ## 0 进程通信的定义 ### 进程通信和进程同步的区别 -* 主要的区别在于:进程同步控制多个进程按一定顺序执行;进程通信,实现进程间信息交换。 +* 主要的区别在于:进程同步控制多个进程按一定顺序执行;进程通信,实现进程间信息交换。目标是不一样的。 * 进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。 diff --git a/操作系统/3 处理机管理.md b/操作系统/3 处理机管理.md index 6db3e1b8..6922c106 100644 --- a/操作系统/3 处理机管理.md +++ b/操作系统/3 处理机管理.md @@ -18,8 +18,7 @@ * **作业(Job)** 。作业是一个比程序更为广泛的概念,它不仅包含了通常的程序和数据,而且还应配有一份作业说明书,系统根据该说明书来对程序的运行进行控制。在批处理系统中,是以作业为基本单位从外存调入内存的。 * **作业步(Job Step)** 。作业部类似于操作系统命令脚本。在作业运行期间,每个作业都必须经过若干个相对独立,又相互关联的顺序加工步骤才能得到结果,我们把其中的每一个加工步骤称为一个作业步。例如编译执行过程: 1. “编译”作业步,通过执行编译程序对源程序进行编译,产生若干个目标程序段;② “连结装配”作业步,将“编译”作业步所产生的若干个目标程序段装配成可执行的目标程序; - 2. “运行”作业步,将可执行的目标程序 - 3. 读入内存并控制其运行。 + 2. “运行”作业步,将可执行的目标程序读入内存并控制其运行。 * **作业控制块 JCB(Job Control Block)** 。为了管理和调度作业,在多道批处理系统中为每个作业设置了一个作业控制块。 * **作业调度**。作业调度的主要功能是根据作业控制块中的信息,审查系统能否满足用户作业的资源需求,以及按照一定的算法,从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。 @@ -66,11 +65,11 @@ * 每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。 * FCFS 算法比较有利于长作业(进程),而不利于短作业(进程)。 -### 短作业优先调度算法SJB +### 短作业优先调度算法SJF * 短作业(进程)优先调度算法 SJ(P)F,是指对短作业或短进程优先调度的算法。短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。 * SJF 调度算法能有效地降低作业的平均等待时间,提高系统吞吐量。 -### 优先权调度算法 +### 优先级调度算法 * 根据分配给进程的优先数决定运行进程。可以分为以下两种方式: 1. 抢占式优先数调度算法 2. 非抢占式优先数调度算法 @@ -82,15 +81,16 @@ 4. 进程进入系统的时间长短 * 与进入系统时间相关的优先权 - 1. 计算时间短(作业/进程)优先 - 2. 剩余计算时间短进程优先 - 3. 响应比高者(作业/进程)优先 - 4. 先来先服务:先进入先被选择 + 1. 先来先服务 + 2. 短作业/短进程优先 + 3. 最短剩余时间优先 + 4. 响应比高(作业/进程)优先 ### 时间片轮转调度算法 -1. 根据各个进程进入就绪队列的时间先后轮流占用CPU一个时间片.在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,并令其执行一个时间片。 +1. 根据各个进程进入就绪队列的时间先后轮流占用CPU一个时间片。 +2. 在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,并令其执行一个时间片。 ### 分级调度算法(多队列策略,反馈循环队列) @@ -137,7 +137,7 @@ * 交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。 -1. 时间片轮转 +1. 时间片轮转调度算法 * 将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。 @@ -146,11 +146,12 @@ - 而如果时间片过长,那么实时性就不能得到保证。 ![](image/2021-03-29-22-56-33.png) -2. 优先级调度 +2. 优先级调度算法 - * 为每个进程分配一个优先级,按优先级进行调度。为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。 + * 为每个进程分配一个优先级,按优先级进行调度。 + * 动态优先级:为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。 -3. 多级反馈队列 +3. 分级调度算法——多级反馈队列 * 一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。 * 每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。 @@ -170,8 +171,8 @@ ### 具体算法 -1. 最早截止时间优先即 EDF(Earliest Deadline First)算法 -2. 最低松弛度优先即 LLF(Least Laxity First)算法 +1. 最早截止时间优先 EDF(Earliest Deadline First)算法 +2. 最低松弛度优先 LLF(Least Laxity First)算法 @@ -259,7 +260,7 @@ * 每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。 -### 每种类型多个资源的死锁检测 +### 每种类型多个资源的死锁检测(银行家算法) ![](image/2021-03-30-16-23-30.png) diff --git a/操作系统/4.3 分页存储管理.md b/操作系统/4.3 分页存储管理.md index ebb31cc9..c5c9be54 100644 --- a/操作系统/4.3 分页存储管理.md +++ b/操作系统/4.3 分页存储管理.md @@ -95,7 +95,7 @@ ### 4.4 时钟CLOCK==最近未使用NRU, Not Recently Used - +**第一个解析** 每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类: - R=0,M=0 @@ -107,27 +107,31 @@ NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。 -> 算法执行如下操作步骤: +**算法执行如下操作步骤**: -从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不做任何修改。选择遇到的第一个帧(u=0, m=0)用于替换。 -如果第1)步失败,则重新扫描,查找(u=0, m=1)的帧。选择遇到的第一个这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成0。 -如果第2)步失败,指针将回到它的最初位置,并且集合中所有帧的使用位均为0。重复第1步,并且如果有必要,重复第2步。这样将可以找到供替换的帧 -> 第二个解析 +1. 从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不做任何修改。选择遇到的第一个帧(u=0, m=0)用于替换。 +2. 如果第1)步失败,则重新扫描,查找(u=0, m=1)的帧。选择遇到的第一个这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成0。 +3. 如果第2)步失败,指针将回到它的最初位置,并且集合中所有帧的使用位均为0。重复第1步,并且如果有必要,重复第2步。这样将可以找到供替换的帧 + + +**第二个解析** 1. 第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。 2. 采用循环队列机制构造页面队列,形成了一个类似于钟表面的环形表 3. 队列指针则相当于钟表面上的表针,指向可能要淘汰的页面 4. 使用页引用标志位 -5. 工作流程: - 1. 页面调入主存时,其引用标志位置1 - 2. 访问主存页面时,其引用标志位置1 - 3. 淘汰页面时,从指针当前指向的页面开始扫描循环队列 - 1. 把所遇到的引用标志位是1的页面的引用标志位清0,并跳过 - 2. 把所遇到的引用标志位是0的页面淘汰,指针推进一步 + +工作流程: +1. 页面调入主存时,其引用标志位置1 +2. 访问主存页面时,其引用标志位置1 +3. 淘汰页面时,从指针当前指向的页面开始扫描循环队列 + 1. 把所遇到的引用标志位是1的页面的引用标志位清0,并跳过 + 2. 把所遇到的引用标志位是0的页面淘汰,指针推进一步 ![](image/2021-04-06-00-22-23.png) -### 4.3 第二次机会算法 + +**第三个解析** FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改: diff --git a/操作系统/4.5 虚拟存储器.md b/操作系统/4.5 虚拟存储器.md index 5aa3525d..9f6aa2ae 100644 --- a/操作系统/4.5 虚拟存储器.md +++ b/操作系统/4.5 虚拟存储器.md @@ -4,9 +4,9 @@ ## 1 虚拟存储器 ### 引入 -* 虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。 -* 为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。 -* 从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。 +* 虚拟内存的目的是为了**让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存**。 +* 为了更好的管理内存,**操作系统将内存抽象成地址空间**。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。 +* 从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说**一个程序不需要全部调入内存就可以运行**,这使得**有限的内存运行大程序成为可能**。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。 ![](image/2021-03-30-21-09-50.png) @@ -38,4 +38,5 @@ ## 3 请求分页功能 ## 4 请求分段功能 - \ No newline at end of file + + diff --git a/操作系统/5.1 Linux IO模型.md b/操作系统/5.1 Linux IO模型.md index b4a8f411..f5ec3d19 100644 --- a/操作系统/5.1 Linux IO模型.md +++ b/操作系统/5.1 Linux IO模型.md @@ -66,7 +66,7 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * ## 3 I/O 复用(事件驱动IO) -* IO multiplexing这个词可能有点陌生,但是如果我说select/epoll,大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。 +* IO multiplexing即select/epoll方法。也称这种IO方式为事件驱动IO(event driven IO)。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。 * I/O多路复用。I/O指的是I/O事件(包括I/O读写、I/O异常等事件),多路指多个独立连接(或多个Channel),复用指多个事件复用一个控制流(线程或进程)。串起来理解就是很多个独立I/O事件的处理依赖于一个控制流。 * 主要是select、poll、epoll;对一个IO端口,两次调用,两次返回,比阻塞IO并没有什么优越性;关键是能实现同时对**多个IO端口进行监听**; * I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。 @@ -173,9 +173,9 @@ else * 从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。 ### select机制的问题 -* 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大 -* 同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大 -* 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024) +1. 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大 +2. 同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大 +3. 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024) ## 7.2 poll @@ -343,6 +343,8 @@ else 1. LT 模式。当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。 2. ET 模式。和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。 + + * 很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。 * LT和ET原本应该是用于脉冲信号的,可能用它来解释更加形象。Level和Edge指的就是触发点,Level为只要处于水平,那么就一直触发,而Edge则为上升沿和下降沿的时候触发。比如:0->1 就是Edge,1->1 就是Level。 diff --git a/操作系统/5.3 IO多路复用模型.md b/操作系统/5.3 IO多路复用模型.md index d7868283..0608fc2a 100644 --- a/操作系统/5.3 IO多路复用模型.md +++ b/操作系统/5.3 IO多路复用模型.md @@ -32,7 +32,7 @@ Reactor 模式是灵活多变的,可以应对不同的业务场景,灵活在 将上面的两个因素排列组设一下,理论上就可以有 4 种方案选择: * 单 Reactor 单进程 / 线程; * 单 Reactor 多进程 / 线程; -* ~~多 Reactor 单进程 / 线程;~~不仅复杂而且也没有性能优势,因此实际中并没有应用。 +* ~~多 Reactor 单进程 / 线程;~~ 不仅复杂而且也没有性能优势,因此实际中并没有应用。 * 多 Reactor 多进程 / 线程; Redis、Nginx、Netty都用到了reactor模式: @@ -270,9 +270,12 @@ Proactor 正是采用了异步 I/O 技术,所以被称为异步网络模型。 2. Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响) ### 优点 -1. Reactor实现相对简单,对于耗时短的处理场景处理高效;操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性;事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁;事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来, +1. Reactor实现相对简单,对于耗时短的处理场景处理高效; +2. 操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性; +3. 事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁; +4. 事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来, -2. Proactor性能更高,能够处理耗时长的并发场景; +5. Proactor性能更高,能够处理耗时长的并发场景; ### 缺点 1. Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理; diff --git a/操作系统/附录11 IO多路复用.md b/操作系统/5.4 IO多路复用与线程进程.md similarity index 92% rename from 操作系统/附录11 IO多路复用.md rename to 操作系统/5.4 IO多路复用与线程进程.md index 66f85a52..ea772fe9 100644 --- a/操作系统/附录11 IO多路复用.md +++ b/操作系统/5.4 IO多路复用与线程进程.md @@ -1,8 +1,9 @@ # IO多路复用 +## 1 单线程并发的含义 IO多路复用即单线程并发,事件驱动模型。有事件响应机制、事件回调机制等。 -单线程并发,并非真正意义上的单线程。而是只有单一的用户线程。还包括数据库,socket等系统多线程。 +**单线程并发,并非真正意义上的单线程。而是只有单一的用户线程。还包括数据库,socket等系统多线程。** 单个用户线程:对于十万个用户同时访问服务器,有两种方式处理并发。 diff --git a/操作系统/附录10 回调函数.md b/操作系统/5.5 IO多路复用与回调函数.md similarity index 93% rename from 操作系统/附录10 回调函数.md rename to 操作系统/5.5 IO多路复用与回调函数.md index e8ffe52e..6d22b74c 100644 --- a/操作系统/附录10 回调函数.md +++ b/操作系统/5.5 IO多路复用与回调函数.md @@ -1,6 +1,6 @@ -# 多线程与并发 +# IO多路复用与回调函数 -## 1 补充 +## 1 this指针 ### this指针 this关键字指向的是当前对象的引用 @@ -8,14 +8,14 @@ this 不是指向类。而是在实例化的时候与当前类的实例也就是 ### 关于回调 -对于Python与JavaScript这种,能够直接传递“函数类型”的参数的语言,回调函数可以作为另外一个函数的参数,进行传递。 +对于Python与JavaScript这种,能够直接传递“函数类型”的参数的语言,回调函数可以作为另外一个函数的参数,进行传递。(C++也是可以传递函数指针的) 在C++和java这种只能传递基本数据类型和构造数据类型的语言中,即函数不作为数据类型的语言中,可以通过传递整个对象的方法,使得被调用者,能够通过调用者对象的指针,进行回调,这也是设计模式的一种。(观察者模式?) -关于回调函数的本质理解:当其他程序执行时,能够通过回调函数,转移进程控制权限,给调用者(这里并没有转移控制权,而是保留控制权在本地,完成不可知的后续处理)。可以把调用者与被调用者分开。调用者能够显式调用被调用者,同时被调用这能通过回调函数隐式调用调用者。 +关于回调函数的本质理解:当其他程序执行时,能够通过回调函数,转移进程控制权限,给调用者(这里并没有转移控制权,而是保留控制权在本地,完成不可知的后续处理)。可以把调用者与被调用者分开。调用者能够显式调用被调用者,同时被调用这能通过回调函数隐式调用调用者。但是实在另一个线程中执行。 -关于回调函数的本质理解:回调函数在同一个层次内是没有用的,或者说没有必要的。如果一个人,在实现一个层次内部的函数执行权限转移过程中,可以直接进行相互调用,因为本质上,两个部分是完全可知的。但是在不同层次的调用中,例如用户层与系统层中,用户层调用系统层,可以通过显式调用,但是系统层并不会考虑用户层的具体实现,对用户层是不可知的。这个时候,系统层会通过传递来的回调函数,将进程控制权交给用户层。 +关于回调函数的本质理解:回调函数在同一个层次内是没有用的,或者说没有必要的。如果一个人,在实现一个层次内部的函数执行权限转移过程中,可以直接进行相互调用,因为本质上,两个部分是完全可知的。但是在不同层次的调用中,例如用户层与系统层中,用户层调用系统层,可以通过显式调用,但是系统层并不会考虑用户层的具体实现,对用户层是不可知的。这个时候,系统层会通过传递来的回调函数,将进程控制权交给用户层。(并不会转移控制权,交给其他的线程) 那么问题来了,直接通过返回值的方式转移进程的控制权限不好吗。在回调的部分通过返回值,区分不同的情况,用户自己选择执行什么样的函数。 diff --git a/操作系统/6 文件系统.md b/操作系统/6 文件系统.md index 5f38525d..a2df75ed 100644 --- a/操作系统/6 文件系统.md +++ b/操作系统/6 文件系统.md @@ -10,10 +10,22 @@ > 7. 数据一致性控制 ## 1 文件 + + +### 为什么说Linux一切皆文件 + +1. Linux世界中的所有、任意、一切东西都可以通过文件的方式访问、管理。任何东西都挂在文件系统之上,即使它们不是文件,也以文件的形式来呈现。开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源。 +2. 进程(/proc)、设备(/dev)、Socket等等,实际上都不是文件,但是你可以以文件系统的规范来访问它,修改属主和属性。 + +### 常见的问题件系统 + +FAT、NTFS、ExtFAT、ext2、ext3、reiserFS、VFAT、APFS + ### 定义 -* 具有符号名的,在逻辑上具有完整意义的一组相关信息项的序列 -* 文件(document)与计算机文件(file) +文件系统是对一个存储设备上的数据进行组织的机制。 +* 具有符号名,在逻辑上具有完整意义的一组相关信息项的序列 * 文件名是由字母、数字和其他符合组成的一个字符串,其格式和长度因系统而异 + ### 命名: 1. 文件名和拓展名:前者用于识别文件,后者用于标识文件特性,二者用'.'隔开 2. 不同OS有约定的拓展名,Unix不做介绍,Windows如下 diff --git a/操作系统/image/2021-09-07-17-41-32.png b/操作系统/image/2021-09-07-17-41-32.png new file mode 100644 index 00000000..49c23e59 Binary files /dev/null and b/操作系统/image/2021-09-07-17-41-32.png differ diff --git a/操作系统/image/2021-09-07-17-45-18.png b/操作系统/image/2021-09-07-17-45-18.png new file mode 100644 index 00000000..feb2543d Binary files /dev/null and b/操作系统/image/2021-09-07-17-45-18.png differ diff --git a/操作系统/image/2021-09-07-18-40-50.png b/操作系统/image/2021-09-07-18-40-50.png new file mode 100644 index 00000000..c9f30cc5 Binary files /dev/null and b/操作系统/image/2021-09-07-18-40-50.png differ diff --git a/操作系统/image/2021-09-07-18-46-05.png b/操作系统/image/2021-09-07-18-46-05.png new file mode 100644 index 00000000..f6dcfed0 Binary files /dev/null and b/操作系统/image/2021-09-07-18-46-05.png differ diff --git a/操作系统/image/2021-09-07-18-48-41.png b/操作系统/image/2021-09-07-18-48-41.png new file mode 100644 index 00000000..21652e38 Binary files /dev/null and b/操作系统/image/2021-09-07-18-48-41.png differ diff --git a/操作系统/附录10 动态内存的原理.md b/操作系统/附录10 动态内存的原理.md new file mode 100644 index 00000000..c74cfd76 --- /dev/null +++ b/操作系统/附录10 动态内存的原理.md @@ -0,0 +1,129 @@ +# 动态内存管理 +> 参考文献 +> * [SLBA的原理和使用](https://blog.csdn.net/qq_26626709/article/details/52742484) +> * [SLBA教程](https://www.cnblogs.com/foundwant/p/4028993.html) +## 1 SLBA分配器 + +### 概念 +在linux内核中伙伴系统用来管理物理内存,其分配的单位是页,但是向用户程序一样,内核也需要动态分配内存,而伙伴系统分配的粒度又太大。 + +由于内核无法借助标准的C库,因而需要别的手段来实现内核中动态内存的分配管理,linux采用的是slab分配器。 + +slab分配器不仅可以提供动态内存的管理功能,而且可以作为经常分配并释放的内存的缓存。通过slab缓存,内核能够储备一些对象,供后续使用。需要注意的是slab分配器只管理内核的常规地址空间(准确的说是直接被映射到内核地址空间的那部分内存包括 ZONE_NORMAL和ZONE_DMA )。 + + +### 优点 +采用了slab分配器后,在释放内存时,slab分配器将释放的内存块保存在一个列表中,而不是返回给伙伴系统。在下一次内核申请同样类型的对象时,会使用该列表中的内存开。slab分配器分配的优点: +* 可以提供小块内存的分配支持 +* 不必每次申请释放都和伙伴系统打交道,提供了分配释放效率 +* 如果在slab缓存的话,其在CPU高速缓存的概率也会较高。 +* 伙伴系统的操作队系统的数据和指令高速缓存有影响,slab分配器降低了这种副作用 +* 伙伴系统分配的页地址都页的倍数,这对CPU的高速缓存的利用有负面影响,页首地址对齐在页面大小上使得如果每次都将数据存放到从伙伴系统分配的页开始的位置会使得高速缓存的有的行被过度使用,而有的行几乎从不被使用。slab分配器通过着色使得slab对象能够均匀的使用高速缓存,提高高速缓存的利用率 + +在引入了slab分配器后,内核的内存管理方案如图所示: + +![](image/2021-09-07-17-41-32.png) + +### 缺点 + +slab分配器也不是万能的,它也有缺陷: +* 对于微型嵌入式系统,它显得比较复杂,这是可以使用经过优化的slob分配器,它使用内存块链表,并使用最先适配算法 +* 对于具有大量内存的大型系统,仅仅建立slab分配器的数据结构就需要大量内存,这时候可以使用经过优化的slub分配器 + +### 使用 +无论是slab分配器家族的这三个中的那个一,它们提供的接口都是相同的: +* kmalloc、kmalloc_node用于普通内存的分配 +* kmem_cache_alloc、kmem_cache_alloc_node用于申请特定类型的内存 + +* 内核中普通内存的申请使用kmalloc(size,flags),size是申请的大小,flags告诉分配器分配什么样的内存,如何分配等等。 +* 内核中普通内存的释放使用kfree(*ptr);释放ptr所指向的内存区。 +* 可以通过/proc/slabinfo查看活动的缓存列表。 + +## 2 SLBA分配器的原理 + +### 基本原理 + +slab分配器把对象分组放进高速缓存。每个高速缓存都是同种类型对象的一种“储备”。一个cache管理一组大小固定的内存块(也称为对象实体),每个内存块都可用作一种数据结构。cache中的内存块来自一到多个slab。一个slab来自物理内存管理器的一到多个物理页,该slab被分成一组固定大小的块,被称为slab对象(object),一个slab属于一个cache,其中的对象就是该cache所管理的固定大小的内存块。所以一个cache可以有一到多个slab。下图给出了slab分配器的各个部分及其相互关系: + +![](image/2021-09-07-17-45-18.png) + +在基于slab的内核内存管理器中,基本的概念是保存管理型数据的缓存(即slab cache,slab缓存)和保存被管理对象的各个slab。每个缓存都负责一种对象类型,比如kmalloc-128会负责管理65-128字节的内存的kmalloc分配。系统中的所有缓存类型都保存在一个链表slab_caches中。 + +## 3 虚拟内存 +> 参考文献 +> * [虚拟内存与动态内存](https://zhuanlan.zhihu.com/p/374477494) +### 组织形式 + +1. linux虚拟内存形式安装堆栈形式组织,栈位于内存高地址,分为内核栈和用户栈,增长方向从高到低。而堆位于内存的低地址,是程序员进行动态内存分配的空间,增长方向由低到高。堆和栈中间是共享映射空间,用于共享库在内存中的映射,这样每次如果有不同代码调用相同的共享库,就不需要再次向内存中复制一份副本,节省了时间和空间。 +2. 栈内存的更高地址用于存放一些全局数据结构 +3. 堆内存的更低地址按地址从低到高放置着代码段(.text)、已分配数据段(.data)、未分配数据段(.bss)。你可能还听说过 COMMON 段专门储存未初始化全局变量,真正的.bss存储未初始化的静态变量以及初始化为0的全局和静态变量 [1],组织形式如下 +``` +SECTIONS { + .text : { *(.text) } + .data : { *(.data) } + .bss : { *(.bss) *(COMMON) } +} +``` +![](image/2021-09-07-18-40-50.png) + + + +​Linux动态内存分配的实现方式是由 mmap, munmap 以及 brk, sbrk 这四个系统函数联合完成的。 + +### mmap + +``` +void *mmap(void *addr, size_t length, int prot, int flags, + int fd, off_t offset); +``` + +mmap 创建一个新的虚拟内存空间和文件设备之间的映射。 + + +![](image/2021-09-07-18-46-05.png) + + +其中 addr 代表分配开始地址,fd是相应文件描述符,len是指文件存储部分映射的长度,offset指的是从文件头开始offset距离开始分配。 + +```C++ +prot包含权限位 +PROT_EXEC // 可执行 +PROT_READ // 可读 +PROT_WRITE // 可写 +PROT_NONE // 不可访问 +Flags 表示映射对象类型 +MAP_ANON // 匿名请求二进制零的 +MAP_PRIVATE // 私有的 +MAP_SHARED // 共享的 +``` + + +### munmap + +取消相应地址内存块的映射 +``` +int munmap(void *addr, size_t length); +``` +很好理解取消开始地址为 addr 长度为 length 的内存映射。 + + +### brk与sbrk +​brk, sbrk 用来移动 program break 指向的指针来扩展堆内存,program break 位于堆顶未初始化数据段末尾之后,通过移动 program break 指针来动态控制堆的大小。 + +![](image/2021-09-07-18-48-41.png) + + +``` +int brk(void *addr); +``` + +brk 会在允许的情况下简单的将 program break 设为 addr 地址,来控制堆内存大小。相当于 program break 的绝对移动 + +``` +void *sbrk(intptr_t increment); +``` +​sbrk 会在允许的情况下将 program break 指针加 increment 值,返回扩展前的 program break 地址。当increment为正值时,堆被扩展;为0时,返回当前 program break 的指针;为负值时,堆被收缩。相当于 program break 的相对移动 + + +## 4 malloc和alloc的原理 + diff --git a/操作系统/附录12 并发编程.md b/操作系统/附录12 并发编程.md index a217999c..3bed3f5d 100644 --- a/操作系统/附录12 并发编程.md +++ b/操作系统/附录12 并发编程.md @@ -1,6 +1,8 @@ # 并发编程 ## 1 并发概述 +> 并发一般应用在高性能服务器上,用来响应多个客户端的并发访问。但是在客户端也会用到并发的方法,如某些耗时长的文件读取(并发读取,不会阻塞用户进程)、点对点通信(并发通信不会阻塞用户界面)、ajax异步通信(并发获取服务器上的数据,不会阻塞界面)等。 + ### 问题重述