操作系统知识记录
死锁
一、死锁的定义:
多个进行相互等待对方资源,在得到所有资源继续运行之前,都不会释放自己已有的资源,这样造成了循环等待的现象,称为死锁。
二、产生死锁的四大必要条件:
①资源互斥/资源不共享
每个资源要么已经分配给了一个进程,要么是可用的,只有这两种状态,资源不可以被共享使用,所以所谓的互斥是指:资源不共享,如果被使用,只能被一个进程使用。
②占有和等待/请求并保持
已经得到资源的进程还能继续请求新的资源,所以个人觉得叫占有并请求也许更好理解。
③资源不可剥夺
当一个资源分配给了一个进程后,其它需要该资源的进程不能强制性获得该资源,除非该资源的当前占有者显示地释放该资源。
④环路等待
死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,环路上的每个进程都在等待下一个进程所占有的资源。
三、预防死锁的方法
①预防死锁的发生只需破坏死锁产生的四个必要条件之一即可。
②下面的方法开销非常之大,目前没有一个操作系统可以实现。
③因此,目前使用的方法是避免死锁,而不是预防死锁。
④这部分的内容大致浏览简单了解一遍即可,只要能在某些选择题中判断出选项对应的是下面四个方法中的哪个就可以了。
1、破坏互斥条件
方法:
如果允许系统资源都能共享使用,则系统不会进入死锁状态。
缺点:
有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。
2、破坏请求并保持条件
方法:
釆用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。
一旦投入运行后,这些资源就一直归它所有,也不再提出其他资源请求,这样就可以保证系统不会发生死锁。
缺点:
系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。
而且还会导致“饥饿”现象,当由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。
3、破坏不可剥夺条件
方法:
当一个已保持了某些不可剥夺资源的进程,请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。
这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺了,或从而破坏了不可剥夺条件。
缺点:
该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。
这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。
4、破坏循环等待条件
方法:
为了破坏循环等待条件,可釆用顺序资源分配法。首先给系统中的资源编号,规定每个进程,必须按编号递增的顺序请求资源,
同类资源一次申请完。也就是说,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中,只能申请编号大于Ri的资源。
缺点:
这种方法存在的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦。
四、避免死锁的算法
1、判断“系统安全状态”法
在进行系统资源分配之前,先计算此次资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程; 否则,让进程等待。
2、银行家算法
1、申请的贷款额度不能超过银行现有的资金总额
2、分批次向银行提款,但是贷款额度不能超过一开始最大需求量的总额
3、暂时不能满足客户申请的资金额度时,在有限时间内给予贷款
4、客户要在规定的时间内还款
五.鸵鸟算法
在计算机科学中,鸵鸟算法(英语:Ostrich algorithm)是一个忽略潜在问题的一种算法策略,这种策略对计算机程序可能出现的问题采取无视态度(类似于鸵鸟在遇到危险时将头埋在地里,装作看不见)。鸵鸟算法的使用前提是,问题出现的概率很低。
例如应对死锁问题时,当不会对用户造成很大影响 或者 系统很少发生死锁时,可以采取鸵鸟算法。
孤儿进程,僵尸进程和守护进程.
参考:https://www.cnblogs.com/wannable/p/6021617.html
1. 孤儿进程
正常情况下,父进程先结束会调用wait或waitpid函数等待子进程结束在退出,而一旦一个父进程不等待直接退出, 而它的一个或几个子进程仍然还在运行,那么这些子进程就会变成孤儿进程,孤儿进程将被init进程(pid=1)所收养,并由init进程对它们完成状态收集的工作
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <signal.h>
int main(void)
{
pid_t pid ;
signal(SIGCHLD,SIG_IGN);
printf("before fork pid:%d\n",getpid());
int abc = 10;
pid = fork();
if(pid == -1)
{
perror("tile");
return -1;
}
if(pid > 0) //父进程先退出
{
abc++;
printf("parent:pid:%d \n",getpid());
printf("abc:%d \n",abc);
sleep(5);
}
else if(pid == 0){ //值进程后退出,被托付给init进程
abc++;
printf("child:%d,parent: %d\n",getpid(),getppid());
printf("abc:%d",abc);
sleep(100);
}
printf("fork after...\n");
}
我们执行程序后由于子进程进入sleep(100),而父进程先退出.通过ps -ef命令我们可以知道,此时27710号进程的父进程变成了1号进程.也就是我们所说的init进程.
disda 27710 1 0 10:47 pts/1 00:00:00 ./review
disda 27713 25948 47 10:47 pts/3 00:00:00 ps -ef
2.僵尸进程
进程终止后进入僵死状态(zombie),等待告知父进程自己终止,后才能完全消失.但是如果一个进程已经终止了,但是其父进程还没有调用wait或waitpid函数获取其状态,那么这个进程就称之为僵尸进程.僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态可以提供父进程想要的信息.一旦父进程得到想要的信息,僵尸进程就会结束.
int main(void)
{
pid_t pid ;
//signal(SIGCHLD,SIG_IGN);
printf("before fork pid:%d\n",getpid());
int abc = 10;
pid = fork();
if(pid == -1)
{
perror("tile");
return -1;
}
if(pid > 0)
{
abc++;
printf("parent:pid:%d \n",getpid());
printf("abc:%d \n",abc);
sleep(20); //父进程后退出
}
else if(pid == 0){ //子进程先退出,未告知
abc++;
printf("child:%d,parent: %d\n",getpid(),getppid());
printf("abc:%d",abc);
exit(0);
}
printf("fork after...\n");
}
怎么才能避免僵尸进程呢?
程序注释的那句signal(SIGCHLD,SIG_IGN) , 加上就不会出现僵尸进程了
这是signal()函数的声明sighandler_t signal(int signum, sighandler_t handler),我们可以得出,signal函数的第一个参数是Linux支持的信号,第二个参数是对信号的操作** ,是系统默认还是忽略或捕获.
我们就可以知道signal(SIGCHLD,SIG_IGN)是选择对子程序终止信号选择忽略,这时僵尸进程就是交个内核自己处理,并不会产生僵尸进程.
3.守护进程
守护进程就是在后台运行,不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行。它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务.习惯上守护进程的名字通常以d结尾(sshd),但这些不是必须的.
创建守护进程的步骤:
- 调用fork(),创建新进程,它会是将来的守护进程.
- 在父进程中调用exit,保证子进程不是进程组长
- 调用setsid()创建新的会话区
- 将当前目录改成跟目录(如果把当前目录作为守护进程的目录,当前目录不能被卸载他作为守护进程的工作目录)
- 将标准输入,标注输出,标准错误重定向到/dev/null
#include <sys/types.h>
#incldue <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#incldue <unistd.h>
#include <linux/fs.h>
int main(void)
{
pid_t pid;
int i;
pid = fork(); //创建一个新进程,将来会是守护进程
if(pid == -1)
{
return -1;
}
else if(pid != 0){ //父进程调用exit,保证子进程不是进程组长
exit(EXIT_SUCCESS);
}
if(setsid() == -1) //创建新的会话区
{
return -1;
}
if(chdir("/") == -1) //将当前目录改成根目录
{
return -1;
}
for(i = 0;i < NR_OPEN;i++)
{
close(i);
}
open("/dev/null",O_RDWR); 重定向
dup(0);
dup(0);
return 0;
}
disda 26217 1 0 06:59 ? 00:00:00 ./dm01_demon 则出现了守护进程!
多线程和多进程适用场景
-
频繁创建和销毁优先多线程
-
需要进行大量计算优先多线程
-
强相关处理优先多线程
-
多核分布优先多线程
-
弱相关处理优先多线程
-
多机器分布优先多线程
I/O:epoll机制:epoll_create、epoll_ctl、epoll_wait、close
参考:https://blog.youkuaiyun.com/yusiguyuan/article/details/15027821