• 操作系统
  • fork&&vfork
  • 环境变量
  • 进程状态(僵尸孤儿)
  • 进程线程 &&协程 && 进程切换
  • 线程的独有与共享 && 如何保证线程数据安全
  • 互斥锁、乐观悲观锁、自旋锁&&死锁
  • Linux下的信息查看
  • 程序地址空间(虚拟地址空间)
  • 内存的管理方式(映射算法)
  • 缺页中断
  • 创建&&终止&&等待&&替换(进程&&线程)
  • 单例模式&&适配器模式
  • 软硬链接
  • 用户态内核态&&文件系统分区(文件存储获取
  • 动态库静态库&&动态链接静态链接
  • 进程间通信(IPC)&&管道&&消息队列&&共享内存&&信号量
  • 信号&&信号注册和处理&&可重入&&sigpipe&&sigchild
  • 多路转接模型 && 事件触发
  • GBD调试 && makefile && PHONY && $^
  • 银行家算法

操作系统

fork&&vfork

1、fork()创建的子进程,父子进程可以同时运行;vfork()创建的子进程会阻塞父进程,直到子进程exit退出或者程序替换以后,父进程才能运;因为vfork创建的子进程和父进程公用同一块物理空间,为了防止父子进程调用栈混乱,所以阻塞了父进程;
2、fork的子进程拷贝父进程的数据段和代码段,但vfork的子进程与父进程共享数据段
3、fork的父子进程执行次序不一定谁先谁后,而vfork必须保证子进程先运行

过程:
1、对新的进程分配内存空间
2、复制父进程的各种信息拷贝到子进程进程中
3、将子进程添加到系统进程列表中
注意
子进程创建出来起初和父进程是公用同一块地址的,当父子进程如果有数据需要改变的时候,才会给子进程重新开辟空间;这就是写时拷贝技术
优势:因为很多子进程创建出来数据不需要修改,这样做可以提高效率

特点:
1、调用一次fork有两个返回值;(return==0子进程;>0父进程)

fork失败的情况:
系统进程数已经到达上线;无法再创建

进程状态&&僵尸进程&&孤儿进程

Linux下的进程状态
运行态:程序运行
可中断休眠态:程序的阻塞状态可以被中断;
不可中断休眠态:程序的阻塞状态不可被中断
停止态:程序停止运行;(进程没退出,资源没释放)
僵尸态:程序已经退出但资源没被释放

僵尸进程:

产生-当一个进程完成工作,它的父进程需要调用wait或者waitpid函数来获取子进程的状态,而父进程有时候没有及时调用子进程就会成了僵尸进程。那么子进程的进程描述符仍将保存在操作系统中,占用资源。。(其实僵尸进程是一个进程必然经历的过程吗,因为每个子进程结束后,父进程没来得急处理的这段时间这个进程就会变成僵尸进程);
因此资源没有完全释放的意思就是:保留子进程退出的原因。

僵尸进程的危害资源泄露,一个用户能创建的进程是有限的,僵尸进程导致资源一直无法回收

解决办法:
1.通过kill结束父进程(不推荐 因为一个父进程可能有许多子进程,枪毙父进程,意味着要牵连无辜的子进程)
2.进程等待——(子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行/waitpid处理僵尸进程。 )

3.孤儿进程:父进程先于子进程退出,那么子进程就成了孤儿进程
(没问就别说)——同时孤儿进程的父进程也会变成1号进程,并且在后台运行(孤儿进程不会成为僵尸进程,因为1号进程一直关注着孤儿进程的状态)

4.守护进程(精灵进程):
守护进程也是一种特殊的孤儿进程(区别:脱离了终端与登录会话,不再受影响,通常是一种运行在后台的批处理程序)

环境变量

环境变量就是保存程序运行环境参数的变量
优点:使运行环境的配置更加的简单灵活
查看环境变量:env、set、echo
设计环境变量:export、
删除环境变量:unset、

代码中获取环境变量的操作:

1、main函数中的第三个参数;
int  main(int argc,char* argv[],cahr* env[])而env就是保存的环境变量
2、通过一个全局变量extern char** environ——environ保存环境变量
int main(int argc,char* argv []){extern char** environ;for(int i = 0;environ[i];i++){printf("%s",environ[i]);}return 0;
}

进程和线程

进程:fork()、vfork() 线程:pthread_create()

协程
简单来说协程就是线程下的一个执行流,它完全是一种用户控制的用户态的轻量级线程。携程拥有自己的寄存器、上下文、栈。因此进程和线程都是受操作系统的调度和分配,而协程完全是自己控制,
(如果没有协程,线程下的执行方式都是串行的,一个阻塞其他就不能运行,而协程就可以切换;)

协程和(进程线程)的区别
1、切换时机不一样,进程线程是操作系统决定的,协程是用户(的程序决定)
2、京城线程的切换内容保存在内核栈中,协程保存于用户自己的栈中;
3、进程的切换状态是用户态和内核态之间的切换,协程一直处于用户态
4、协程的切换效率高
协程缺点:一个线程内的多个协程是串行执行的不能利用多核,所以协程不适合计算密集型的场景;(引用多进程解决串行问题)
解决:上面举的例子是5个I/O处理,如果每秒500个,5万个或500万个呢?已经达到了“I/O密集型”的程度;——多进程程+协程

进程线程的区别
进程是一个运行中的程序,是CPU资源分配的最小单位,同时每一个进程都有自己独立的空间,;而线程是进程的一个执行流,一个进程可以包含多个线程,是CPU调度的最小单位,创建一个执行流的同时会创建一个pcb,因此我们也常说线程是轻量级进程;
除了这些基本区别, 从多任务处理来讲线程和进程也有着区别
多进程是指操作系统的并发 可以同时运行多个程序,而多线程是指一个程序内部的并发进行多任务处理(好比杀毒软件杀毒的同时也可以进行垃圾清理)。
除此以外进程和线程的通讯方式也略有不同,进程间的通讯是由于每个进程是独立的虚拟地址空间,为了方便各个进程间的数据交互而提出的一种概念(其中包括管道/共享内存/消息队列/信号量);而线程间的通信目的主要是用于线程同步(通信方式包括临界区、信号、信号量)
临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问; )

多进程和多线程各自有着自己的优势,他们在多任务处理的时候多进程的健壮性和稳定性更高,因为进程的地址空间是独有的,一个进程崩溃不会影响到下一个进程;多线程的优点是更加灵活,由于线程共用用同一个虚拟地址空间,每创建一个线程只需要创建一个pcb,共用的数据不需要重新创建所以创建和销毁的成本更低;所以多线程的更加灵活,线程间切换成本更低,创建和销毁成本也更低。
但线程有个比较致命的缺点,就是一个线程崩溃会影响整个进程;因此在谈到线程的时候我们必须考虑到线程安全的问题为保证多个线程在对临界资源争抢访问的时候不会出现二义性;主要通过同步和互斥两种方式完成的,同步保证访问资源的合理性,互斥保证访问资源的安全性(同步的实现依靠条件变量,也就是向外提供一个使线程等待和唤醒的接口+pcb的等待队列,能访问的时候访问,不能访问的时候让其休眠等待;而互斥是通过锁来实现的。)
进程的切换
实质上就是被中断运行进程与待运行进程的上下文切换。从主观上来理解。只分为两步:
1.切换新的页表,然后使用新的虚拟地址空间
2.切换内核栈,加入新的内容(PCB控制块,资源相关),硬件上下文切换

线程的独有与共享

独有
标识符:相当于每个线程的id,这是独有的;
寄存器:是一个存储单元包含了内存指针(当前在栈中的栈顶位置)程序计数器(存储下一条要执行的指令),上下文计数器,用来保存线程状态,保证下次可以从对应位置起始;
信号屏蔽字:共用会因为分时机制,导致多个线程处理同一个信号
共享
信号屏蔽方式:信号屏蔽字独有,但怎么处理是一样的;
虚拟地址空间:多个线程共用同一块进程空间;
补充:线程在切换过程中药保存当前线程的id,线程状态、堆栈、寄存器状态等等

线程安全在三个方面体现1.原子性 2.可见性 3.合理性

1.原子性:通过锁来提供互斥访问,保证同一时刻只能有一个线程对数据进行操作;
2.可见性:一个线程对主内存的修改可以及时的被其他线程看到
对于可见性;(这个通过volatile来实现的,volatile会在写操作时,在写操作后加一条store屏障指令,将本地内存中给共享变量值刷新到主内存。Volatile在进行读操作时,会在读操作前加一条load指令,从内存中读取共享变量)
3.合理性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,改观察结果一般杂乱无序。通过信号量来实现来实现访问的合理性。

我了解的锁有互斥锁、读写锁、自旋锁;互斥锁本身是一个只有0或1的计数器,描述了一个临界资源的访问状态。当有线程取访问临界资源的时候会先加锁,其他线程再访问的时候只能先在等待队列休眠。读写锁 无锁可读可写,有读锁读共享,但不能写,有写锁既不能读也不能写的一种锁;自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。它不像互斥锁那种如果不能访问资源就会处于阻塞状态,它的优点就是一直处于忙等待,避免了用户态内核态的切换,所以比较适用于执行流程较短的资源。

死锁
锁保证了线程访问的安全性,但如果锁使用不当也会造成死锁问题:死锁的本质就是多执行流进行资源争抢的时候由于访问的顺序不当,造成相互等待的情况;
死锁的产生条件有1、互斥条件2、不可剥夺条件3、请求保持条件 4、环路等待条件;互斥条件指我加了锁别人不能加锁,不可剥夺条件指我加锁别人不能解锁;请求保持条件指我加锁A,然后去请求B,请求不到我也不释放A;环路等待条件指我加锁A,去请求B,别人加锁B来请求A我们互相请求不到互相也不解锁;

解决办法:
我了解的死锁避免办法有银行家算法:它的算法思想就是预先判断若给一个执行流分配了一个指定的锁以后是否会达成环路等待条件导致系统进入不安全状态,如果有就不分配;

补充:乐观锁悲观锁
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,它的底层原理在每次拿数据的时候都会上锁,这样别人想拿到这个资源就会阻塞,意思就是共享资源每次只给一个线程使用,用完再把资源转让给其他线程(读写锁)
乐观锁总是认为别人不会修改数据,但在更新的时候回去判断再次期间有没有人更新这个数据,一般用于版本号机制去实现;版本号机制就是加上一个标识符version表示数据被修改的次数,如果被修改version+1;当线程A要更新数据的时候也会读取标识符,只有此时的version和刚开始A读的version一致时候(说明中途没人修改过),才会更新操作,否则一直重复。

乐观悲观锁的优缺点及使用场景
乐观锁适用于写比较少的情况下(乐观的人不写日记),因为写少的情况冲突发生的频率就低,所以就省去的锁的开销;但如果写多情况,发生冲突的概率就大,此时悲观锁比较合适;

自旋锁的死锁
自旋锁其实不太适用于单核,比较容易造成死锁;比如拥有自旋锁的进程A阻塞了,内核调用进程B。如果B也需要自旋锁就回去申请,但A一直阻塞,B申请不到就会一直自旋,而自旋就会一直占用CPU,对于单核来说A不能推进,B又拿不到锁就造成死锁;

互斥锁
int ticket = 100;
pthread_mutex_t mutex;//所有线程争抢一个锁资源,所以要放到全局
void *thr_scalpers(void *arg)
{while(1) {//加锁一定是只保护临界资源的访问pthread_mutex_lock(&mutex);if (ticket > 0) {//有票就一直抢usleep(1000);printf("%p-I got a ticket:%d\n",  pthread_self(), ticket);ticket--;pthread_mutex_unlock(&mutex);}else {//加锁后在任意有可能退出线程的地方都要解锁pthread_mutex_unlock(&mutex);pthread_exit(NULL);}}return NULL;
}
读写锁
pthread_mutex_t rdLock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t wrLock = PTHREAD_MUTEX_INITIALIZER;//定义并初始化两个锁
int readCnt = 0;//设置读锁调用次数的标志位
//实现读锁(共享锁)
void rdLock()
{
pthread_mutex_lock(&rdLock);
readCnt++;
if (readCnt == 1)//有人读,于是阻塞写锁
pthread_mutex_lock(&wrLock);
pthread_mutex_unlock(&rdLock);
}
void rdUnlock()
{
pthread_mutex_lock(&rdLock);
readCnt--;
if (readCnt == 0)//表示已没有人在读,释放写锁,可以写入了
pthread_mutex_unlock(&wrLock);
pthread_mutex_unlock(&rdLock);
}
自旋锁
static spinlock_t lock;
static int flag = 1;
void open() {spin_lock(&lock);//先抢占锁资源,如果没抢到一直强if (flag != 1) {//如果不等于1说明没有资源就解锁返回;spin_unlock(&lock);return;}flag = 0;//走到这里说明flag = 1,这时候把临界资源抢过来,用完然后解锁spin_unlock(&lock);return 0;
}

Linux下的信息查看

>netstat :查看网络相关情况(-a显示所有,-t显示tcp,-u显示udp)
>netstat -nap:查看端口占用情况
>ps -ef:查看进程相关信息(ps -aux)
>top:查看cpu使用情况
>df:查看磁盘分区情况
>free:查看内存运行信息、
ifconfig # 查看所有网络接口的属性
uname -a # 查看内核/操作系统/CPU信息
# env # 查看环境变量route -n # 查看路由表

程序地址空间

.程序地址空间是为了防止不同进程在同一时刻对物理内存争抢而导致进程崩溃采用的一种技术;因为每个进程运行需要一块连续的空间,程序地址空间就是将多个不连续的物理空间通过页表映射出一块虚拟的连续空间,可实现数据的离散存储,提高了内存的利用率。

内存的管理方式——虚拟地址如何找到物理地址(映射算法)

分页管理(页号+页内偏移量)、分段管理、段页式管理
分页式:将内存分成固定长度的一个个页片。操作系统为每一个进程维护了一个从虚拟地址到物理地址的映射关系的数据结构,叫页表,页表的内容就是该进程的虚拟地址到物理地址的一个映射;实现了数据的离散存储,提高了物理内存的利用率;
分段式:将内存分为了代码段,初始化全局段等等,什么变量就在什么段申请地址,让程序员对内存管理更加方便;分段式是——段号+段内偏移 通过段号找到段的起始地址。
段页式:结合了分页分段的优点,对虚拟地址进行分段管理;首先将地址空间首先被分成若干个逻辑分段,每段都有自己的段号,然后再将每段分成若干个大小相等的页。段页式是——段号+页号+页内偏移 先通过段号找到页的起始地址,通过页表当中的页号找到对应的块号,再通过块号计算出起始地址

每种存储的优点:
分页式:提高内存的利用率。
分段式:便于程序员,编译器的内存管理
段页式:集合了分页式与分段式的优点

缺页中断

缺页中断:就是进程的页表缺少对应的数据。
这是由于Linux有两个分区交换分区、文件系统分区当内存不够用的时候,我们把内存中不活跃的数据根据一定的算法保存到交换分区,腾出空间加载数据。
但当某个进程的页表中对应的物理地址中的数据被交换出去,则这个页表项位置缺页中断。
等到下次进程要访问被交换出去的数据(这是还在交换分区中),触发缺页中断,重新将数据交换回来。

问题:什么样的数据应该被交换出去(置换算法)
1、LRU算法:最久未使用
2、LFU:最不常使用

进程创建&&中止&&等待&&替换

进程终止:退出一个进程

1.main中的return退出
2.exit(int status )库函数中的退出,将status作为返回值给父进程
3._exit(int status )系统调用接口的退出,将status作为返回值给父进程
相同:return和exit都会刷新缓冲区再退出,而_exit不会刷新缓冲区,直接释放资源退出
不同:return只在main函数中才会退出进程,而exit再任意位置调用都会退出进程

进程等待:1父进程等待子进程的退出,2wait进行/waitpid获取子进程的返回值,3释放子进程资源 4防止产生僵尸进程

程序替换——本质就是替换原有pcb中存储的内容。将另一个程序加载到内存中,然后让原有pcb去调度这个新进程
******************************************************
线程的创建
pthread_create(获取线程id, 线程属性, 线程入口函数, 参数)

线程的终止主线程退出并不会导致进程退出,只有所有线程都退出,进程才会退出
0、在线程函数中调用return,会退出线程。而在main函数中用return会退出进程(进程退出线程不一定退出)。
2、在线程任意位置void pthread_exit(void* retval);——退出线程接口,谁调用谁退出。(exit无论在哪个进程调用都会退出线程)
3、在其他线程中int pthread_cancel(pthread_thread);——终止一个线程;在A中调用取消B,所以cancel退出的线程是被动取消的(pthread参数说明可以退出一个指定的线程)

exit和cancel的区别
cancel是不管进程有没有运行完,而eixt是等线程运行完所有功能。(联想exit是安全出口,所以要安全结束)
cancel分离的线程为detach属性,退出后会自动释放资源,不需要等待

线程等待
线程默认创建出来的属性是joinable,处于这个属性的线程,退出后,需要被其他线程等待获取返回值回收资源
int pthread_join(pthread_t thread, void ** retval)——等待指定线程退出,获取其返回值

线程分离:将线程joinable属性修改为detach属性
int pthread_detach(pthread_t thread);——将制定的线程分离出去,属性改为detach

一个线程属性若是joinable那么就必须被等待;
一个线程属性若是detach那么这个线程退出后自动释放资源,不需要被等待(因为资源已经被自动释放 了)

补充:分离一个线程,一定是你对线程的返回值不感兴趣,不想获取又不想等待线程退出!

单例模式

单例模式:单例模式主要解决一个全局使用的类频繁的创建和销毁的问题。单例模式下可以确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式有三个要素:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
为什么要用单例模式——单例模式节省公共资源
比如:大家都要喝水,但是没必要每人家里都打一口井是吧,通常的做法是整个村里打一个井就够了,大家都从这个井里面打水喝。

饿汉:一次加载所有资源;
缺点:1、初始加载时间过长 2、有可能会加载到很多用不到的资源

class signal{                             4   public:                                 5     int* get_instance()                   6     {
W>  7       return &_data;//有资源就访问完了                                                                     8     }                                 9   private:                            10     static int _data;//static保证了一个类对象共享一份资源11 };                                                                   13 int signal::_data = 10;//静态成员类内声明,内外定义

>懒汉:用到哪个资源加载哪个资源;
缺点:使用过程中的模块加载时间长

class signal_instance{                   17   public:                                18     int* get_instance()                  19     {                                    20       if(_data == NULL)//这里二次判断,当资源为空的时候(需要初始化的时候)再去进行加锁,否则直接return21       {                                  22       _mutex.lock();//保证资源访问的安全,但由于每次先加锁再判断资源是否为空。若资源不为空不需要操作,加锁解锁操作就浪费了                         23       if(_data == NULL)//注意有了上面的判断这次的判断还是有用的,当A加锁时间片切换到B,B加载了资源要是不二次判断,A有创建了资源。就不对24       {25         _data = new int;//下面这两行表示当_data没有访问的时候再访问26         *_data = 10;27       }28       _mutex.unlock();29       }
E> 30       return _data;}                                                                                                      32   private:33     volatile static int* _data;//static保证一个类对象共享一份资源;volatile保证不会优化使data一会为空34 };

加锁:是为了保证线程安全
为什么要两次判断?
因为如果没有外层的判断就会导致不管资源是不是空都会进行一次加锁解锁,这个虽然没有问题但是效率比较低;因此加锁前进行一次判断,如果为空再进去,不为空就不进去了;
volatile作用是防止编译器过度优化,因为懒汉模式定义的是静态指针,因此在类外初始化一般为NULL,到使用的时候才会申请资源,但是编译器优化,有可能就会为了提高访问速度会把初始化这个空值直接放到寄存器中,导致后面进行赋值也只是改变内存中的值,因此使用volatile防止把值存入寄存器中;

适配器模式:这个设计模式的大体思想就是类的接口转换成客户希望的另外一个接口;但是只是知道个大概了解的 不深,当时在学stack和queue容器底层原理的时候解除了一点;因为这俩就是设计模式就是一个适配器模式;
它的底层就是通过一个容器来实现另一个容器的,(vector、list、dequeue)

为什么从寄存器访问速度快、从内存速度慢

因为内存的工作流程比寄存器(CPU内部的元件多出许多步。每一步都会产生延迟,累积起来就使得内存比寄存器慢得多;比如寄存器的工作方式很简单,只有两步:
(1)找到相关的位,
(2)读取这些位。
内存的工作方式就要复杂得多:
(1)找到数据的指针。(指针可能存放在寄存器内,所以这一步就已经包括寄存器的全部工作了。)
(2)将指针送往内存管理单元(MMU),由MMU将虚拟的内存地址翻译成实际的物理地址。
(3)将物理地址送往内存控制器(memory controller),由内存控制器找出该地址在哪一块内存上。从该块读取数据。
(5)数据先送回内存控制器,再送回CPU,然后开始使用。

如何优化来提高内存的速度
比较早的时候都是寄存器直接从内存中获取数据,现在给两个之间加了一个Cache(即高速缓冲存储器)是位于CPU与主内存间的一种容量较小但速度很高的存储器。由于CPU的速度远高于主内存,CPU直接从内存中存取数据要等待一定时间周期,而Cache中保存着CPU刚用过或循环使用的一部分数据,当CPU再次使用该部分数据时可从Cache中直接调用,这样就减少了CPU的等待时间,提高了系统的效率。

软硬链接

作用:通过给一个源文件创建软硬链接,就可以通过被创建出来的软硬链接文件来操作源文件。
软:ln -s test.txt test.soft
硬:ln test.txt test.hard

区别:
1、软链接:本质上是一个独立的文件,有自己的inode节点,文件数据中心保存源文件的路径,通过路径访问源文件。
硬链接:本质上和源文件没什么区别。都是一个文件的名称,与源文件公用一个inode节点,通过inode节点访问源文件数据。
2.删除源文件后。
软连接:软连接文件失效,无法访问源文件数据(因为记录的是源文件路径,还是通过源文件进行访问)
硬链接:因为公用同一个inode,只是inode链接数减1(只是删除了一个目录项),只有当inode为0才是真的被删除。
3.是否跨分区
软链接:因为存的源文件路径(唯一的,只要路径对就行),所以可以跨分区
硬链接:每个分区有各自独立的inode节点,而硬链接和源文件共用同一个inode节点。出了自己的分区,就会和其他分区节点冲突。所以不能跨分区

补充知识
链接数:一个inode节点对应几个目录项
删除一个文件,文件并不会立即被删除,而是直接删除了目录项信息,inode链接数-1,之后-到0时,文件才真的被删除

用户态内核态&&文件系统分区

用户态:进程运行库函数,程序员自己写的代码
内核态:完成系统核心功能,比如调用系统调用接口
从用户态到内核态:系统调用(比如一些接口printf fwrite write),中断(比如kill-9),异常(除0)

文件系统
文件系统就是磁盘上管理文件的系统
从左到右:超级块——inode位图——数据块位图——磁盘存储空间(文件的存储也类似分页管理,把文件的信息存到inode上,方便存取)
超级块——文件统筹信息、位图、磁盘块
inode位图——标记inode节点的空满状态
inode节点——文件大小、文件占用块数、数据块指针
数据位图——磁盘块空满状态
磁盘块——存文件
文件的存储流程
1、通过超级块获取各区域起始位置(找到ionde位图和数据块位图)
2、根据位图信息,找到空的inode节点,和空的数据块
3、把数据存到数据块中,再把对应的文件信息存到inode位图中
4、将文件名和inode节点号存在所在目录文件中(因为目录和文件分离)
文件获取流程
1、根据文件路径打开目录,获取到目录项(得到inode节点号)
2、通过inode节点号->inode节点
3、根据inode节点中存储的文件大小地址等信息得到文件数据

动态&&静态

动态链接和静态链接
动态链接只是在编译的时候把函数实现在库中的位置记录下来,等到链接的时候再去库里面找;
静态链接直接把函数实现在编译的时候直接加载到可执行程序内

动态库和静态库
动态库:windons下.dll文件是动态库;linux下.so是动态库
这类库在编译的时候只是将库中函数的位置记录,等到链接的时候再去库函数中调用所以动态库不能单独运行;
静态库:windows下是.lib文件;linux下是.a文件
这类库在遍历的时候直接将库函数加载到程序中,所以生成的可执行程序一般较大,但优点就是可以独立运行不需要依赖库

IPC&&管道&&消息队列&&共享内存&&信号量

进程间通信的作用?
因为每个进程都有自己独立的虚拟地址空间,进程之间无法访问,因此需要进程间通讯来达到进程信息交互的作用。
管道——消息队列——共享内存——信号量

管道(自带同步互斥)
管道是一种半双工通讯,意思就是可以选择方向的单向传输。它的本质就是内核中的一块缓冲区,多个进程访问同一块缓冲区实现进程间通讯。它的声明周期随进程,所有打开管道的进程退出,资源就释放了

管道又分命名管道和匿名管道。他们两个都是内核中的缓冲区。不过命名管道的缓冲区是具有标识符的,可以用于同一主机的任何进程间通讯。而匿名管道的缓冲区没有标识符,同时匿名管道只能用于具有亲缘关系的进程间通讯,因为匿名管道只能通过子进程复制父进程的方式获取相同管道的操作句柄

不过他俩的特性相同
若管道没有数据则read会阻塞,若管道数据满了则write会阻塞
若管道所有读端关闭则write会触发异常,若管道所有写段关闭则read读完数据返回0,不再阻塞。

共享内存(不自带同步与互斥)
在物理内存上开辟一块空间,多个进程可以将开辟的同一块物理空间和自己的虚拟地址空间形成映射,来访问这块物理空间,实现数据共享。(它的生命周期随内核,需要手动删除或者重起)
特点:
相比于管道的两次读取数据(先要写入内核,再从内核中拿),共享内存直接通过虚拟地址访问内存,减少的数据的两次拷贝,因此共享内存是速度最快的通讯方式

消息队列
多个进程通过访问同一个队列,进行添加节点或者获取节点来实现通信。消息队列自带同步与互斥,生命周期随内核

信号量
信号量的本质就是一个计数器+等待队列+进程等待和唤醒接口。
它是通过计数器对数据资源进行管理,进程访问资源之前,通过技术判断资源能否访问。如果计数器等于0,则不能访问,这时候进行就到等待队列等待,知道可以访问,然后对其唤醒。声明周期随内核

信号

信号是用于进程的事件通知的,有可靠信号和非可靠信号。
1-31:是非可靠信号,可能会信号丢失,而34——64是可靠信号,信号不会丢失。信号丢失的意思就是,可靠信号来一次添加一次,而非可靠信号只添加一次,所以多的信号就会被无视,也就是信号丢失。
信号的注册
pcb中有个sigset_t结构体,中有个位图,进程收到那个信号就把那个位图置为1,表示收到信号。不过这样只能表示收到没收到,不能表示收到的次数,因此还会创建一个sigqueue,来一个信号就往链表插入一个节点,这样就把可靠信号和非可靠信号区分开了。
信号的处理方式
1.默认处理:就是按操作系统定义号的信号处理方式处理
2.忽略处理:什么都不做
3.自定义处理:使用自定义函数,替换内核中的处理函数,这样信号到来就会调用我们定义的函数

信号的阻塞
信号阻塞并不是步接受信号,而是在block数组中标记信号,block数组中的信号到来暂不处理。

volatile
保持内存可见性,防止编译器过度优化;当代码的优化等级提高以后,就会把const的变量从内存转拷贝到寄存器上,然后从寄存器上获取数据提高速度。此时如果对变量进行更改是没用的,volatile的作用就是保证编译器每次都从内存中读取数据。

函数的可重入和不可重入
在多个执行流中同时进入一个函数,如果重入后不会造成代码的二义性就称为可重入,如果有可能造成代码的二义性就是不可重入。

sigchild 当子进程退出时,会向父进程发送SIGCHLD信号,而父进程就是根据是否收到这个信号来判断子进程是否退出的。
sigpipe:当服务器关闭的时候,如果客户端再对齐发送信号,系统会发出一个 SIGPIPE 信号给客户端进程,导致客户端进程退出。

IO多路复用(多路转接)&&事件触发

IO多路复用(多路转接模型)就是针对大量描述符进行就绪事件监控的,让进程能够对就绪的描述符进行操作,提高任务处理的效率。比如有100万用户与一个进程保持这TCP链接,而每一时刻只有几百个TCP链接是活跃的,也就是说每一时间进程只需要处理这100万个链接中的一小部分,为了提高效率这种情况下就用到了多路转接模型。常用多路转接模型有select、poll、和epoll。(多用于TCP,因为TCP为每个链接都创建了套接字)。
select & poll & epoll

select模型的平台移植性比较好但缺点也很明显。第一、select用于监控的Fd_size数组默认是1024大小,因此描述符的监控是有最大数量限制的;第二、它的监控原理是轮训遍历的,性能会随着描述的增多而下降。
poll和select大致相同,它是select的优化版本。poll模型的底层是一个结构体,成员有fd、events、revents。分表表示需要监控的文件描述符、需要监控的事件、实际就绪的事件。监控调用返回后,通过每个节点的revents成员来判断当前描述符就绪了哪些事件。因此poll模型的优点是监控描述符没有了上限。第二、poll通过事件结构体简化了多种集合操作,所以不需要每次重新添加描述符。但poll的缺点就是平台移植性比较差,而且它也要把数据拷贝到内核进行轮训遍历,性能会随着描述符增多而下降。

epoll是目前来说相对比较好的多路转接模型;当某个进程调用epoll_creat后内核会创建一个eventpoll对象,创建epoll对象后可以用epoll_ctl()向内核添加新的描述符,已经注册的描述符会被维护在一颗红黑树上。而将已经就绪的描述符放在一个双向链表中。进程调用epoll_wait就可以得到已经完成 的描述符了。
epoll模型的优点就是他通过红黑树来维护描述符,因此IO效率不会随着描述符的增多而降低。

IO事件触发包括水平触犯(LT)和边缘触发(ET),水平触发意思是 只要这个文件描述符还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;,而边缘触发是指 不管缓存区中是否还有数据,只有数据到来才触发。
select和poll是水平触发;
epoll既可以水平也可以边缘

gdb

gdb:不能直接使用,因为Linux默认生成的是relese版本的,所以要在生成的时候要gcc -g 生成debug版本的,才可以调试。
开始调试:run(直接开始跑)/start(重新开始)
流程控制:step(单步动)/next(函数内单步动,但遇到其他函数会一步执行)/until(until file:num跳转到哪一行)
list(显示某行附近几行)/continue(继续执行)
断点调试:break(break num断点打在第几行)/watch(成员变量的监视 watch 变量)/info break(查看断点信息)/delete break id(删除断点,不加断点id就是全部删除)
调用栈的查看:backtrace(哪里出问题了)

makefile:项目自动化构建工具
make:makefile解释器,对makefile中记录的规则进行解释执行,完成项目构建
makefile流程:找到makefile文件中的第一个目标对象,然后根据依赖对象生成目标对象;若目标对象的依赖对象也需要生成,则会在下面的生成规则中找依赖对象的生成规则,然后先生成依赖对象再生成目标对象。

main:(目标对象)   main.c(依赖对象)
table(写一个table) gcc $^ -o main(执行过程)

.PHONY是一个伪目标,可以防止在Makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突;(采用.PHONY关键字声明一个目标之后,make并不会将其当做一个文件来处理。因为假目标并不与文件关联,所以每次构建假目标时它所在规则中的命令一定会被执行)

预定义变量
main:main.c child.c
$<:第一个依赖对象-mian.c
$^:全部依赖对象-mian.c child.c
$@:目标对象-main

银行家算法

它的思想是:允许进程动态地申请资源,系统在每次实施资源分配之前,先计算资源分配的安全性,若此次资源分配安全(即资源分配后,系统能按某种顺序来为每个进程分配其所需的资源,直至最大需求,使每个进程都可以顺利地完成),便将资源分配给进程,否则不分配资源,让进程等待。——解决死锁问题

//银行家算法
int n,m; //系统中进程总数n和资源种类总数m
int Available[m]; //资源当前可用总量
int Allocation[n*m]; //当前给分配给每个进程的各种资源数量
int Need[n*m];//当前每个进程还需分配的各种资源数量
int Work[m]; //当前可分配的资源
bool Finish[n]; //进程是否结束

算法流程:
我们将第i个进程请求的资源数记为Requests[i]

1.如果Requests[i]<=Need[i],则转到2。否则,返回异常。这一步是控制进程申请的资源不得大于需要的资源

2.如果Requests[i]<=Available,则转到3,否则Pi等待资源。

3.如果满足前两步,那么做如下操作:

Available[i] = Available[i] -Requests[i]
Allocation[i] = Allocation[i]+Requests[i]
Need[i]=Need[i]-Requests[i]
调用安全判定算法,检查是否安全
if(安全){资源分配
}else{资源撤回。第三步前几个操作进行逆操作
}

安全判断算法:

Boolen found=false;
work=Available;
finish[n]=false;//初始化
while(true){found=false;for(int i=0;i<n;i++){//循环一次if(finish[i]==false && Need[i]<=Work){//进入循环表示可分配,并且回收资源Work+=Allocation[i];found=true;finish[i]=true;}}if(found=false){//不存在资源分配的情况就跳出break;}
}
for(int i=0;i<n;i++){if(Finish[i]==false){//如果有没分配的说明会死锁return "deadlock";}
}

操作系统--常见秋招、春招问题汇总(持续更新)相关推荐

  1. 在各大厂的秋招春招中,笔试面试都是必考的

    进修嵌入式须要那些内容? 数据构造与算法 这局部是程序员的必修课.在各大厂的秋招春招中,笔试面试都是必考的.常见的数据构造如链表,二叉树,堆,队列,常见排序算法及其改进(快排,归并,冒泡,插入)等都是 ...

  2. 校招/社招/秋招/春招求职指南

    秋招.春招 1.招聘人数:秋招多余春招 2.招聘时间:秋招一般7月开始,大概一直延续到10月底,但是大厂(BAT)都会开始的很早:春招最佳的时间在3月,次佳是在4月,进入5月基本上就很少了. 3.难度 ...

  3. 研究生、本科生Java开发、后台、软件工程师秋招春招经验

    研究生.本科生Java开发.后台.软件工程师秋招春招经验 在2020年10月份的时候结束了自己的秋招过程.在秋招过程中,我也算是大厂中厂都拿过多个offer.在这个过程.在这半年的秋招过程中,通过自己 ...

  4. 【秋招/春招】投递岗位记录【快速法】

      除了投递以外,投递后秋招/春招进程的记录,也很重要,快速记录投递过程,进行信息检索与汇总,也是我们需要学会的能力.   以下介绍一下我自己个人记录的方法: Excel表格记录法 一.表格内容设计 ...

  5. 2020届校招总结(秋招+春招)

    2020届校招总结(秋招+春招) 目录 个人情况 秋招 春招 总结 个人情况 末流211本科,通信工程专业,在实验室主要是做机器视觉和深度学习相关,实习在一家500强外企做数据挖掘,主要是在研究时序数 ...

  6. 自己的秋招春招经历,现在的发展情况

    本人大四学生一枚,即将毕业,目前还未找到工作,说一下自己的秋招春招的经历. 秋招经历 由于个人的原因,在大三暑假的时候,自己并没有去找实习,这里就缺少了以后在就职过程中的一个加分的点.在2019年9月 ...

  7. 吉大计算机专硕报录比,22考研院校报录比汇总(持续更新)

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 22考研院校报录比汇总(持续更新) 每个考研的小伙伴最关心的就是你所报考学校的报录比,因为这直接关系到你报考院校的难易程度,从中可以看出各高校的整体录取情 ...

  8. 【帆软报表】使用技巧及常见问题汇总-持续更新

    [帆软报表]使用技巧及常见问题汇总-持续更新 1.重复与冻结设置,做用:冻结区域 模板-重复与冻结设置 2.单元格有效小数设置 选中单元格-格式-数字-#0.00 3.图表中有效小数设置 图表属性表- ...

  9. iOS精品资源汇总(持续更新)

    文章目录 引言 I.iOS自定义视图相关热门资源 1.1 <用户协议及隐私政策>弹框 1.2 电子签名 1.3 商品详情页 1.4 上传图片视图的封装[支持删除和添加] 1.5 查看风险商 ...

  10. Telegram Android源码问题汇总 持续更新

    libtgvoip目录为空 git clone下来的工程中带有submodule时,submodule的内容没有下载下来,执行如下命令 cd Telegram git submodule update ...

最新文章

  1. 如何建设数据安全体系?
  2. javascript 异步模块加载 简易实现
  3. 【Android 进程保活】应用进程拉活 ( JobScheduler 拉活 | JobScheduler 使用流程 | JobService 服务 | 不同版本兼容 | 源码资源 )
  4. Oracle NVL函数的用法
  5. PostgreSQL新手入门教程
  6. git 拉取远程分支到本地
  7. share-jquery
  8. SpringBoot之项目启动
  9. 【英语学习】【WOTD】prodigous 释义/词源/示例
  10. 【SpringMVC笔记】Ajax 入门(jQuery.ajax)
  11. ajax同步异步问题
  12. 吞了1000瓶老干妈的南山头铁鹅,Python制作千图成像(附上源代码和应用程序)...
  13. 使用log4j失误导致系统假死,记录一下
  14. HDU 5336 BFS
  15. 摩尔定律,贝尔定律,吉尔德定律,麦特卡尔夫定律
  16. 北京地铁拥挤度实时查询
  17. android 查看UID
  18. 湖北武汉施工员报考施工员安全意识的建立方法建筑七大员报考
  19. 【线性代数】深入理解矩阵乘法、对称矩阵、正定矩阵
  20. zcmu-1653,1654...【水题集合】

热门文章

  1. 元学习:实现通用人工智能的关键!
  2. 23位子网掩码是多少_23位子网掩码 网关计算
  3. adguard自定义_AdGuardHome拦截页面模版下载,自定义AdGuardHome拦截页教程,修改AdGuardHome默认拦截页...
  4. “语象观察”-爬取人民日报并统计词频
  5. cocos creator 3.0见缝插针(口红机)
  6. 阿里云短信平台实现手机验证码登录
  7. 各大短信平台接入方法
  8. 游戏建模小白必看的游戏人物模型贴图制作方法及制作小技巧
  9. 组织要为每一个员工赋能
  10. DFS(深度优先搜索算法)入门