This commit is contained in:
Estom
2021-09-07 21:05:39 +08:00
parent 3a4ea4c954
commit 5a7d555f1a
18 changed files with 537 additions and 52 deletions

View File

@@ -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;
};
```

View File

@@ -5,7 +5,7 @@
### 两种制约关系
> 针对两种制约关系,合作制约关系和互斥制约关系,需要通过同步机制实现。
- 直接制约关系(同步)。由于多个进程相互合作产生,使得进程有一定的先后执行关系。
- 直接制约关系(合作)。由于多个进程相互合作产生,使得进程有一定的先后执行关系。
- 间接制约关系(互斥)。由于多个进程资源共享产生,多个进程在同一时刻只有一个进程能进入临界区。
@@ -79,6 +79,7 @@ void P2() {
}
```
### 进程同步机制——条件变量
### 进程同步机制——信号

View File

@@ -7,7 +7,7 @@
## 0 进程通信的定义
### 进程通信和进程同步的区别
* 主要的区别在于:进程同步控制多个进程按一定顺序执行;进程通信,实现进程间信息交换。
* 主要的区别在于:进程同步控制多个进程按一定顺序执行;进程通信,实现进程间信息交换。目标是不一样的。
* 进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。

View File

@@ -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)

View File

@@ -95,7 +95,7 @@
### 4.4 时钟CLOCK==最近未使用NRU, Not Recently Used
**第一个解析**
每个页面都有两个状态位R 与 M当页面被访问时设置页面的 R=1当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:
- R=0M=0
@@ -107,27 +107,31 @@
NRU 优先换出已经被修改的脏页面R=0M=1而不是被频繁使用的干净页面R=1M=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 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:

View File

@@ -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 请求分段功能
<!-- 用来实现虚拟内存的功能 -->
<!-- 用来实现虚拟内存的功能 -->

View File

@@ -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 就是Edge1->1 就是Level。

View File

@@ -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处理耗时长的操作会造成事件分发的阻塞影响到后续事件的处理

View File

@@ -1,8 +1,9 @@
# IO多路复用
## 1 单线程并发的含义
IO多路复用即单线程并发事件驱动模型。有事件响应机制、事件回调机制等。
单线程并发并非真正意义上的单线程。而是只有单一的用户线程。还包括数据库socket等系统多线程。
**单线程并发并非真正意义上的单线程。而是只有单一的用户线程。还包括数据库socket等系统多线程。**
单个用户线程:对于十万个用户同时访问服务器,有两种方式处理并发。

View File

@@ -1,6 +1,6 @@
# 多线程与并发
# IO多路复用与回调函数
## 1 补充
## 1 this指针
### this指针
this关键字指向的是当前对象的引用
@@ -8,14 +8,14 @@ this 不是指向类。而是在实例化的时候与当前类的实例也就是
### 关于回调
对于Python与JavaScript这种能够直接传递“函数类型”的参数的语言回调函数可以作为另外一个函数的参数进行传递。
对于Python与JavaScript这种能够直接传递“函数类型”的参数的语言回调函数可以作为另外一个函数的参数进行传递。C++也是可以传递函数指针的)
在C++和java这种只能传递基本数据类型和构造数据类型的语言中即函数不作为数据类型的语言中可以通过传递整个对象的方法使得被调用者能够通过调用者对象的指针进行回调这也是设计模式的一种。观察者模式
关于回调函数的本质理解:当其他程序执行时,能够通过回调函数,转移进程控制权限,给调用者(这里并没有转移控制权,而是保留控制权在本地,完成不可知的后续处理)。可以把调用者与被调用者分开。调用者能够显式调用被调用者,同时被调用这能通过回调函数隐式调用调用者。
关于回调函数的本质理解:当其他程序执行时,能够通过回调函数,转移进程控制权限,给调用者(这里并没有转移控制权,而是保留控制权在本地,完成不可知的后续处理)。可以把调用者与被调用者分开。调用者能够显式调用被调用者,同时被调用这能通过回调函数隐式调用调用者。但是实在另一个线程中执行。
关于回调函数的本质理解:回调函数在同一个层次内是没有用的,或者说没有必要的。如果一个人,在实现一个层次内部的函数执行权限转移过程中,可以直接进行相互调用,因为本质上,两个部分是完全可知的。但是在不同层次的调用中,例如用户层与系统层中,用户层调用系统层,可以通过显式调用,但是系统层并不会考虑用户层的具体实现,对用户层是不可知的。这个时候,系统层会通过传递来的回调函数,将进程控制权交给用户层。
关于回调函数的本质理解:回调函数在同一个层次内是没有用的,或者说没有必要的。如果一个人,在实现一个层次内部的函数执行权限转移过程中,可以直接进行相互调用,因为本质上,两个部分是完全可知的。但是在不同层次的调用中,例如用户层与系统层中,用户层调用系统层,可以通过显式调用,但是系统层并不会考虑用户层的具体实现,对用户层是不可知的。这个时候,系统层会通过传递来的回调函数,将进程控制权交给用户层。(并不会转移控制权,交给其他的线程)
那么问题来了,直接通过返回值的方式转移进程的控制权限不好吗。在回调的部分通过返回值,区分不同的情况,用户自己选择执行什么样的函数。

View File

@@ -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如下

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -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 cacheslab缓存和保存被管理对象的各个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的原理

View File

@@ -1,6 +1,8 @@
# 并发编程
## 1 并发概述
> 并发一般应用在高性能服务器上用来响应多个客户端的并发访问。但是在客户端也会用到并发的方法如某些耗时长的文件读取并发读取不会阻塞用户进程、点对点通信并发通信不会阻塞用户界面、ajax异步通信并发获取服务器上的数据不会阻塞界面等。
### 问题重述