跳转至

操作系统知识

总结-gihub

1 进程、线程、管程、协程、PCB

1.1 进程概念

  • 进程是计算机中的程序关于某数据集合上的一次 运行活动,是系统进行 资源分配和调度的基本单位,是操作系统结构的基础。
  • 进程是线程的容器。
  • 程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作

1.2 管程概念

管程 (monitor) 只是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用 (由编译器实现)。

进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
管程引入了 条件变量 以及相关的操作:wait() 和 signal() 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。

1.3 协程概念

协程可以在执行过程中暂停而去执行其它任务,在适当的时候再回来继续执行。协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程那样需要上下文切换来消耗资源,因此协程的开销远远小于线程的开销。

1.4 进程与程序比较

进程与应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中,而进程则是处于动态条件下由操作系统维护的系统资源管理实体。

1.5 进程与线程比较

  • 进程是系统进行 资源分配的基本单位;线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
  • 同一个进程下创建的线程共享进程资源。
  • 一个线程可以创建或删除另一个线程。
  • 线程创建切换开销比进程小。

1.6 进程通信(IPC)

  1. 信号量:它是一个计数器,用于为多个进程提供对共享数据对象的访问
  2. 管道、FIFO 命名管道
  3. 消息队列
  4. 共享内存
  5. 套接字 (可用于不同机器间的进程通信)
共享内存是最快的进程通信方式,因为数据不需要在进程之间复制。
消息队列提供了异步进程通信方式,消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。
title: 匿名管道和有名管道总结
1. 管道是特殊类型的文件,在满足先入先出的原则条件下可以进行读写,但不能进行定位读写。
2. 匿名管道是单向的,只能在有亲缘关系的进程间通信;有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
3. 无名管道阻塞问题:无名管道无需显示打开,创建时直接返回文件描述符,在读写时需要确定对方的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程。如果写入无名管道的数据超过其最大值,写操作将阻塞,如果管道中没有数据,读操作将阻塞,如果管道发现另一端断开,将自动退出。
4. 有名管道阻塞问题:有名管道在打开时需要确认对方的存在,否则将阻塞。即以读方式打开某管道,在此之前必须一个进程以写方式打开管道,否则阻塞。此外,可以以读写(O_RDWR)模式打开有名管道,即当前进程读,当前进程写,不会阻塞。

1.7 进程同步

  1. 信号量
  2. 临界区
  3. 管程

1.8 线程同步方式

线程同步是为了确保线程安全。 1. 互斥量:确保同一时间只有一个线程访问资源,本质上是一把锁。 2. 信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。 3. 事件(信号):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。 4. 条件变量 5. 读写锁 6. 自旋锁 7. 屏障

1.9 进程间切换

当操作系统把控制权由当前进程转移到新进程时,首先需要进行上下文切换,保存当前进程的上下文信息,恢复新进程上下文信息,然后将控制权转移给新进程,新进程从上次停止的地方继续执行。

这里所说的上下文指进程运行所需的全部状态信息,包括PC(程序计数器)和寄存器文件当前值,以及主存内容(如代码、数据)。

1.10 僵尸进程

  • 定义:一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
  • 危害:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息 (包括进程号 the process ID,退出状态 the termination status of the process,运行时间 the amount of CPU time taken by the process 等)。直到父进程通过 wait / waitpid 来取时才释放。 但这样就导致了问题,如果进程不调用 wait / waitpid 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
  • 解决方法:杀死父进程,使子进程由 init 进程负责。

1.11 孤儿进程

  • 定义:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程 (进程号为 1) 所收养,并由 init 进程对它们完成状态收集工作。
  • 危害:孤儿进程没有危害。

1.12 进程组、会话

进程组是进程的集合,会话是进程组的集合。 每一个进程都有一个进程组;每一个进程组都有一个组长进程,组长进程退出不影响进程组消失;只有当最后一个进程退出,进程组才消失。进程组中最后一个进程可以终止或者转移到另一个进程组中。 一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

1.13 控制终端

详见 http://shareinto.github.io/2016/11/17/linux-terminal/

1.14 守护进程

  • 定义:守护进程,也即通常所说的 Daemon 进程,是 Linux 下一种特殊的后台服务进程,它独立于控制终端并且周期性的执行某种任务或者等待处理某些发生的事件。守护进程通常在系统引导装入时启动,在系统关闭时终止。Linux 系统下大多数服务都是通过守护进程实现的,守护进程的名称通常以 d 结尾,如 httpd、crond、mysqld 等。

1.14.1 创建守护进程的步骤

详见 http://blog.chinaunix.net/uid-21411227-id-1826736.html https://www.cnblogs.com/yinbiao/p/11203225.html https://zhuanlan.zhihu.com/p/73330470 1. fork 创建子进程,父进程退出 子进程变成孤儿进程,然后由 1 号 init 进程收养

  1. setsid 子进程创建新会话 调用 setsid 创建新的会话,摆脱原会话,原进程组,原终端的控制,自己成为新会话的组长

  2. chdir 将当前目录改为根目录 正在运行的进程文件系统不能卸载,如果目录要回退,则此时进程不能做到,为了避免这种麻烦,以根目录为当前目录

  3. umask 重设文件权限掩码 子进程的文件权限掩码是复制的父进程的,不重新设置的话,会给子进程使用文件带来诸多麻烦。

  4. close 关闭不需要的文件描述符 子进程的文件描述符也是从父进程复制来的,那些不需要的文件描述符永远不会被守护进程使用,会白白的浪费系统资源,还可能导致文件系统无法结束

1.14.2 示例:守护线程每隔 100s 就向文件写入一句话

#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<semaphore.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
using namespace std;

#define max_file 65535

int main()
{
    pid_t pc;
    int fd,len;

    char buf[]="this is a demo\n";
    len=strlen(buf);

    pc=fork();//第一步,创建子进程

    if(pc<0)
    {
        cout<<"fork error"<<endl;
        exit(1);
    }
    else if(pc>0)
    {
        exit(0);//第二步,父进程退出
    }
    else
    {
        setsid();//第三步,创建新会话
        chdir("/");//第四步,将当前目录改为根目录
        umask(0);//第五步,重新设置文件权限掩码

        for(int i=0; i<max_file; i++)
        {
            close(i);//第六步,关闭不需要的文件描述符
        }

        while(1)
        {
            if((fd=open("/tmp/dameo.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0)
            {
                cout<<"open erro"<<endl;
                exit(1);
            }
            write(fd,buf,len+1);
            close(fd);
            sleep(100);//每隔100s输出一句话到文件
        }
    }
}

1.14.3 示例:守护进程的代码实现

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>

static bool flag = true;
void create_daemon();
void handler(int);

int main()
{
    time_t t;
    int fd;
    create_daemon();
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(sigaction(SIGQUIT, &act, NULL))
    {
        printf("sigaction error.\n");
        exit(0);
    }
    while(flag)
    {
        fd = open("/home/mick/daemon.log", 
        O_WRONLY | O_CREAT | O_APPEND, 0644);
        if(fd == -1)
        {
            printf("open error\n");
        }
        t = time(0);
        char *buf = asctime(localtime(&t));
        write(fd, buf, strlen(buf));
        close(fd);
        sleep(60);
    }
    return 0;
}
void handler(int sig)
{
    printf("I got a signal %d\nI'm quitting.\n", sig);
    flag = false;
}
void create_daemon()
{
    pid_t pid;
    pid = fork();

    if(pid == -1)
    {
        printf("fork error\n");
        exit(1);
    }
    else if(pid)
    {
        exit(0);
    }

    if(-1 == setsid())
    {
        printf("setsid error\n");
        exit(1);
    }

    pid = fork();
    if(pid == -1)
    {
        printf("fork error\n");
        exit(1);
    }
    else if(pid)
    {
        exit(0);
    }

    chdir("/");
    int i;
    for(i = 0; i < 3; ++i)
    {
        close(i);
    }
    umask(0);
    return;
}

1.14.4 示例:用系统函数 daemon 实现

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>

static bool flag = true;
void handler(int);

int main()
{
    time_t t;
    int fd;
    if(-1 == daemon(0, 0))
    {
        printf("daemon error\n");
        exit(1);
    }
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(sigaction(SIGQUIT, &act, NULL))
    {
        printf("sigaction error.\n");
        exit(0);
    }
    while(flag)
    {
        fd = open("/home/mick/daemon.log", 
        O_WRONLY | O_CREAT | O_APPEND, 0644);
        if(fd == -1)
        {
            printf("open error\n");
        }
        t = time(0);
        char *buf = asctime(localtime(&t));
        write(fd, buf, strlen(buf));
        close(fd);
        sleep(60);
    }
    return 0;
}
void handler(int sig)
{
    printf("I got a signal %d\nI'm quitting.\n", sig);
    flag = false;
}

1.15 PCB 概念

为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个 PCB,在创建进程时,建立 PCB,伴随进程运行的全过程,直到进程撤消而撤消。 - Linux 下 PCB 是 task_struct 结构体,含以下信息

1. 标示符: 描述本进程的唯一标识符,用来区别其他进程。
2. 状态 :任务状态,退出代码,退出信号等。
3. 优先级 :相对于其他进程的优先级。
4. 程序计数器:程序中即将被执行的下一条指令的地址。
5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
6. 上下文数据:进程执行时处理器的寄存器中的数据。
7. I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • task_struct 数据结构及注释如下
struct task_struct {
volatile long state;  //说明了该进程是否可以执行,还是可中断等信息
unsigned long flags;  //Flage 是进程号,在调用fork()时给出
int sigpending;    //进程上是否有待处理的信号
mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
                        //0-0xBFFFFFFF for user-thead
                        //0-0xFFFFFFFF for kernel-thread
//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatile long need_resched;
int lock_depth;  //锁深度
long nice;       //进程的基本时间片
//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //进程内存管理信息
int processor;
//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向运行队列的指针
unsigned long sleep_time;  //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages;       //指向本地页面      
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal;     //父进程终止时向子进程发送的信号
unsigned long personality;
//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
int did_exec:1; 
pid_t pid;    //进程标识符,用来代表一个进程
pid_t pgrp;   //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp;  //进程控制终端所在的组标识
pid_t session;  //进程的会话标识
pid_t tgid;
int leader;     //表示进程是否为会话主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group;   //线程链表
struct task_struct *pidhash_next; //用于将进程链入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;  //供wait4()使用
struct completion *vfork_done;  //供vfork() 使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值

//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer;   //指向实时定时器的指针
struct tms times;      //记录进程消耗的时间
unsigned long start_time;  //进程创建的时间
//记录进程在每个CPU上所消耗的用户态时间和核心态时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];   //进程正在运行的可执行文件名
 //文件系统信息
int link_count, total_link_count;
//NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct *tty;
unsigned int locks;
//进程间通信信息
struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct thread_struct thread;
  //文件系统信息
struct fs_struct *fs;
  //打开文件信息
struct files_struct *files;
  //信号处理函数
spinlock_t sigmask_lock;
struct signal_struct *sig; //信号处理函数
sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending;  //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;

spinlock_t alloc_lock;
void *journal_info;
};

2 系统启动管理

引用 http://c.biancheng.net/linux_tutorial/12/

Linux 系统的启动,从计算机开机通电自检开始,一直到登陆系统,需要经历多个过程。学习 Linux 的启动过程,有助于了解 Linux 系统的结构,也对系统的排错有很大的帮助。

CentOS 6.x 系统启动过程发生了较大的变化,使用 Upstart 启动服务取代了原先的 System V init 启动服务。Upstart 启动服务的效率更高,启动速度更快。

Linux 系统启动过程比较复杂,我们先整理一下基本的启动过程,有一个整体的印象,然后再进一步说明。

目前,CentOS 6.x 的启动流程经历以下几步:

  1. 服务器加电,加载 BIOS 信息,BIOS 进行系统检测。依照 BIOS 设定,找到第一个可以启动的设备(一般是硬盘);
  2. 读取第一个启动设备的 MBR (主引导记录),加载 MBR 中的 Boot Loader(启动引导程序,最为常见的是 GRUB)。
  3. 依据 Boot Loader 的设置加载内核,内核会再进行一遍系统检测。系统一般会采用内核检测硬件的信息,而不一定采用 Bios 的自检信息。内核在检测硬件的同时,还会通过加载动态模块的形式加载硬件的驱动。
  4. 内核启动系统的第一个进程,也就是 /sbin/init。
  5. 由 /sbin/init 进程调用 /etc/init/rcS.conf 配置文件,通过这个配置文件调用 /etc/rc.d/rc.sysinit 配置文件。而 /etc/rc.d/rc.sysinit 配置文件是用来进行系统初始化的,主要用于配置计算机的初始环境。
  6. 还是通过 /etc/init/rcS.conf 配置文件调用 /etc/inittab 配置文件。通过 /etc/inittab 配置文件来确定系统的默认运行级别。
  7. 确定默认运行级别后,调用 /etc/init/rc.conf 配置文件。
  8. 通过 /etc/init/rc.conf 配置文件调用并执行 /etc/rc.d/rc 脚本,并传入运行级别参数。
  9. /etc/rc.d/rc 确定传入的运行级别,然后运行相应的运行级别目录 /etc/rc[0-6].d/ 中的脚本。
  10. /etc/rc[0-6].d/ 目录中的脚本依据设定好的优先级依次启动和关闭。
  11. 最后执行 /etc/rc.d/rc.local 中的程序。
  12. 如果是字符界面启动,就可以看到登录界面了。如果是图形界面启动,就会调用相应的 X Window 接口。

3 操作系统、内核

  • 操作系统(英语:Operating System,缩写:OS)是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
  • 内核是操作系统最基本的部分,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。
  • 内核还分宏内核、微内核、混合内核、外内核,详见 内核-维基百科

4 内存

4.1 3 种内存管理方式

  1. 虚拟内存,最适合用来管理大型对象或者结构数组;
  2. 内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行多个进程之间共享数据;
  3. 内存堆栈,最适合用来管理大量的小对象。

5 中断、轮询

  • 中断:它能使 CPU 在运行过程中对外部事件发出的中断请求及时地进行处理, 处理完成后又立即返回断点,继续进行 CPU 原来的工作。CPU 利用率高。
  • 轮询:由 cpu 定时对每一个 io 设备进行轮流询问是否需要 cpu 服务。效率低,等待时间很长,CPU 利用率不高。

5.1 中断和轮询之间的区别

  • 中断时,设备会通知 CPU 引起注意;而在轮询中,CPU 会稳定地检查设备是否需要注意。
  • 中断不是协议,而是一种硬件机制;轮询反之。
  • 在中断中,该设备由中断处理程序提供服务;轮询时,该设备由 CPU 维修。
  • 中断可以随时发生;轮询时,CPU 会以固定的间隔稳定地对设备进行投票。
  • 在中断中,中断请求线用作指示设备需要维修的指示;在轮询时,命令就绪位用作指示,表明设备需要维修。
  • 在中断中,一旦任何设备将其中断,处理器就会受到干扰;在轮询中,处理器通过重复检查每个设备的命令就绪位来浪费无数的处理器周期。

6 临界区、临界资源

  • 临界资源:指同一时间仅允许 一个进程访问的资源;硬件有打印机、磁带机等,软件有消息缓冲队列、变量、数组、缓冲区等。
  • 临界区:指每一个进程访问临界资源的那段代码。

6.1 临界区问题的解决方案

  1. 互斥:如果进程在其临界区内执行,那么其他进程都不能在其临界区内执行。
  2. 有限等待:从一个进程做出进入临界区的请求直到这个请求允许为止,其他进程允许进入其临界区的次数具有上限。
  3. 资源释放:如果进程不能进入自己的临界区,则应让出 CPU,避免进程出现“忙等”现象。
假设临界区a和临界区b都访问相同的临界资源。
当一个进程A首先进入自己的临界区a,进程B就无法进入自己的临界区b。

7 文件描述符

Linux 下一切都是文件,比如 C++ 源文件、视频文件、Shell 脚本、可执行文件等,就连键盘、显示器、鼠标等硬件设备也都是文件。 一个 Linux 进程可以打开成百上千个文件,为了表示和区分已经打开的文件,Linux 会给每个文件分配一个编号(一个 ID),这个编号就是一个整数,被称为文件描述符(File Descriptor)。

详见 http://c.biancheng.net/view/3066.html

8 五种 IO 模型

8.1 阻塞 IO 模型

8.2 非阻塞 IO 模型

8.3 IO 多路复用模型

8.4 信号驱动 IO 模型

8.5 异步 IO 模型

9 IO 多路复用

详见 https://blog.csdn.net/heidou_2016/article/details/105290254

10 阻塞不会占用 cpu

解释: 1. 进程 3 状态 阻塞等待、就绪、运行,阻塞原因是即没有 cpu 资源,又没有其它资源(如 io),就绪是含有除 cpu 外所有资源,等待 cpu 资源。 2. 除非是死循环,这个不能归为阻塞,而是一直执行一段程序,即使循环体中没有语句需要执行,他也占用资源。

11 缓冲区溢出

  • 解释:缓冲区溢出是指当计算机向缓冲区内填充数据时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。造成缓冲区溢出的主原因是程序中没有仔细检查用户输入的参数。典型的 c 函数 strcpy() 就会造成缓冲区溢出,在高版本中提供 strcpy_s() 来解决这个问题。
  • 危害:
  • 程序崩溃,导致拒绝服务
  • 跳转并且执行一段恶意代码

12 死锁

死锁的规范定义如下:如果一个进程集合中的每个进程都在等待只能由该进程集合中其他进程才能引发的事件,那么该进程集合就是死锁的。

title: 产生死锁的四个必要条件:
1. 互斥条件:每个资源要么已经分配给了一个进程,要么就是可用的。
2. 占有和等待条件:已经得到了某个资源的进程可以再请求新的资源。
3. 不可抢占条件:已经分配给一个进程的资源不能强制性地被抢占,只能被占有它的进程显式地释放;
4. 环路等待条件:死锁发生时,系统中一定有两个或者两个以上的进程组成的一条环路,该环路中的每个进程都在等待着下一个进程所占有的资源。
title: 四种处理死锁的策略
1. 鸵鸟策略(忽略死锁);
2. 检测死锁并恢复;
3. 仔细对资源进行分配,动态地避免死锁;
4. 通过破坏引起死锁的四个必要条件之一,防止死锁的产生。

13 单工、半双工、全双工

单工:数据只能由一端 a 传向另一端 b,而不能由一端 b 传向一端 a。比喻汽车的单行道 半双工:半双工 (Half Duplex) 数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。比喻对讲机。 全双工:通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B 且 B→A)。指 A→B 的同时 B→A,是瞬时同步的。比喻打电话。

14 分布式、集群、单机

https://www.huaweicloud.com/articles/6809c5da1dbeb88d6b65bd29083bce2c.html

14.1 单机

单机就是把做的系统部署到一台服务器上,,所有的请求业务都由这台服务器处理。显然,当业务增长到一定程度的时候,服务器的硬件会无法满足业务需求。很多人就会想到多部署几台服务器,这就是集群。

14.2 集群

集群就是单机的多实例,在多个服务器上部署多个服务,每个服务就是一个节点,部署 N 个节点,处理业务的能力就提升 N 倍(大约),这些节点的集合就叫做集群。 - 优点:操作简单,容易部署; - 缺点:每个节点负载相同(耦合度高),每个具体业务的访问量可能差异很大,比如美团外卖美食外卖的访问量一定大于鲜花外卖的访问量,这就造成了资源浪费

14.3 分布式(微服务)

分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在 web 容器中,它们之间通过 RPC 方式通信。 - 优点:资源利用率高 - 缺点:安全性低,如果一台服务器出现问题整个系统就会崩塌

15 RPC(Remote Procedure Call)

https://www.zhihu.com/question/25536695 - 作用:提供在主机 A 调用主机 B 上的函数或方法