美文网首页
面试知识点(4)操作系统

面试知识点(4)操作系统

作者: 微糖去冰_ | 来源:发表于2019-08-23 16:53 被阅读0次

    1.请你说一下进程与线程的概念,其中有什么区别,他们各自又是怎么同步的

    基本概念

    是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
    线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;

    区别

    1.一个线程只能属于一个进程,而一个进程可以有多个线程。线程依赖于进程而存在。
    2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
    3.进程是资源分配的最小单位,线程是CPU调度的最小单位;
    4.系统开销进程切换的开销也远大于线程切换的开销。
    5.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预
    6.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
    7.进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉
    8.进程适应于多核、多机分布;线程适用于多核


    2.进程间通信的方式:

    进程间通信主要包括管道系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket
    1.管道:
    管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
    1.1 普通管道PIPE:
    1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
    2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
    3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
    1.2 命名管道FIFO:
    1)FIFO可以在无关的进程之间交换数据
    2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
    2. 系统IPC:
    2.1 消息队列
    消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符来标记。具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;
    2.2 信号量semaphore
    信号量(semaphore)是一个计数器,可以用来控制多个进程对共享资源的访问。
    2.3 信号signal
    信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    2.4 共享内存(Shared Memory)
    它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等
    3.套接字SOCKET:
    socket也是一种进程间通信机制,它可用于不同主机之间的进程通信。


    3.线程间通信的方式

    1. 临界区:通过多线程的串行化来访问公共资源,速度快,适合控制数据访问;
    2. 互斥量Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
    3. 信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目
    4. 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

    4.进程调度方法详细介绍

    批处理系统:

    1.先来先服务
    2.短作业优先
    3.高响应比优先

    交互式系统

    1.时间片轮转算法
    2.优先级调度算法
    3.多级反馈算法

    5.页面置换方法详细介绍

    1.(OPT)最佳置换算法
    2.(FIFO)先进先出算法
    3.(LRU)最近最久未使用算法

    class LRUCache{
    public:
        LRUCache(int capacity) {
            cap = capacity;
        }
    
        int get(int key) {
            auto it = m.find(key);
            if (it == m.end()) return -1;
            l.splice(l.begin(), l, it->second);
            return it->second->second;
        }
        
        void put(int key, int value) {
            auto it = m.find(key);
            if (it != m.end()) l.erase(it->second);
            l.push_front(make_pair(key, value));
            m[key] = l.begin();
            if (m.size() > cap) {
                int k = l.rbegin()->first;
                l.pop_back();
                m.erase(k);
            }
        }
    private:
        int cap;
        list<pair<int, int>> l;
        unordered_map<int, list<pair<int, int>>::iterator> m;
    };
    

    6.请你说一说死锁发生的条件以及如何解决死锁

    死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件:互斥、占有且等待、不可抢占、循环等待
    解决死锁的方法如下:
    1.资源一次性分配,从而剥夺请求和保持条件
    2.可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件
    3.资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

    7.生产者消费者问题,读者写者问题

    描述了一组生产者与一组消费者,它们共享一个有界缓冲池,生产者向池中投入产品,消费者从池中取得产品。假定缓冲池中有 N 个缓冲区,每个缓冲区只能存放一个类型为 item 的产品,而所有的生产者和消费者是相互等效的,则需要为该问题设置三个信号量:互斥信号量 mutex,用于实现对缓冲池的互斥访问,其初值为 1;资源信号量 empty,用来表示空闲缓冲区的数量,其初值为 n;资源信号量 full,用来表示满缓冲区的数量,即缓冲池中可供消费的产品数量,其初值为 0。empty 和 full 用来同步生产者和消费者进程,即当缓冲池全空时,消费者进程必须等待;缓冲池全满时,生产者进程必须等待。

    int in = 0, out = 0;
    Item buffer[N];
    semaphore mutex, empty, full;//互斥信号量,资源信号量空和满
     
    void producer()
    {
        while(1)
        {
            produce an item nextp;
            P(empty);//向空闲缓冲区信号量empty中申请一个,如果没有,则阻塞该进程;如果有,则继续执行
            P(mutex);//临界区资源需要使用互斥量,保证只有一个线程可以访问,避免多线程访问冲突。
            buffer[in] = nextp;
            in = (in + 1) % N;
            V(mutex);//释放一个信号量,临界区访问结束
            V(full);//满缓冲区信号量full中加 1
        }
    }
    void consumer()
    {
        while(1)
        {
            P(full);//向满缓冲区信号量full申请一个,如果没有,则阻塞该进程;如果有,则继续执行
            P(mutex);//同理,临界区资源需要互斥访问
            nextc = buffer[out];
            out = (out + 1) % N;
            V(mutex);
            V(empty);//空闲缓冲区empty中加 1
            consume the item in nextc
            ...
        }
    }
    

    读者写者问题。读写公平型伪代码。

    semaphore  rw=1;//用于实现对文件的互斥访问
    semaphore mutex=1;//用于实现对count 的互斥访问
    semaphore w=1;//用于实现写优先
    int count=0;//用于记录有多少个进程同时在访问文件
    
    writer()
    {
      while(1)
      {
        P(w);
        P(rw);
        写文件;
        V(rw);
        V(w);
      }
    }
    reader()
    {
      while(1)
      {
        P(w);
        P(mutex);
        if(count==0)
            P(rw);
        count++;
        V(mutex);
        读文件;
        P(mutex);
        count--;
        if(count==0)
            V(rw);
        V(w);
      }
    }
    

    8. Linux的4种锁机制:

    互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒

    读写锁:rwlock,分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。

    自旋锁:spinlock,在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源。

    RCU:即read-copy-update,在修改数据时,首先需要读取数据,然后生成一个副本,对副本进行修改。修改完成后,再将老数据update成新的数据。使用RCU时,读者几乎不需要同步开销,既不需要获得锁,也不使用原子指令,不会导致锁竞争,因此就不用考虑死锁问题了。而对于写者的同步开销较大,它需要复制被修改的数据,还必须使用锁机制同步并行其它写者的修改操作。在有大量读操作,少量写操作的情况下效率非常高。

    9.请你来介绍一下5种IO模型

    1.阻塞IO:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作

    2.非阻塞IO:非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。

    3.信号驱动IO:信号驱动IO:linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件。

    4.IO复用/多路转接IO:linux用 select/poll 函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数

    5.异步IO:linux中,可以调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。

    同步、异步

    同步:用户进程发起IO后,进行就绪判断,轮询内核状态。
    异步:用户进程发起IO后,可以做其他事情,等待内核通知。

    阻塞、非阻塞

    阻塞:用户进程访问数据时,如果未完成IO,调用的进程一直处于等待状态,直到IO操作完成。
    非阻塞:用户进程访问数据时,会马上返回一个状态值,无论是否完成,此时进程可以操作其他事情。

    select,poll,epoll的区别

    1.select

    是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理。

    存在的问题:

    1. 内置数组的形式使得select的最大文件数受限与FD_SIZE;

    2. 每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;

    3. 轮寻排查当文件描述符个数很多时,效率很低;

    2.poll

    poll:通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。

    3.epoll

    epoll:轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。

    epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式

    1. LT模式

    LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。

    1. ET模式

    ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

    ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

    3、LT模式与ET模式的区别如下:
    LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
    ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

    相关文章

      网友评论

          本文标题:面试知识点(4)操作系统

          本文链接:https://www.haomeiwen.com/subject/pthtectx.html