c++ 程序编译过程?
预处理:文件的包含、宏处理、语言扩展等
解释器:形成汇编文件
汇编器:形成目标文件
链接:将目标文件链接起来形成可执行文件
加载器:加载可执行文件 并执行
自动装箱和自动拆箱
Integer v = 1 其实发生了自动装箱 Integer v = Integer.valueOf(i); Integer.parseInt(“1”)是把字符串直接转化为基本数据类型。
int i = Integer.valueOf(1);其实发生了自动拆箱 即 默认调用了Integer对象的intValue方法。
new String(“abc”)创建了几个对象
答案:jdk 8 heap上的 String 对象 和 字符串常量池中的 字符串对象。
String 为什么是final
final 可以修饰类 方法 变量 修饰的类 不能被继承 修饰的方法不能被 重写 修饰的变量一定要进行初始化。首先final的类代表不能被继承了,其中存放字符的数组 也是final 的,这里只是引用指向不变,不是代表里面的值不变,又由于其为private 所以除了本类其他类都不能改变。
因为String 为final所以为线程安全的,不存在多线程问题。
因为字符串是不可变的,所以hashcode 就可以被缓存,所以很适合做hashMap的键。
如果字符串是可变的,那么如果你检查了字符串的之后,虽然现在是正确的,但有可能之后被改变,比如文件名、用户名、密码等重要的东西。是一个可以攻击的漏洞。
正是因为不可变所以虚拟机在堆区创建了一块专门存放字符串的常量池用来减少重复字符串的创建。减少内存消耗。
ArrayList 中modCount有什么作用?对ArrayList还有什么了解
modCount 是放置ArrayList 在迭代的过程中发生并发修改,即其他线程修改了list导致遍历可能出现未知错误。
ArrayList 中使用 new ArrayList<>()创建时候会将底层elementData数组置为{}(DEFAULTCAPACITY_EMPTY_ELEMENTDATA
);在真正添加元素的时候才会创建固定大小的数据。
这样的好处是实现懒加载的功能,在一个虚拟机进程中,只会有一个默认初始化空数组供多个ArrayList引用,减少内存空间使用。
ArrayList中会出现数组越界异常?
会,因为arrayList 在add时,进行两个操作一是判断是否size + 1超出现在的数组大小,如果未超出则进行elements[++size] = value 在多线程环境下,假设还有一个元素就需要扩容,ArrayList目前只能放一个元素后就需要扩容,这时候第一个线程经过第一步判断出不需要,之后线程时间片用完,第二个线程仍然可以经过第一步判断并且不扩容,此时elements[++size] =value之后这两个线程必然会有一个线程再执行后序真正放置的时候 会发生越界异常。
LinkedList底层数据结构是什么?get(index)时候有什么优化吗?
ArrayList 底层使用Object[] 来存储数据。而LinkedList 底层使用链表,即Node(双向链表),在get(index)的时候是需要O(n)的时间复杂度,但是做了一个小优化 比如 你的索引值 小于 size>> 1 则从头找,否则从尾找。 另外LinkedList 可以当做栈 或者队列来使用。poll 当元素不存在时不会抛出异常,而pop会抛出异常。另外 poll 等于pollFirst offer 等于offerLast
如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,
或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们
可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
作者:星期三不上班
链接:https://www.jianshu.com/p/9c7f5daac283
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
整型数在计算机中的怎么存储的?
补码。正数的源码 反码 补码 相同 负数的源码 符号位不变 其余反转 得到反码 反码加1得到补码。
为什么重写equals方法时建议重写hashcode方法?
通常我们是这样判断的
1、若两个对象相等,那么hashcode 应该一定相等。解释:比如hashMap 中 相等的对象如果不同的hashcode 则会放到不同的桶 显然是不符合逻辑的。
2、若两个hashcode相等 那么对象不一定equals 。逻辑上解释就是哈希表中的发生了碰撞。
3、若两个hashcode不相等 那么对象一定不相等。
4、若两个对象equals ,那么hashcode不一定不相等。
TCP为什么要三次握手和四次挥手后等待2MSL原因?
三次而不是两次 的原因:TCP 全双工传输协议,通信双方需要协商序列号;防止已经超时的TCP SYN 数据包重新建立连接。
四次挥手后time-wait 原因:防止新建立的连接收到上一次连接的过时数据包;通过2MSL之后未收到服务端的重传数据包,那么就代表服务端收到了客户端的确认信息 可以安全关闭连接。
syn flood 攻击如何防范?
1、监视无效连接 及时释放资源。
2、使用ddos 防护系统,在攻击者syn seq = x包到来之后首先由ddos防护系统处理返回syn = 1 ack = x + 1 seq = cookie ,之后若收到ack = cookie+1 则 证明该ip 为正常用户。
3、仍然使用ddos 防护,在syn = 1 seq = x 收到后ddos防护系统发送ack = cookie ,如果是正常用户则会发送RST = 1 seq= cookie + 1。
利用反射创建对象的方式?
Class.newinstance();
Class.getDeclaredConstructor().newinstance();
List<? extends A> 和List<? super A> 怎么理解?
List<? extends A> 这个容器放的是A 及其子类,那么可以知道的是我们肯定能拿到的是 A元素。所以get 操作是允许的。而put 是不允许的。
List<? super A>这个容器放的是A 及其父类,那么 List<? super A> 取出来的东西不能够确定类型,但是可以放A 以及A的子类。
泛型的好处是什么?
泛型提供了一种类型检查机制,避免了类型转换失败出现的问题,在编译期期间就确定类型,类型不匹配就会出错。
泛型提高了代码的可读性,不用等到运行的时候才强制转化。使用泛型即参数化类型,使得程序员一眼就知道要操作的是什么类型。
泛型可以有泛型类 比如 class A<T>{T value;}
泛型函数比如public <T> void test(T t){}
泛型接口比如public interface I<T>{}
?通配符提供了只读的功能 ? 其实就是? extends Object
他是放Object 对象及其子类对象的,既然多种 所以你放不进去,因为一种容器只能放一种类型,但是你可以取,你取到就是Object
public static void testWildCards(Collection<?> collection){
Iterator<?> it = collection.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
如果你想界定范围也是没有问题,比如? extends Base 那么你就能知道你肯定操作的是Base对象,是上界通配符,并且只能获取不能增加修改。
如果你想增加可以使用 ? super Base 下界通配符。
public static void testSuper(Collection<? super Fruit> collection){
}
ListIterator 和Iterator 有什么区别?
ListIterator 继承了Iterator 专门用来遍历List 对象的。比如除了Iterator 的hashnext() next() remove() 外 还能进行 add set ,也可以进行previous 向前遍历 ,比如你可以
ListIterator<Integer> lit = a.listIterator(10);//从索引为10 的地方开始
while(lit.hasPrevious()){
int tem = lit.previous();
}
也可以取得当前位置前后游标位置的index.
lit.nextIndex() lit.previousIndex()
普通for循环 和增强for循环遍历应该注意什么?
普通for循环删除时 元素会进行移动,数组大小也会发生变化 所以应该注意 比如删除list中 你删除元素后size会变,之后遍历可能会发生越界异常,甚至出现意料之外的结果比如ArrayList 中删除以“xx”开头的字符串,如果有连续俩个那么可能不会得到想要的结果。
增强for循环 其实使用了迭代器进行遍历,所以直接使用list.remove()会出现并发修改异常。
java 的实例构造器和类构造器?
<clinit>
是编译器自动收集静态变量的赋值、静态代码块。‘clinit’与实例构造器方法不同,编译器保证父类的‘clinit’先执行。所以第一个执行的肯定是Object 中的clinit 方法。
init 方法是实例构造器方法。可以有多个,使我们自己编写的构造函数。
所以 父类子类的初始化顺序为:
父类的clinit 方法执行 静态变量的初始化、静态代码块的初始化。
子类的clinit方法执行 今天变量 静态代码块的执行
父类的普通代码块的执行
父类的构造方法的执行
子类的普通代码块的执行
子类的构造函数的执行
java Error 和非受检异常 和受检异常?
Throwable 代表java中的异常 和错误。错误是Error 比如 StackOverFlowError OutOfMemoryError ;
Exception 有运行时异常(ArrayIndexOutOfBoundsException、ConCurrentModificationException、ArithmeticException)和受检异常(IOException )。
java 抽象类 和接口的区别是什么?
抽象类中可以有方法实现,也可以只有一个抽象的方法签名。如果子类没有实现其所有的抽象方法,那么子类也为抽象类。接口是一个规范集合,他规定了这个接口类应该提供什么功能。如果是抽象类可以不实现所有接口方法,如果是具体类 则实现接口的时候需要实现其所有方法。
面向对象特性 怎么理解封装 继承 多态
封装 指的是对现实世界可以用一个对象的属性来描述其组成,方法来描述其行为。并且可以控制其他对象的访问权限。
继承 是一种复用代码的方式。
多态 可以分为重载 重写 都可以看做一种多态 重载是一个类中的同一个方法名不同参数类型 从而展现出的不同行为,而重写是父类子类之间重写函数会有不同的行为。
Comparable 和Comparator 的区别?
Comparable 是一个形容词 说明 该对象是可比较的 他有一个compareTo方法 通过其正负值来表明大小关系。
Comparator是一个比较器,如果某些对象需要不同类型的排序我们可以定义自己的比较器,来实现先排序。
unix socket 和socket 有什么区别?
unix socket适合域间通信 即本机通信,而socket 可以本机 也可以网络上的不同主机通过ip 端口号通信。unix socket 是不需要将数据送入协议栈,网卡 进行网络上的传播,而是直接通过文件路径来进行 发送套接字和接收套接字的数据 复制。
unix socket 不需要打包拆包 计算校验和 、维护序列号、维护应答。
HashMap 在jdk1.8 和jdk1.7 有什么区别?
1、jdk1.8 数组+ 链表 + 红黑树 jdk 1.7 数组+ 链表
2、key的hash值的获取,jdk1.8是 (h = key.hashcode()) ^( h >>> 16)
jdk1.7由于未采用红黑树所以在hash值的获取上算法较复杂
3、jdk1.8中 设置了树化阈值8,链化阈值6。放置增加 删除元素带来的反复树化 链化的问题
4、jdk1.8扩容时采用了尾插法 而1.7 采用头插法。
2021 4 21 日新增理解:5 、jdk1.7 的扩容条件:size >= threshold && table[i] != null 而 jdk1.8 当size > threshold 就会扩容
java 的5种线程状态及其转换
new 新建状态 调用t1.start()后获得cpu时间片后进入running
running 运行态 t2.join() 或者o.wati() 会进入 waitting
blocked 阻塞态 running 进入临界区 可能会 进入 blocked blocked 状态获取到锁之后可以获得cpu进入运行态running
teminate run 方法执行完成 或者main方法结束 线程进入terminate
注意:o.wait()会释放锁 所以 应该在已经获取到锁的时候再操作。 Thread.sleep()不会释放锁。
方法区中存放的是什么?
类信息 方法信息 域信息 运行时常量池(jdk8 移到堆)jit代码缓存
接口和类的区别?
接口可以多实现 类单继承
接口中的变量必须是public static final ,抽象类的变量可以是public 、private、protected。
接口可以有default 方法,默认实现。抽象类也可以有非抽象方法。
HashMap 了解多少?
HashMap在jdk1.8底层数据结构是数组+链表+红黑树;hashMap 并不是在创建的时候就会初始化好数组大小,而是采用一种懒加载的方式,在真正put key,value
的时候才会初始化数组大小。涉及到的数据结构为Node(实现了Map.Entry<>接口)属性有key value next 以及hash 。注意这里的hash值并不是key调用hashcode 方法得到的hash值,而是key.hashcode()
的高16位与低16位异或得到的值。此方法可以使得哈希值更加与多bit相关,得到的值与数组长度相&后更加散列。当然数组的长度一定是2的幂次方,默认初始大小为16,加载因子为.75 。关于树化的参数有 Treeify_threshold = 8 以及MIN_treeify_capacity = 64最小树化数组大小 ,即当链化后长度为8是会尝试将链表转化为红黑树,但是不一定就会转化,而是判断当时的数组长度是否大于64,如果是则树化,否则只是加倍数组长度,即所谓的resize()扩容。
hashmap的put方法详细过程?
第一次put的时候会同时创建数组,首先将key,vlaue
构造出一个Node,然后根据Node的key的hash值与数组长度相&得到slot索引index,
1、table[index]
如果为null则直接放
2、table[index]
如果为单结点,即table[index].next == null
,则需要判断新加入的key 是否与这仅有的结点一致,一致的意思是不仅hash值相同,而且 equals比较也相同,如果一致的话,则直接替换,同时返回旧value值。
3、若已经链化或者将要链化,则需要遍历单链表,找到一致的结点就替换 否则直接尾部插入 若结点多于8则考虑树化;
4、若已经树化,即结点已经为TreeNode结点(红黑树结点 有parent left right prev 属性 同时继承Node),则需要将其加入到红黑树中。(加入红黑树的具体过程见下)
hashmap的put方法若是加入的Node结点的slot已经发生树化。如何在树中插入?
整体:如果该树中没有与该结点key一致的,则一直遍历到叶节点然后将新结点转化为TreeNode插入到树中,之后进行平衡操作。如果找到一致则将该已经存在的树结点返回,并进行新值替换。这里的难点是红黑树插入结点后的平衡操作。主要思想如下:
可以用语言概括如下:
若x的父结点为空说明x为根结点,设置x的颜色为黑色 并返回x 。
若x的父结点不空且颜色为黑 或者x的祖先节点为空则 返回root
若x的父结点不为空 祖先结点不为空 则
- 若父结点为红且叔父结点为红色 则 直接将父亲结点和叔父结点 以及祖父结点做变色操作,同时令x=xpp
- 若父结点为红色且叔父结点为黑色或者为null则若x为xp的右孩子 则需要左旋操作,跟新指针
x=xp;xp =x.p;xpp = xp.p;
最后进行右旋操作(以xpp为旋转点)xpp.red = true,xp.red = false;
贴一段代码吧
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
else if (!xp.red || (xpp = xp.parent) == null)
return root;
if (xp == (xppl = xpp.left)) {
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
hashmap 的remove(key)方法具体删除过程?
首先根据key的hash值判断slot,得到slot= tab[idnex],这时需要在该slot中查找key对应的Node,查找过程如下:
1、若是tab[index]为null 则直接返回null 值;
2、否则需要判断第一个Node的key是否与查找key 一致,若一致则查找结束 否则3、
3、这时应该判断结点的类型若是TreeNode 则需要在二叉树中搜索,若是链表则需要在链表中搜索结点。经过这一步若是存在 就找到了 node结点。
删除的过程同样分情况讨论:
1、若是node为null则直接返回null
2、若是TreeNode类型则需要在红黑树中删除。红黑树删除具体步骤大致如下
首先需要将红黑树顺带维护的双链表上的该节点删除。
然后在分情况将真正红黑树上的结点删除。删除之后可能会导致红黑树的性质不满足,则需要做平衡化操作。
3、若是普通的链表结点 则需要注意是不是第一个结点 是第一个结点需要将slot[index] = firstNode.next
否则 删除链表结点就好。
了解LinkedHashMap吗?它与HashMap有什么区别?
LinkedHashMap 继承了HashMap ,其内部存储结点也是继承了Node 自己的属性有before 和after,可知增加了双向链表来记录插入顺序 或者访问顺序。其中访问顺序是指不管是访问还是新插入都会将该结点移动到链表尾部,适用于LRU缓存。
了解TreeMap吗?
Treemap底层同样是红黑树,其一般操作 比如put get remove 等方法的时间复杂度为lgn 。主要特性是其Entry 按照key值排序。对于put 操作来说,首先从p=root开始遍历,若大于p.key则从右子树遍历,若小于p.key 则从左子树开始遍历直到为p = null或者直接遇到有相同的key (比较器比较)直接覆盖返回旧值,未找到相同key值则将遍历的最后结点记录,如果大于则将新结点设置为右结点,如果小于则将新结点设置为左节点。插入之后可能不符合红黑树的性质,则需要平衡操作。
了解java io框架吗?比如字节流字符流等
可以分为输入流和输出流(按照流向来分),输入流代表从网络或者文件或者字节数组等位置读到内存中,输出流代表从内存输出到文件或者其他地方。常见的有FileInputStream FileOutputStream (BufferedInputStream BufferedOutputStream 可以增强流的功能 ,使得将流中的内容一次读入缓冲区中,然后一次输出到磁盘上。好处就是减少磁盘io)
另外一种分类方法就是字符流、字节流的分类。字符流出现的意义:当我们使用字节流一个个字节显示中文字符时候会出现乱码的情况这是因为中文使用UTF-8时候是3个字节,而字节流一个字节一个字节的读,由此可以使用字符流来读取。
FileReader fr = new FileReader("./src/main/java/com/gaojl/a.txt");
char[] buf = new char[2];
int count = 0;
while((count = fr.read(buf))>0){
System.out.println(new String(buf,0,count));
}
fr.close();
比如下面的代码就可以进行文本文件的复制(对于图片视频 等文件不行)
FileReader fr = new FileReader("./src/main/java/com/gaojl/a.txt");
FileWriter fw = new FileWriter("./src/main/java/com/gaojl/b.txt",false);
char[] buf = new char[2];
int count = 0;
while((count = fr.read(buf)) != -1){
fw.write(buf,0,count);
}
fr.close();
fw.close();
字符流同样是有BufferedWriter 和BufferedReader 来提高效率;
BufferedWriter br = new BufferedWriter(new FileWriter("src/main/java/com/gaojl/b.txt",false));
br.write("大家好呀!!!");
br.close();
InputStreamReader OutputStreamWritter 是字节流和字符流转化的桥梁流。与FileReader 和FileWritter 不同的是转换流可以指定编码字符集,此类的应用背景是磁盘上的存储的都是字节,而我们内存中使用时候希望以字符的方式使用。所以当输出到磁盘时需要字符转化为字节 输入到内存时需要字节向字符转化。
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
System.out.println(str);
递归删除文件夹
public static void deleteR(File dir){
File[] fs = dir.listFiles();
if(fs.length > 0){
for(File f : fs){
if(f.isFile()){
f.delete();
}else{
deleteR(f);
}
f.delete();
}
}
dir.delete();
}
java Object.wait方法为什么建议将wait方法始终放在while(!condition)中?
由于wait 方法回释放所持有的锁,所以还得放在同步代码块中,为什么要放在循环中呢?因为可能存在不是notify /notifyAll、或者超时唤醒、以及异常停止 所存在的唤醒,称作虚假唤醒。
StringBuffer 和StringBuilder的区别?
StringBuffer 是线程安全的而StringBuilder 是非线程安全的。StringBuffer有一个toStringCache,当多次toString操作的时候提供缓存。
了解内核空间和用户空间吗?
软件中断和硬件中断的区别?
软件中断是运行的程序执行系统调用的时候发出的而硬件中断是外部设备发出的。中断是除cpu之外的组件发出的事件,代表着cpu对于该事件应该立即处理。
硬件中断比如 硬盘在写入一些数据块后可以发出、网络设备接收到数据包之后可以发出、键盘鼠标等操作也可以发出。硬件中断可以分为可屏蔽中断(可以执行优先级更高的中断)和不可屏蔽中断(必须立即处理)每个硬件中断都有一个硬件中断号,使得cpu知道哪个硬件发生了中断。硬件中断一般是异步的。
软件中断是正在执行的程序进行系统调用访问内核空间发出的,当任何中断发生后,cpu都会暂停执行正在运行的活动项目,转而执行中断处理程序。当中断处理程序执行完成后,再继续执行。
不同点:一般来说软件中断优先级高于硬件中断。
重点:中断是是cpu由用户态进入内核态的唯一途径
什么是系统调用?
系统调用是用户程序需要对系统管理的资源进行请求时,就需要操作系统介入。来统一管理调度系统资源。比如进程的管理、文件的管理、设备的管理、进程之间的通讯、内存的管理等等都需要操作系统统一管理。
我们之前说过 用户态切换到核心态仅仅中断才能够引发,那么是否意味着系统调用也是一种中断呢?其实可以这么理解。系统调用 会在我们使用系统资源比如操作文件write()时候会发生,其实翻译成指令时候会生成一个 int x 指令(int 代表 interrupt) x代表中断号,每一个中断号代表一种 中断处理名称。
操作系统的进程?
进程定义是程序的一次执行过程。进程实体=PCB (操作系统通过其来获取进程的各种信息) + 代码段+ 数据段。
进程切换:当操作系统分给进程的时间片用完或者 有更高优先级的进程抢占cpu、或者进程请求的系统资源不到位、都会使得进程发生切换。
进程同步 互斥 以及进程间通信
进程间的协作关系可以分为 同步 互斥 和通信等。
互斥实现方式有软件 (单标志、双标志先检查法、双标志后检查法、peterson法)
硬件互斥有(中断屏蔽的方法、tsl(test and set lock)指令 、swap指令)tsl 和swap都是先确定旧值,然后设置lock为true代表该线程占用临界资源,然后返回旧值如果旧值为false说明可以直接进入临界值,否则循环等待。
bool tesandset(Lock lock){
bool old = lock;
lock = true;
return old;
}
while(testandset(lock));
/**critical part**/
lock = false;
信号量 PV原语 可以实现互斥和同步
S 的数据结构为(注意下面是针对java 的伪代码)
class S{
int value;
//对于该信号量所代表的的资源的等待进程队列。即资源够多的时候会从该等待队列中取出 放入就绪队列中
LinkedList<Process> l;
}
wait原语
void wait(S s){
s.value--;
if(s.value < 0){
Lock(s.l)//lock操作是将此进程放到该资源等待队列尾部 同时进程会阻塞 不占用cpu 不会忙等待
}
}
singnal 原语
void signal(S s){
s.value++;
if(s.value <=0){
wakeup(s.l)//从就绪队列中取出队列头的线程 使得其获得资源 运行。
}
}
那么互斥量是怎么实现进程之间的同步的呢
信号量s 的pv 操作 ,p代表资源的申请 之后s.value–,v代表资源访问结束,只有会s.value++;所以要实现先后访问则需要初始化s.value = 0; 并且将前置操作之后进行v操作,后置操作之前进行p操作。
进程通信有 共享内存、管道(字节流、缓冲区大小受限、半双工通信)、消息队列()
消息队列使用msgget函数来创建,函数原型为 int msgget ( key_t key, int msgflg );key代表要创建消息队列的key,msgflag 代表一些控制信息,比如IPC_CREAT 表示存在key代表的消息队列存在则打开。
int msgsnd ( int msqid, struct msgbuf *msgp, int msgsz, int msgflg )
msqid: 由消息队列的标识符
msgp: 消息缓冲区指针。
消息缓冲区结构为:
struct msgbuf {
long mtype;
char mtext[1];
};
int msgrcv(int msgqid,struct msgbuf * msg,int msgsz, long mtype,int msgflg)
共享内存使用shmget来创建共享内存 返回共享内存标识符,使用shmat 将共享的地址空间链接到本进程的地址空间,之后便可以像访问本地地址空间一样访问
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
int shmget(key_t key, size_t size, int shmflg)
了解管程吗?相比于信号量管程有什么优点?Java 中有类似管程的实现吗?
信号量对于复杂的系统的同步互斥关系,需要对不同的进程加不同的PV操作,比如对于需要同步的进程需要对前置操作之后加V操作,后置操作之前加P操作,注意初始化信号量为0. 如果PV操作使用不当容易造成死锁的情况,而管程的出现将程序员从PV操作中释放出来,从而可以在代码层面实现进程的互斥与同步。Java中的Synchronized 关键字就可以实现管程互斥的功能,即各个线程一个时刻只能由一个线程访问synchronized修饰的代码段。再加上一些条件变量即可实现同步的功能。
死锁的产生的必要条件是什么?了解银行家算法吗?
死锁的四个必要条件为:互斥条件 不可剥夺条件 占有并请求条件 循环等待条件
银行家算法用来死锁的避免 将系统中的各类资源标识为一个数组,每个进程已经占有的资源、已经分配的资源、和再申请的最大资源 等来判断是否有一个安全序列来使得各进程能进行下去。
进程创建相关的函数?涉及的数据结构?
进程相关的数据结构即PCL(进程控制块),linux中的进程的数据结构是task_struct ,创建进程的相关函数有
fork 是创建的子进程完整复制父进程的资源,复制了内存的内容、以及task_struct的内容。(代码段会共享 其他比如数据段(初始化的全局静态变量)、BSS段、堆(动态内存的申请与释放:malloc以及free)、栈(临时变量比如方法体内定义的变量))
注意malloc 函数 涉及到的系统调用有brk 和mmap,brk 适合分配较小的内存空间(紧邻bss之上的较小空间),而mmap适合分配较大的内存空间(堆栈之间的一个空间)。
fork子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”,如果父子进程一直对这个页面是同一个页面,直到其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。这就是所谓的“写时复制”。
那么fork复制出来的进程与父进程一样 那么如何区分呢?其实父进程中fork()的返回值大于0,子进程fork()返回值为0,之后子进程可以利用系统调用execve来执行新的可执行文件。int execve(filename,char *argv[], char *envp[])如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
多级页表来进行 虚拟地址 和物理地址的转换 与单级页表有什么优点和缺点?(32位机器 页大小4KB 页所占的内存4B)
多级页表可以在转换时花费较少的内存:单级页表要想全虚拟地址空间转换,页表的大小 4GB/4kB*4B =4MB ,那么为每个进程就需要分配4MB来进行地址转化,而采用二级页表,每个页目录表项指向1k个页表项所以 地址转换的时候要想全地址转化,首先页目录表得全部分配,然后将页目录表指向的1K个页表项 那一块连续的内存,通过偏移量指向具体页表项。最多需要4KB + 4KB = 8KB。
可以离散存储页表
缺点是寻址次数增加。
地址转化技术使用的场景?
虚拟内存:给应用程序一种假象 ,每个进程可以使用整个计算机内存。
进程隔离:虽然虚拟地址空间一样,但是转化到真实的物理地址空间不一样。
进程间通信:共享内存就是通过shmat使得本进程虚拟地址转化到共享内存的地址。
linux软硬连接的区别是什么?
硬链接是使用ln 源文件 目标文件 创建某文件的硬链接。硬链接不能跨文件系统建立。硬链接文件与原文件使用一个inode。软链接 会创建新的inode inode的内容就是源路径名和对应原来的inode。所以访问软链接也可以访问到源文件。
软链接有点像windows操作系统的快捷方式。