面试笔试中的操作系统问题

关于操作系统的问题吧

1:线程和进程

https://blog.csdn.net/limingandritchie/article/details/61195918 靠背的概念

2:进程中通信的方式

https://www.cnblogs.com/LUO77/p/5816326.html
先贴一个吧具体的等看完apue再来写总结吧

3 计算机的启动过程蛮有意思的,可以看一看,了解就好

https://www.cnblogs.com/mq0036/p/7125810.html

4 请分别简单说一说进程和线程以及它们的区别

进程是具有一定功能的关于某个数据合集的一次运行活动,是资源调度和分配的一个基本单位
关于进程的信息储存在PCB中,主要有程序计数器,堆栈,全局变量,内存地址阿,文件信息,进程号,等等,寄存器等等
线程是CPU调度和分配的基本单位,线程一般共享进程的信息,但是也有自己的信息,比如程序计数器,堆栈
寄存器,状态等等
多进程的资源是不共享的,因此独立性更强,内存空间是互相独立的,安全性较强,但是开销较多速度较慢
多线程共享资源,比较时和多处理器系统,线程的创建销毁,调度更加快速,因此多线程速度更快,但是
稳定性较差

5 进程通信的方式有哪些

https://www.cnblogs.com/LUO77/p/5816326.html
1 管道,是一种半双工通信方式,只允许数据单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2 命名管道,是一种半双工,但是取消了只能在父子进程之间传递的限制
3 流管道,全双工
4 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5 共享内存:是映射一段能被进程共享的内存端,以达到通信的方法,是最快的IPC方式
6 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
7 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
8 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生

线程可以再分为两类:

一类是用户级线程(user level thread)。对于这类线程,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。在应用程序启动后,操作系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序通常先在一个线程中运行,该线程被成为主线“程。在其运行的某个时刻,可以通过调用线程库中的函数创建一个在相同进程中运行的新线程。 用户级线程的好处是非常高效,不需要进入内核空间,但并发效率不高。

另一类是内核级线程(kernel level thread)。对于这类线程,有关线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。内核维护进程及其内部的每个线程,调度也由内核基于线程架构完成。内核级线程的好处是,内核可以将不同线程更好地分配到不同的CPU,以实现真正的并行计算。

事实上,在现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上,相当于是一种折中方案。

线程同步:

1 互斥量:表示对共享资源一次只能有一个线程访问,而其他想要访问共享资源的线程必须等待拥有共享资源的放弃
2 信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
3 事件:用事件通知的方式控制线程同步,可以用于实现多线程优先级的比较操作
4 读写锁: 有三种状态,不加锁,加读锁,加写锁,当处于写锁状态时,不允许任何的加锁操作,本质上时互斥锁
当处于读锁状态时,允许加读锁,适用于大量读操作的数据结构
5 自旋锁:与互斥锁类似,但是不是通过休眠进程使进程阻塞,而是获取锁之前一直处于忙等状态,适用于
持有锁的时间较少,而且线程并不希望在重新调度上花太多的成本

什么是缓冲区溢出?有什么危害?其原因是什么?

缓冲区为暂时置放输出或输入资料的内存。

缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。

缓冲区溢出是一种非常普遍、非常危险的漏洞,在很多软件与操作系统都存在这种问题。
计算机中,缓冲区溢出会造成的危害主要有以下两点:

程序崩溃,导致拒绝服务
跳转并且执行一段恶意代码
造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入是否合理。

死锁

主要是指在两个或多个并发的进程中,进程持有某些资源,而又等待其他进程所拥有的资源,从而造成了一种
互相等待的状态,如果没有外力的干预就会一直保持这种状态

主要有四种条件:
1 互斥,某些资源同一时间只能被一个进程所拥有
2 占用并等待,一个进程因请求资源而阻塞时,对已获得资源保持不放
3 不抢占,即要等待进程主动释放资源,不能抢占
4 循环等待条件:即资源请求图有环

有三种方法解决死锁问题:
1 死锁防止,主要用限定进程申请资源的方式以保证不会进入死锁状态,例如规定进程申请资源时,不能占用其他资源,一次性申请所有资源,在没有资源时才能申请资源,要求每个资源按照递增的顺序申请资源。。
但是这种方法资源利用率比较地
2 死锁避免,是指动态的检测每一次申请是否会导致死锁,不会则允许申请,否则不允许,
从而产生出,如何判定安全性算法,资源请求算法,其中银行家算法是代表
3 假设永远不会产生死锁,这种方法需要判定是否有死锁状态,以及如果出现死锁状态怎么处理了,是一次性释放所有死锁的进程,还是一个一个释放知道没有为止,还是抢占处于死锁状态的进程

银行家算法:

进程有哪几种状态

就绪,运行,等待
就绪->运行
运行->等待
等待->就绪
运行->就绪

中断与系统调用

中断

所谓中断是指cpu在执行某一个任务的时候,收到中断后,停止执行当前的任务,转而去处理这个中断
等待中断处理完后,再回去执行这个程序
中断一般分为三种:
1 因为计算机硬件异常或故障所发出的异常,称为内部异常
2 执行了某些特殊指令所引发的中断成为软中断
3 外部设备所发出的中断,成为外部中断

与中断有关的当然是中断处理程序拉,用来专门处理中断的的程序,成为中断处理程序,
寻找中断程序在哪的成为中断向量

另外一个是有关中断的优先级
机器中断 > 时钟中断 > 磁盘 > 网络设备 > 终端 > 软件中断

系统调用

首先我们知道人物再操作系统上的执行分为用户态和内核态
当应用程序一般再用户态执行,但当应用程序需要操作系统的某些服务时,就需要进入到内核态
向操作系统发出调用服务的请求
当系统发出系统调用时,程序就会从用户态转为内核态,知道调用完成,返回结果给应用程序,
回到用户态

户态和核心态之间的区别

https://blog.csdn.net/youngyoungla/article/details/53106671
用户态能使用自己的内存地址和指令,内核态可以使用某些特殊的指令,
这些特殊的指令用户态不能调用

IO多路复用

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

常见的IO复用实现
select(Linux/Windows/BSD Unix), epoll(Linux),kqueue(BSD/Mac OS X)

IPC实例:

1 管道:半双工通信,用于父子进程间
linux中的示例:

#include<unistd.h>
#include<stdio.h>
#include<string.h>
int main()
{
	int pipefd[2];
	pipe(pipefd);
	pid_t pid;
	char buf[10];
	if((pid=fork())<0){
		printf("fork error\n");
	}
	else if(pid>0){
		sleep(2);
		close(pipefd[1]);
		printf("fsfsf");
		memset(buf,0,sizeof(buf));
		read(pipefd[0],buf,5);
	//	buf[5] = 0;
		//printf("%d\n",strlen(buf));
		printf("%s\n",buf);
		write(1,buf,5);
	}
	else{
		close(pipefd[0]);
		write(pipefd[1],"hello",5);
	}
	return 0;
}

2 popen pclose

popen函数
FILE *popen(const char *cmdstring,char *type)
是指先创建一个管道,fork一个子进程,关闭未使用的管道端,然后exec执行cmdstring,然后返回一个文件指针,模式w绑定cmdstring的标准输入,r绑定cmdstring的标准输出

3 协同进程

协同进程是指某个进程产生另一个进程的输入,而又读入其输出则成为协同进程

一般用两个管道完成

协程版A+B

#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_nal(int sig)
{
	printf("SIGPIPE CAUGHT\n");
	exit(1);
}
int main()
{
	int fd1[2];
	int fd2[2];
	int n;
	pid_t pid;
	char line[1024];
	pipe(fd1);
	pipe(fd2);
	signal(SIGPIPE,sig_nal);
	if((pid=fork())<0)
	{
		printf("fork error");
	}
	else if(pid>0)
	{
		close(fd1[0]);
		close(fd2[1]);
		while(fgets(line,1023,stdin)!=NULL)
		{
			n = strlen(line);
			write(fd1[1],line,n);
			n = read(fd2[0],line,1023);
			line[n] = 0;
			fputs(line,stdout);
		}
	}
	else{
		close(fd1[1]);
		close(fd2[0]);
		dup2(fd1[0],STDIN_FILENO);
		dup2(fd2[1],STDOUT_FILENO);
		close(fd1[0]);
		close(fd2[1]);
		execl("./coprocess","coprocess",(char *)0);
	}
}
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
	int n,a,b;
	char line[1024];
	while((n = read(STDIN_FILENO,line,1023))>0)
	{
		line[n]  = 0;
		if(sscanf(line,"%d%d",&a,&b)==2)
		{
			sprintf(line,"%d",a+b);
			n = strlen(line);
			if(write(STDOUT_FILENO,line,n)!=n)
			{
			//	err_sys("write error");
			}
		}
		else{
			write(STDOUT_FILENO,"invalid args\n",13);
			//	err_sys("write error");
		}
	}
}

命名管道

半双工通信模式,取消了只能在相关进程中通信的限制

消息队列

存放在内核中的消息的链接表,由消息队列标识符标识,
在linux中每个队列有一个msqid_ds结构与其对应
用msgget(key,flag) 获得key对应的队列标识符
msgctl(msqid,cmd,msqid_ds *) 控制队列
msgsnd(msqid,const void *ptr, nbytes,flag)
ptr 指向mymesg{ mytype,char} 发送一条消息
msgrcv(msqid,ptr,nbytes,type,flag)
接受一条消息 更新msqid_ds
消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点

信号量

本质上是一个计数器,用于为多个进程提供共享数据的访问

先说一下PV操作:

P操作:
(1) 测试控制该资源的信号量
(2) 若此信号量值为正,则进程使用该资源,然后信号量值减一
(3) 否则,进程进入阻塞队列,如果被唤醒,则返回步骤一
V操作:
(1) 信号值加一,然后唤醒一个阻塞队列的进程

在linux中信号量是一个信号量集
每个信号量集维护一个semid_ds结构
每个信号量是一个无名结构体
semget()函数获得一个信号量ID
semctl() 设置信号量集
semop() 去执行pv操作

共享内存

是映射一段能被进程共享的内存,进程之间能通过内存进行通信,注意同步问题,是最快的IPC方式
shmget() 获得key对应的共享储存id 当key为IPC_PRIVATE时 创建一个新的
shmat() 获得指向shmid的指针
shmctl() 控制
shmdt() 解除联系,引用减少

套接字:

可以用于本机中的进程之间的通信,也可以用于不同主机之间的通信

信号机制

进程

进程是一个活动实体,进程不止程序代码,
进程包括文本段,程序计数器,寄存器啊,堆栈啊,数据段啊,pcb这些
pcb中储存了进程状态啊进程编号啊程序计数器,寄存器啊,内存界限啊,打开的文件啊,也就是文件描述符

其中寄存器有累加器,索引寄存器,堆栈指针,通用寄存器等等
内存管理信息:这类信息包括基址和界限寄存器的值,页表或者段表等等

操作系统的东西又忘了啊,再巩固巩固!!!!!!!!!!!!!!!!!!!!!!

1 多线程和单线程的区别和联系

进程:
进程是一个活动实体,是资源分配和调度的基本单位,包含了一个程序运行所需要的全部信息,
例如,进程号,正文段,数据段,堆栈段,程序计数器,文件信息,进程号啊,内存地址等等
线程:
线程是进程中的一个执行流,是cpu调度和分配的基本单位,可以共享进程的信息,但是也有自己的基本信息,例如,程序计数器,盏,线程状态,寄存器值

单线程:只有一个主线程的程序称为单线程,这种程序只能顺序的执行程序的代码,如果执行到某一部分阻塞了,那么其他部分得不到执行,造成cpu利用率的下降
多线程:多个线程并发的执行,当某一个线程阻塞时,可以切换另一个线程执行,提高cpu利用率,但是上下文切换时会导致额外的时间开销

如何指定多个线程的执行顺序

#include<pthread.h>
#include<unistd.h>
#include<iostream>
#include<cstdio>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int stat;
pthread_t my[10];
void* print(void* index)
{
	int i =*(int*)index;
	while(1){
	pthread_mutex_lock(&mutex);
	//cout<<"fwfwfwff"<<endl;
	if(stat==(i+1)){
		cout<<stat<<endl;
		stat++;
		pthread_mutex_unlock(&mutex);
		break;
	}
	else{
		pthread_mutex_unlock(&mutex);
	}
	}
	pthread_exit((void*)0);
}
int fc[10];
int main()
{
	stat = 1;
	//pthread_mutex_lock(&mutex);
	for(int i = 0;i<10;i++){
		fc[i] = i;
		pthread_create(my+i,NULL,print,(void*)(fc+i));
		pthread_detach(my[i]);
	}
	sleep(10);
	pthread_mutex_destroy(&mutex);
}

线程和进程的区别

这个应该是必考的:
进程:是一个活动实体,是资源分配和调度的基本单位,包括了一个程序所运行的必备条件,包括
进程号,堆栈啊,程序计数器,堆栈指针,内存地址,以及打开的文件信息(面试的时候可以不说内纯地址,着重说大开的文件信息),进程与进程之间是相互独立,所以多进程的健壮性更强

线程是CPU调度和分配的基本单位,线程是进程的一个执行流,只拥有,程序计数器,寄存器,栈等少量资源,线程共享进程的资源和内存地址,所以需要线程之间通信较为容易,但是需要特别注意同步的问题

在系统开销方面:
在创建和销毁进程时,进程所拥有的资源更多,所以相应的花销远大于线程的花销,相应的进行切换时,cpu需要保存和设置的资源,野更多,所以线程切换的效率更高

多进程与多线程的区别

数据共享、同步

数据是分开的:共享复杂,需要用IPC;同步简单

多线程共享进程数据:共享简单;同步复杂

各有优势

内存、CPU

占用内存多,切换复杂,CPU利用率低

占用内存少,切换简单,CPU利用率高

线程占优

创建销毁、切换

创建销毁、切换复杂,速度慢

创建销毁、切换简单,速度快

线程占优

编程调试

编程简单,调试简单

编程复杂,调试复杂

进程占优

可靠性

进程间不会相互影响

一个线程挂掉将导致整个进程挂掉

进程占优

分页和分段

https://blog.csdn.net/u014558484/article/details/52155959
首先明确为什么会引入分页分段技术
如果我们直接把程序装入内存,那么会带来三个问题:
1 各个进程之间的封闭性不够强,一个进程容易访问到另一个进程的内存地址
2 每次运行程序的地址都不固定
3 每次换出内存都需要把整个从程序换出去,效率较低

为此引入了分页技术

分页就是把页作为一个内存管理的基本单位,把程序装到1页1页中,
每次调度都以页为单位,这样就解决了上述第三个问题
分页技术是引入了虚拟地址,地址映射,物理内存地址,
虚拟地址每次都不会变,自然就解决了问题二,虚拟地址高位代表着页表号,低位为页内偏移地址
那么问题一自然就解决了,地址映射:内核为每个进程提供了一个页表用于地址映射

分段:
分页技术主要是针对内存管理来说的,对于编写者并不友好,有可能同一个部分的不再同一个页中,
这样不利用程序的独立性,给换入换出处理、存储保护和存储共享等操作造成麻烦。
虚拟地址由段号和段内地址组成,虚拟地址到实存地址的变换通过段表来实现。每???程序设置一个段表,段表的每一个表项对应一个段,每个表项至少包括三个字段:有效位(指明该段是否已经调入主存)、段起址(该段在实存中的首地址)和段长(记录该段的实际长度)。

记住每个段的大小是不固定的,而且虚拟地址是二维的,段号+段内地址,注意是虚拟地址
针对每一个虚拟地址,存储管理部件首先以段号S为索引访问段表的第S个表项。若该表项的有效位为1,则将虚拟地址的段内地址D与该表项的段长字段比较;若段内地址较大则说明地址越界,将产生地址越界中断;否则,将该表项的段起址与段内地址相加,求得主存实地址并访存。如果该表项的有效位为0,则产生缺页中断,从辅存中调入该页,并修改段表

银行家算法:

https://blog.csdn.net/cout_sev/article/details/24980627

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值