日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

操作系统面试题目详解

發(fā)布時(shí)間:2023/12/19 windows 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 操作系统面试题目详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

        • 1.13 什么是協(xié)程?
        • 1.14 為什么協(xié)程比線程切換的開銷小?
        • 1.15 線程和進(jìn)程的區(qū)別?
        • 1.16 進(jìn)程切換為什么比線程更消耗資源?
        • 1.17 介紹一下進(jìn)程之間的通信。
        • 1.18 介紹一下信號(hào)量。
        • 1.19 說(shuō)說(shuō)僵尸進(jìn)程和孤兒進(jìn)程。
        • 1.20 請(qǐng)介紹進(jìn)程之間的通信方式。
        • 1.21 請(qǐng)介紹線程之間的通信方式。
        • 1.22 說(shuō)一說(shuō)進(jìn)程的狀態(tài)。
        • 1.23 CPU調(diào)度的最小單位是什么?線程需要CPU調(diào)度嗎?
        • 1.24 進(jìn)程之間共享內(nèi)存的通信方式有什么好處?
        • 1.25 如何殺死一個(gè)進(jìn)程?
        • 1.26 說(shuō)一說(shuō)kill的原理。
        • 1.27 介紹下你知道的鎖。
        • 1.28 什么情況下會(huì)產(chǎn)生死鎖?
        • 1.29 說(shuō)一說(shuō)你對(duì)自旋鎖的理解。
        • 1.30 說(shuō)一說(shuō)你對(duì)悲觀鎖的理解。
        • 1.31 說(shuō)一說(shuō)你對(duì)樂(lè)觀鎖的理解。
        • 1.32 CAS在什么地方用到過(guò)嗎?
        • 1.33 談?wù)処O多路復(fù)用。
        • 1.34 談?wù)刾oll和epoll的區(qū)別。
        • 1.35 談?wù)剆elect和epoll的區(qū)別。
        • 1.36 epoll有哪兩種模式?
        • 1.37 說(shuō)一下epoll的原理,它的查詢速度是O(1)的嗎?
        • 1.38 介紹域名解析成IP的全過(guò)程。
        • 1.39 如何在Linux上配置一個(gè)IP地址,如果給定端口號(hào)如何解析出域名?
        • 1.40 解釋一下IP地址、子網(wǎng)掩碼、網(wǎng)關(guān)。
        • 1.41 說(shuō)說(shuō)IP如何尋址?
        • 1.42 操作系統(tǒng)的地址有幾種,請(qǐng)具體說(shuō)明。
        • 1.43 Linux的靜態(tài)網(wǎng)絡(luò)怎么配置?
        • 1.44 DNS用了哪些協(xié)議?
        • 1.45 說(shuō)一說(shuō)你對(duì)Linux內(nèi)核的了解。
        • 1.46 說(shuō)一說(shuō)你對(duì)Linux內(nèi)核態(tài)與用戶態(tài)的了解。
        • 1.47 Linux負(fù)載是什么?
        • 1.48 Linux如何設(shè)置開機(jī)啟動(dòng)?
        • 1.49 談?wù)凩inux的內(nèi)存管理。
        • 1.50 談?wù)剝?nèi)存映射文件。
        • 1.51 談?wù)勌摂M內(nèi)存模型。
        • 1.52 什么是物理內(nèi)存和虛擬內(nèi)存,為什么要有虛擬內(nèi)存?
        • 1.53 內(nèi)存和緩存有什么區(qū)別?
        • 1.54 請(qǐng)你說(shuō)說(shuō)緩存溢出。
        • 1.55 深拷貝和淺拷貝的區(qū)別是什么,它們各自的使用場(chǎng)景是什么?
        • 1.56 說(shuō)說(shuō)IO模型。
        • 1.57 Linux中的軟鏈接和硬鏈接有什么區(qū)別?
        • 1.58 說(shuō)說(shuō)缺頁(yè)中斷機(jī)制。
        • 1.59 軟中斷和硬中斷有什么區(qū)別?
        • 1.60 介紹一下你對(duì)CopyOnWrite的了解。
        • 1.61 Linux替換文本該如何操作呢?
        • 1.61 Linux替換文本該如何操作呢?

1.13 什么是協(xié)程?

參考回答

協(xié)程:協(xié)程是微線程,在子程序內(nèi)部執(zhí)行,可在子程序內(nèi)部中斷,轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r(shí)候再返回來(lái)接著執(zhí)行。

答案解析

  • 線程與協(xié)程的區(qū)別:

    (1)協(xié)程執(zhí)行效率極高。協(xié)程直接操作棧基本沒(méi)有內(nèi)核切換的開銷,所以上下文的切換非常快,切換開銷比線程更小。

    (2)協(xié)程不需要多線程的鎖機(jī)制,因?yàn)槎鄠€(gè)協(xié)程從屬于一個(gè)線程,不存在同時(shí)寫變量沖突,效率比線程高。

    (3)一個(gè)線程可以有多個(gè)協(xié)程。

  • 協(xié)程的優(yōu)勢(shì):

    (1)協(xié)程調(diào)用跟切換比線程效率高:協(xié)程執(zhí)行效率極高。協(xié)程不需要多線程的鎖機(jī)制,可以不加鎖的訪問(wèn)全局變量,所以上下文的切換非常快。

    (2)協(xié)程占用內(nèi)存少:執(zhí)行協(xié)程只需要極少的棧內(nèi)存(大概是4~5KB),而默認(rèn)情況下,線程棧的大小為1MB。

    (3)切換開銷更少:協(xié)程直接操作?;緵](méi)有內(nèi)核切換的開銷,所以切換開銷比線程少。

  • 1.14 為什么協(xié)程比線程切換的開銷小?

    參考回答

    (1)協(xié)程執(zhí)行效率極高。協(xié)程直接操作?;緵](méi)有內(nèi)核切換的開銷,所以上下文的切換非???/strong>,切換開銷比線程更小。

    (2)協(xié)程不需要多線程的鎖機(jī)制,因?yàn)槎鄠€(gè)協(xié)程從屬于一個(gè)線程,不存在同時(shí)寫變量沖突,效率比線程高。避免了加鎖解鎖的開銷。

    1.15 線程和進(jìn)程的區(qū)別?

    參考回答

    (1)一個(gè)線程從屬于一個(gè)進(jìn)程;一個(gè)進(jìn)程可以包含多個(gè)線程。

    (2)一個(gè)線程掛掉,對(duì)應(yīng)的進(jìn)程掛掉;一個(gè)進(jìn)程掛掉,不會(huì)影響其他進(jìn)程。

    (3)進(jìn)程是系統(tǒng)資源調(diào)度的最小單位;線程CPU調(diào)度的最小單位。

    (4)進(jìn)程系統(tǒng)開銷顯著大于線程開銷;線程需要的系統(tǒng)資源更少。

    (5)進(jìn)程在執(zhí)行時(shí)擁有獨(dú)立的內(nèi)存單元,多個(gè)線程共享進(jìn)程的內(nèi)存,如代碼段、數(shù)據(jù)段、擴(kuò)展段;但每個(gè)線程擁有自己的棧段和寄存器組。

    (6)進(jìn)程切換時(shí)需要刷新TLB并獲取新的地址空間,然后切換硬件上下文和內(nèi)核棧,線程切換時(shí)只需要切換硬件上下文和內(nèi)核棧。

    (7)通信方式不一樣。

    (8)進(jìn)程適應(yīng)于多核、多機(jī)分布;線程適用于多核

    1.16 進(jìn)程切換為什么比線程更消耗資源?

    參考回答

    進(jìn)程切換時(shí)需要刷新TLB并獲取新的地址空間,然后切換硬件上下文和內(nèi)核棧;線程切換時(shí)只需要切換硬件上下文和內(nèi)核棧。

    答案解析

    進(jìn)程是程序的動(dòng)態(tài)表現(xiàn)。 一個(gè)程序進(jìn)行起來(lái)后,會(huì)使用很多資源,比如使用寄存器,內(nèi)存,文件等。每當(dāng)切換進(jìn)程時(shí),必須要考慮保存當(dāng)前進(jìn)程的狀態(tài)。狀態(tài)包括存放在內(nèi)存中的程序的代碼和數(shù)據(jù),它的棧、通用目的寄存器的內(nèi)容、程序計(jì)數(shù)器、環(huán)境變量以及打開的文件描述符的集合,這個(gè)狀態(tài)叫做上下文(Context)。可見,想要切換進(jìn)程,保存的狀態(tài)還不少。不僅如此,由于虛擬內(nèi)存機(jī)制,進(jìn)程切換時(shí)需要刷新TLB并獲取新的地址空間。

    線程存在于進(jìn)程中,一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線程。線程是運(yùn)行在進(jìn)程上下文中的邏輯流,這個(gè)線程可以獨(dú)立完成一項(xiàng)任務(wù)。同樣線程有自己的上下文,包括唯一的整數(shù)線程ID, 棧、棧指針、程序計(jì)數(shù)器、通用目的寄存器和條件碼。可以理解為線程上下文是進(jìn)程上下文的子集。

    由于保存線程的上下文明顯比進(jìn)程的上下文小,因此系統(tǒng)切換線程時(shí),必然開銷更小。

    1.17 介紹一下進(jìn)程之間的通信。

    參考回答

    為了提高計(jì)算機(jī)系統(tǒng)的效率.增強(qiáng)計(jì)算機(jī)系統(tǒng)內(nèi)各種硬件的并行操作能力.操作系統(tǒng)要求程序結(jié)構(gòu)必須適應(yīng)并發(fā)處理的需要.為此引入了進(jìn)程的概念。而進(jìn)程并行時(shí),需要考慮進(jìn)程間的通信,進(jìn)程間通信主要有以下幾種方式:匿名管道、命名管道、信號(hào)、消息隊(duì)列、共享內(nèi)存、信號(hào)量、Socket。

  • 匿名管道:管道是一種半雙工的通信方式,數(shù)據(jù)只能單向流動(dòng),而且只能在具有親緣關(guān)系的進(jìn)程間使用。進(jìn)程的親緣關(guān)系通常是指父子進(jìn)程關(guān)系。
  • #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h>int pipe_default[2];int main() {pid_t pid;char buffer[32];memset(buffer, 0, 32);if(pipe(pipe_default) < 0){printf("Failed to create pipe!\n");return 0;}if(0 == (pid = fork())) {close(pipe_default[1]); //關(guān)閉寫端sleep(2);if(read(pipe_default[0], buffer, 32) > 0){printf("[Client] Receive data from server: %s \n", buffer);}close(pipe_default[0]);}else{close(pipe_default[0]); //關(guān)閉讀端char msg[32]="== hello world ==";if(-1 != write(pipe_default[1], msg, strlen(msg))){printf("[Server] Send data to client: %s \n",msg);}close(pipe_default[1]);waitpid(pid, NULL, 0);}return 1; }
  • 有名管道

    匿名管道,由于沒(méi)有名字,只能用于親緣關(guān)系的進(jìn)程間通信。為了克服這個(gè)缺點(diǎn),提出了有名管道(FIFO)。

    有名管道不同于匿名管道之處在于它提供了一個(gè)路徑名與之關(guān)聯(lián),以有名管道的文件形式存在于文件系統(tǒng)中,這樣,即使與有名管道的創(chuàng)建進(jìn)程不存在親緣關(guān)系的進(jìn)程,只要可以訪問(wèn)該路徑,就能夠彼此通過(guò)有名管道相互通信,因此,通過(guò)有名管道不相關(guān)的進(jìn)程也能交換數(shù)據(jù)。值的注意的是,有名管道嚴(yán)格遵循先進(jìn)先出(first in first out) ,對(duì)匿名管道及有名管道的讀總是從開始處返回?cái)?shù)據(jù),對(duì)它們的寫則把數(shù)據(jù)添加到末尾。它們不支持諸如lseek()等文件定位操作。有名管道的名字存在于文件系統(tǒng)中,內(nèi)容存放在內(nèi)存中。

  • 信號(hào)

    • 信號(hào)是Linux系統(tǒng)中用于進(jìn)程間互相通信或者操作的一種機(jī)制,信號(hào)可以在任何時(shí)候發(fā)給某一進(jìn)程,而無(wú)需知道該進(jìn)程的狀態(tài)。
    • 如果該進(jìn)程當(dāng)前并未處于執(zhí)行狀態(tài),則該信號(hào)就有內(nèi)核保存起來(lái),知道該進(jìn)程回復(fù)執(zhí)行并傳遞給它為止。
    • 如果一個(gè)信號(hào)被進(jìn)程設(shè)置為阻塞,則該信號(hào)的傳遞被延遲,直到其阻塞被取消是才被傳遞給進(jìn)程。

    以下列出幾個(gè)常用的信號(hào):

    信號(hào)描述
    SIGHUP當(dāng)用戶退出終端時(shí),由該終端開啟的所有進(jìn)程都退接收到這個(gè)信號(hào),默認(rèn)動(dòng)作為終止進(jìn)程。
    SIGINT程序終止(interrupt)信號(hào), 在用戶鍵入INTR字符(通常是Ctrl+C)時(shí)發(fā)出,用于通知前臺(tái)進(jìn)程組終止進(jìn)程。
    SIGQUIT和SIGINT類似, 但由QUIT字符(通常是Ctrl+\)來(lái)控制. 進(jìn)程在因收到SIGQUIT退出時(shí)會(huì)產(chǎn)生core文件, 在這個(gè)意義上類似于一個(gè)程序錯(cuò)誤信號(hào)。
    SIGKILL用來(lái)立即結(jié)束程序的運(yùn)行. 本信號(hào)不能被阻塞、處理和忽略。
    SIGTERM程序結(jié)束(terminate)信號(hào), 與SIGKILL不同的是該信號(hào)可以被阻塞和處理。通常用來(lái)要求程序自己正常退出。
    SIGSTOP停止(stopped)進(jìn)程的執(zhí)行. 注意它和terminate以及interrupt的區(qū)別:該進(jìn)程還未結(jié)束, 只是暫停執(zhí)行. 本信號(hào)不能被阻塞, 處理或忽略.

    代碼示例:

    下面的代碼收到程序退出信號(hào)后會(huì)執(zhí)行用戶定義的信號(hào)處理函數(shù)來(lái)替代系統(tǒng)默認(rèn)的處理程序。

    #include<stdlib.h> #include<stdio.h> #include<signal.h> #include<sys/types.h> #include<unistd.h>void sig_handle(int sig) {printf("received signal: %d, quit.\n", sig);exit(0); }int main () {signal(SIGINT, sig_handle);signal(SIGKILL, sig_handle);signal(SIGSEGV, sig_handle);signal(SIGTERM, sig_handle);int i = 0;while (1) {printf("%d\n", ++i);sleep(2);}printf("main quit.");return 0; }

    運(yùn)行結(jié)果:

    1 2 received signal: 15, quit.
  • 消息隊(duì)列
    • 消息隊(duì)列是存放在內(nèi)核中的消息鏈表,每個(gè)消息隊(duì)列由消息隊(duì)列標(biāo)識(shí)符表示。
    • 與管道(無(wú)名管道:只存在于內(nèi)存中的文件;命名管道:存在于實(shí)際的磁盤介質(zhì)或者文件系統(tǒng))不同的是消息隊(duì)列存放在內(nèi)核中,只有在內(nèi)核重啟(即,操作系統(tǒng)重啟)或者顯示地刪除一個(gè)消息隊(duì)列時(shí),該消息隊(duì)列才會(huì)被真正的刪除。
    • 另外與管道不同的是,消息隊(duì)列在某個(gè)進(jìn)程往一個(gè)隊(duì)列寫入消息之前,并不需要另外某個(gè)進(jìn)程在該隊(duì)列上等待消息的到達(dá)

    消息隊(duì)列特點(diǎn)總結(jié):

    (1)消息隊(duì)列是消息的鏈表,具有特定的格式,存放在內(nèi)存中并由消息隊(duì)列標(biāo)識(shí)符標(biāo)識(shí).

    (2)消息隊(duì)列允許一個(gè)或多個(gè)進(jìn)程向它寫入與讀取消息.

    (3)管道和消息隊(duì)列的通信數(shù)據(jù)都是先進(jìn)先出的原則。

    (4)消息隊(duì)列可以實(shí)現(xiàn)消息的隨機(jī)查詢,消息不一定要以先進(jìn)先出的次序讀取,也可以按消息的類型讀取.比FIFO更有優(yōu)勢(shì)。

    (5)消息隊(duì)列克服了信號(hào)承載信息量少,管道只能承載無(wú)格式字 節(jié)流以及緩沖區(qū)大小受限等缺。

    (6)目前主要有兩種類型的消息隊(duì)列:POSIX消息隊(duì)列以及System V消息隊(duì)列,系統(tǒng)V消息隊(duì)列目前被大量使用。系統(tǒng)V消息隊(duì)列是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者人工刪除時(shí),該消息隊(duì)列才會(huì)被刪除。

  • 共享內(nèi)存

    進(jìn)程間本身的內(nèi)存是相互隔離的,而共享內(nèi)存機(jī)制相當(dāng)于給兩個(gè)進(jìn)程開辟了一塊二者均可訪問(wèn)的內(nèi)存空間,這時(shí),兩個(gè)進(jìn)程便可以共享一些數(shù)據(jù)了。但是,多進(jìn)程同時(shí)占用資源會(huì)帶來(lái)一些意料之外的情況,這時(shí),我們往往會(huì)采用上述的信號(hào)量來(lái)控制多個(gè)進(jìn)程對(duì)共享內(nèi)存空間的訪問(wèn)。

    #include <iostream> #include <stdlib.h> #include <string.h> #include <sys/shm.h> #include <sys/ipc.h> #include <unistd.h>using namespace std; int main() {char *shmaddr;char *shmaddread;char str[]="Hello, I am a processing. \n";int shmid;key_t key = ftok(".",1);pid_t pid1 = fork();if(pid1 == -1){cout << "Fork error. " << endl;exit(1);}else if(pid1 == 0){//子進(jìn)程shmid = shmget(key,1024,IPC_CREAT | 0600);shmaddr = (char*)shmat(shmid, NULL, 0);strcpy(shmaddr, str);cout << "[Writer] write: " << shmaddr << endl;shmdt(shmaddr);}else{//父進(jìn)程pid_t pid2 = fork();if(pid2 == -1){cout << "Fork error. " << endl;exit(1);}else if(pid2 == 0){//子進(jìn)程sleep(2);shmid = shmget(key,1024,IPC_CREAT | 0600);shmaddread = (char*)shmat(shmid, NULL, 0); cout << "[Reader] read: " << shmaddread << endl;shmdt(shmaddread);}}sleep(3);return 0; }
  • 信號(hào)量

    信號(hào)量主要用來(lái)解決進(jìn)程和線程間并發(fā)執(zhí)行時(shí)的同步問(wèn)題,進(jìn)程同步是并發(fā)進(jìn)程為了完成共同任務(wù)采用某個(gè)條件來(lái)協(xié)調(diào)他們的活動(dòng),這是進(jìn)程之間發(fā)生的一種直接制約關(guān)系。

    對(duì)信號(hào)量的操作分為P操作和V操作,P操作是將信號(hào)量的值減一,V操作是將信號(hào)量的值加一。當(dāng)信號(hào)量的值小于等于0之后,再進(jìn)行P操作時(shí),當(dāng)前進(jìn)程或線程會(huì)被阻塞,直到另一個(gè)進(jìn)程或線程執(zhí)行了V操作將信號(hào)量的值增加到大于0之時(shí)。鎖也是用的這種原理實(shí)現(xiàn)的。

    信號(hào)量我們需要定義信號(hào)量的數(shù)量,設(shè)定初始值,以及決定何時(shí)進(jìn)行PV操作。

    #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/sem.h>#define KEY (key_t)15030110070#define N 20static void p(int semid ,int semNum); static void v(int semid ,int semNum);union semun { int val; struct semid_ds *buf; ushort *array; }; int main(int argc ,char* argv[]){int i;int semid;semid = semget(KEY,3,IPC_CREAT|0660); union semun arg[3]; arg[0].val = 1; //mutex [0] 對(duì)緩沖區(qū)進(jìn)行操作的互斥信號(hào)量arg[1].val = N; //empty [1] 緩沖區(qū)空位個(gè)數(shù)narg[2].val = 0; //full [2] 產(chǎn)品個(gè)數(shù)for(i=0;i<3;i++) semctl(semid,i,SETVAL,arg[i]); pid_t p1,p2;if((p1=fork()) == 0){//子進(jìn)程1,消費(fèi)者while(1){printf("消費(fèi)者 1 等待中...\n");sleep(2);int product = rand() % 2 + 1;for(int i = 0; i < product; i++){p(semid ,2); //消費(fèi)p(semid ,0); //加鎖printf(" [消費(fèi)者 1] 消費(fèi)產(chǎn)品 1. 剩余: %d\n", semctl(semid, 2, GETVAL, NULL));v(semid ,0); //開鎖v(semid ,1); //釋放空位}sleep(2);}}else{if((p2=fork()) == 0){//子進(jìn)程2,消費(fèi)者while(1){printf("消費(fèi)者 2 等待中...\n");sleep(2);int product = rand() % 2 + 1;for(int i = 0; i < product; i++){p(semid ,2); //消費(fèi)p(semid ,0); //加鎖printf(" [消費(fèi)者 2] 消費(fèi)產(chǎn)品 1. 剩余: %d\n", semctl(semid, 2, GETVAL, NULL));v(semid ,0); //開鎖v(semid ,1); //釋放空位}sleep(2);}}else{//父進(jìn)程,生產(chǎn)者while(1){printf("生產(chǎn)者開始生產(chǎn)...\n");int product = rand() % 5 + 1;for(int i = 0; i < product; i++){p(semid ,1); //占用空位p(semid ,0); //加鎖printf(" [生產(chǎn)者] 生產(chǎn)產(chǎn)品 1. 剩余: %d\n", semctl(semid, 2, GETVAL, NULL) + 1); v(semid ,0); //開鎖v(semid, 2); //生產(chǎn)}sleep(2);}}}return 0;}/* p操作 */ void p(int semid ,int semNum){ struct sembuf sb; sb.sem_num = semNum; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; semop(semid, &sb, 1); } /* v操作 */ void v(int semid ,int semNum){ struct sembuf sb; sb.sem_num = semNum; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; semop(semid, &sb, 1); }
  • socket

    套接字可以看做是:不同主機(jī)之間的進(jìn)程進(jìn)行雙向通信的端點(diǎn)。(套接字 = IP地址 + 端口號(hào))

  • 1.18 介紹一下信號(hào)量。

    參考回答

  • 在多進(jìn)程環(huán)境下,為了防止多個(gè)進(jìn)程同時(shí)訪問(wèn)一個(gè)公共資源而出現(xiàn)問(wèn)題,需要一種方法來(lái)協(xié)調(diào)各個(gè)進(jìn)程,保證它們能夠合理地使用公共資源。信號(hào)量就是這樣一種機(jī)制。

    信號(hào)量的數(shù)據(jù)類型為結(jié)構(gòu)sem_t,它本質(zhì)上是一個(gè)長(zhǎng)整型的數(shù)。函數(shù)sem_init()用來(lái)初始化一個(gè)信號(hào)量。它的原型為:

    extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));

    sem為指向信號(hào)量結(jié)構(gòu)的一個(gè)指針;pshared不為0時(shí)此信號(hào)量在進(jìn)程間共享,否則只能為當(dāng)前進(jìn)程的所有線程共享;value給出了信號(hào)量的初始值。

    (1)函數(shù)sem_post( sem_t *sem )用來(lái)增加信號(hào)量的值。當(dāng)有線程阻塞在這個(gè)信號(hào)量上時(shí),調(diào)用這個(gè)函數(shù)會(huì)使其中的一個(gè)線程不在阻塞,選擇機(jī)制同樣是由線程的調(diào)度策略決定的。

    (2)函數(shù)sem_wait( sem_t *sem )被用來(lái)阻塞當(dāng)前線程直到信號(hào)量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經(jīng)使用后減少。函數(shù)sem_trywait ( sem_t *sem )是函數(shù)sem_wait()的非阻塞版本,它直接將信號(hào)量sem的值減一。

    (3)函數(shù)sem_timedwait(sem_t *sem, const struct timespec *abs_timeout) 與 sem_wait() 類似,只不過(guò) abs_timeout 指定一個(gè)阻塞的時(shí)間上限,如果調(diào)用因不能立即執(zhí)行遞減而要阻塞。

    (4)函數(shù)sem_destroy(sem_t *sem)用來(lái)釋放信號(hào)量sem。

  • 使用示例代碼如下

  • //g++ semtest.cpp -o test -lpthread #include <stdio.h> #include <semaphore.h> #include <pthread.h> #include <unistd.h> #include <sys/time.h> sem_t sem;/*function:獲取當(dāng)前時(shí)間,精確到毫秒* */ int64_t getTimeMsec() {struct timeval tv;gettimeofday(&tv, NULL);return tv.tv_sec * 1000 + tv.tv_usec / 1000; }void* func_sem_wait(void* arg) {printf("set wait\n");sem_wait(&sem);printf("sem wait success\n");int *running = (int*)arg;printf("func_sem_wait running\n");printf("%d\n", *running); }void* func_sem_timedwait(void* arg) {timespec timewait;timewait.tv_sec = getTimeMsec() / 1000 + 2;timewait.tv_nsec = 0;printf("sem_timedwait\n");int ret = sem_timedwait(&sem, &timewait);printf("sem_timedwait,ret=%d\n", ret);printf("func_sem_timedwait running\n"); }void* func_sem_post(void* arg) {printf("func_sem_post running\n");printf("sem post\n");int *a = (int*)arg;*a = 6;sem_post(&sem);sem_post(&sem); }int main() {sem_init(&sem, 0, 0);pthread_t thread[3];int a = 5;pthread_create(&(thread[0]), NULL, func_sem_wait, &a);printf("thread func_sem_wait\n");pthread_create(&(thread[2]), NULL, func_sem_timedwait, &a);printf("thread func_sem_timedwait\n");sleep(4);pthread_create(&(thread[1]), NULL, func_sem_post, &a);printf("thread func_sem_post\n");pthread_join(thread[0], NULL);pthread_join(thread[1], NULL);pthread_join(thread[2], NULL);sem_destroy(&sem); }

    1.19 說(shuō)說(shuō)僵尸進(jìn)程和孤兒進(jìn)程。

    參考回答

  • 我們知道在unix/linux中,正常情況下,子進(jìn)程是通過(guò)父進(jìn)程創(chuàng)建的,子進(jìn)程在創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個(gè)異步過(guò)程,即父進(jìn)程永遠(yuǎn)無(wú)法預(yù)測(cè)子進(jìn)程 到底什么時(shí)候結(jié)束。 當(dāng)一個(gè) 進(jìn)程完成它的工作終止之后,它的父進(jìn)程需要調(diào)用wait()或者waitpid()系統(tǒng)調(diào)用取得子進(jìn)程的終止?fàn)顟B(tài)。
  • 孤兒進(jìn)程:一個(gè)父進(jìn)程退出,而它的一個(gè)或多個(gè)子進(jìn)程還在運(yùn)行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號(hào)為1)所收養(yǎng),并由init進(jìn)程對(duì)它們完成狀態(tài)收集工作。
  • 僵尸進(jìn)程:一個(gè)進(jìn)程使用fork創(chuàng)建子進(jìn)程,如果子進(jìn)程退出,而父進(jìn)程并沒(méi)有調(diào)用wait或waitpid獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的進(jìn)程描述符仍然保存在系統(tǒng)中。這種進(jìn)程稱之為僵尸進(jìn)程。
  • 1.20 請(qǐng)介紹進(jìn)程之間的通信方式。

    參考回答

    進(jìn)程間通信主要有以下幾種方式

  • 管道pipe:管道是一種半雙工的通信方式,數(shù)據(jù)只能單向流動(dòng),而且只能在具有親緣關(guān)系的進(jìn)程間使用。進(jìn)程的親緣關(guān)系通常是指父子進(jìn)程關(guān)系。

    #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h>int pipe_default[2];int main() { pid_t pid; char buffer[32];memset(buffer, 0, 32); if(pipe(pipe_default) < 0) {printf("Failed to create pipe!\n");return 0; }if(0 == (pid = fork())) {close(pipe_default[1]); //關(guān)閉寫端sleep(2);if(read(pipe_default[0], buffer, 32) > 0){printf("[Client] Receive data from server: %s \n", buffer);}close(pipe_default[0]);} else {close(pipe_default[0]); //關(guān)閉讀端char msg[32]="== hello world ==";if(-1 != write(pipe_default[1], msg, strlen(msg))){printf("[Server] Send data to client: %s \n",msg);}close(pipe_default[1]);waitpid(pid, NULL, 0); } return 1; }
  • 命名管道FIFO:有名管道也是半雙工的通信方式,但是它允許無(wú)親緣關(guān)系進(jìn)程間的通信。

    寫管道:

    #include <stdio.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/stat.h>int main() {int nFd = 0;int nWrLen = 0, nReadLen = 0;;char szBuff[BUFSIZ] = {0};/* 打開當(dāng)前目錄下的管道文件 */nFd = open("pipe", O_RDWR);if (-1 == nFd){perror("Open fifo failed\n");return 1;}while (1){/* 從終端讀取數(shù)據(jù) */memset(szBuff,0,BUFSIZ);nReadLen = read(STDIN_FILENO,szBuff,BUFSIZ);if(nReadLen > 0){/* 往管道寫入數(shù)據(jù) */nWrLen = write(nFd, szBuff, strlen(szBuff)+1);if (nWrLen > 0){printf("write data successful: %s \n", szBuff);}else{perror("write failed:");}}} }
  • 讀管道:

    #include <stdio.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/stat.h>int main() {int nFd = 0;int nReadLen = 0;;char szBuff[BUFSIZ] = {0};/* 打開當(dāng)前目錄下的管道文件 */nFd = open("pipe", O_RDWR);if (-1 == nFd){perror("Open fifo failed\n");return 1;}while (1){/* 從管道讀取數(shù)據(jù) */memset(szBuff,0,BUFSIZ);nReadLen = read(nFd,szBuff,BUFSIZ);if(nReadLen > 0){printf("read pipe data: %s\n", szBuff);} } }
  • 消息隊(duì)列MessageQueue:消息隊(duì)列是由消息的鏈表,存放在內(nèi)核中并由消息隊(duì)列標(biāo)識(shí)符標(biāo)識(shí)。消息隊(duì)列克服了信號(hào)傳遞信息少、管道只能承載無(wú)格式字節(jié)流以及緩沖區(qū)大小受限等缺點(diǎn)。

    示例:使用消息隊(duì)列進(jìn)行進(jìn)程間通信

    接收信息的程序源文件為msgreceive.c的源代碼為:

    #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/msg.h>struct msg_st {long int msg_type;char text[BUFSIZ]; };int main() {int running = 1;int msgid = -1;struct msg_st data;long int msgtype = 0; //注意1//建立消息隊(duì)列msgid = msgget((key_t)1234, 0666 | IPC_CREAT);if(msgid == -1){fprintf(stderr, "msgget failed with error: %d\n", errno);exit(EXIT_FAILURE);}//從隊(duì)列中獲取消息,直到遇到end消息為止while(running){if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1){fprintf(stderr, "msgrcv failed with errno: %d\n", errno);exit(EXIT_FAILURE);}printf("You wrote: %s\n",data.text);//遇到end結(jié)束if(strncmp(data.text, "end", 3) == 0)running = 0;}//刪除消息隊(duì)列if(msgctl(msgid, IPC_RMID, 0) == -1){fprintf(stderr, "msgctl(IPC_RMID) failed\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS); }

    發(fā)送信息的程序的源文件msgsend.c的源代碼為:

    #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/msg.h> #include <errno.h>#define MAX_TEXT 512 struct msg_st {long int msg_type;char text[MAX_TEXT]; };int main() {int running = 1;struct msg_st data;char buffer[BUFSIZ];int msgid = -1;//建立消息隊(duì)列msgid = msgget((key_t)1234, 0666 | IPC_CREAT);if(msgid == -1){fprintf(stderr, "msgget failed with error: %d\n", errno);exit(EXIT_FAILURE);}//向消息隊(duì)列中寫消息,直到寫入endwhile(running){//輸入數(shù)據(jù)printf("Enter some text: ");fgets(buffer, BUFSIZ, stdin);data.msg_type = 1; //注意2strcpy(data.text, buffer);//向隊(duì)列發(fā)送數(shù)據(jù)if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1){fprintf(stderr, "msgsnd failed\n");exit(EXIT_FAILURE);}//輸入end結(jié)束輸入if(strncmp(buffer, "end", 3) == 0)running = 0;sleep(1);}exit(EXIT_SUCCESS); }

    運(yùn)行結(jié)果如下:

    biao@ubuntu:~/test/msgRecvSend$ biao@ubuntu:~/test/msgRecvSend$ ls msgreceive.c msgsend.c recv send biao@ubuntu:~/test/msgRecvSend$ ./recv & [1] 8753 biao@ubuntu:~/test/msgRecvSend$ ./send Enter some text: helloworld You wrote: helloworldEnter some text: Caibiao Lee You wrote: Caibiao LeeEnter some text: end You wrote: end[1]+ Done ./recv biao@ubuntu:~/test/msgRecvSend$
  • 共享存儲(chǔ)SharedMemory:共享內(nèi)存就是映射一段能被其他進(jìn)程所訪問(wèn)的內(nèi)存,這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建,但多個(gè)進(jìn)程都可以訪問(wèn)。共享內(nèi)存是最快的 IPC 方式,它是針對(duì)其他進(jìn)程間通信方式運(yùn)行效率低而專門設(shè)計(jì)的。它往往與其他通信機(jī)制,如信號(hào)兩,配合使用,來(lái)實(shí)現(xiàn)進(jìn)程間的同步和通信。

    /* Linux 6.cpp */ #include <iostream> #include <stdlib.h> #include <string.h> #include <sys/shm.h> #include <sys/ipc.h> #include <unistd.h>using namespace std; int main() {char *shmaddr;char *shmaddread;char str[]="Hello, I am a processing. \n";int shmid;key_t key = ftok(".",1);pid_t pid1 = fork();if(pid1 == -1){cout << "Fork error. " << endl;exit(1);}else if(pid1 == 0){//子進(jìn)程shmid = shmget(key,1024,IPC_CREAT | 0600);shmaddr = (char*)shmat(shmid, NULL, 0);strcpy(shmaddr, str);cout << "[Writer] write: " << shmaddr << endl;shmdt(shmaddr);}else{//父進(jìn)程pid_t pid2 = fork();if(pid2 == -1){cout << "Fork error. " << endl;exit(1);}else if(pid2 == 0){//子進(jìn)程sleep(2);shmid = shmget(key,1024,IPC_CREAT | 0600);shmaddread = (char*)shmat(shmid, NULL, 0); cout << "[Reader] read: " << shmaddread << endl;shmdt(shmaddread);}}sleep(3);return 0; }
  • 信號(hào)量Semaphore:信號(hào)量是一個(gè)計(jì)數(shù)器,可以用來(lái)控制多個(gè)進(jìn)程對(duì)共享資源的訪問(wèn)。它常作為一種鎖機(jī)制,防止某進(jìn)程正在訪問(wèn)共享資源時(shí),其他進(jìn)程也訪問(wèn)該資源。因此,主要作為進(jìn)程間以及同一進(jìn)程內(nèi)不同線程之間的同步手段。

    使用示例代碼如下 //g++ semtest.cpp -o test -lpthread#include <stdio.h>#include <semaphore.h>#include <pthread.h>#include <unistd.h>#include <sys/time.h>sem_t sem;/*function:獲取當(dāng)前時(shí)間,精確到毫秒
    • */
      int64_t getTimeMsec()
      {
      struct timeval tv;
      gettimeofday(&tv, NULL);
      return tv.tv_sec * 1000 + tv.tv_usec / 1000;
      }

      void* func_sem_wait(void* arg)
      {
      printf(“set wait\n”);
      sem_wait(&sem);
      printf(“sem wait success\n”);
      int running = (int)arg;
      printf(“func_sem_wait running\n”);
      printf("%d\n", *running);
      }

      void* func_sem_timedwait(void* arg)
      {
      timespec timewait;
      timewait.tv_sec = getTimeMsec() / 1000 + 2;
      timewait.tv_nsec = 0;
      printf(“sem_timedwait\n”);
      int ret = sem_timedwait(&sem, &timewait);
      printf(“sem_timedwait,ret=%d\n”, ret);
      printf(“func_sem_timedwait running\n”);
      }

      void* func_sem_post(void* arg)
      {
      printf(“func_sem_post running\n”);
      printf(“sem post\n”);
      int a = (int)arg;
      *a = 6;
      sem_post(&sem);
      sem_post(&sem);
      }

      int main()
      {
      sem_init(&sem, 0, 0);
      pthread_t thread[3];
      int a = 5;

      pthread_create(&(thread[0]), NULL, func_sem_wait, &a);printf("thread func_sem_wait\n");pthread_create(&(thread[2]), NULL, func_sem_timedwait, &a);printf("thread func_sem_timedwait\n");sleep(4);pthread_create(&(thread[1]), NULL, func_sem_post, &a);printf("thread func_sem_post\n");pthread_join(thread[0], NULL);pthread_join(thread[1], NULL);pthread_join(thread[2], NULL);sem_destroy(&sem);

      }

  • 套接字Socket:套解口也是一種進(jìn)程間通信機(jī)制,與其他通信機(jī)制不同的是,它可用于不同機(jī)器間的進(jìn)程通信。

  • 信號(hào) ( sinal ) : 信號(hào)是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,可以看作是異步通知,通知接收信號(hào)的進(jìn)程有哪些事情發(fā)生了。也可以簡(jiǎn)單理解為信號(hào)是某種形式上的軟中斷。

    一般情況下,信號(hào)的來(lái)源可分為以下三種:

    • 硬件方式:除數(shù)為零、無(wú)效的存儲(chǔ)訪問(wèn)等硬件異常產(chǎn)生信號(hào)。這些事件通常由硬件(如:CPU)檢測(cè)到,并將其通知給Linux操作系統(tǒng)內(nèi)核,然后內(nèi)核生成相應(yīng)的信號(hào),并把信號(hào)發(fā)送給該事件發(fā)生時(shí)正在進(jìn)行的程序。

    • 軟件方式:用戶在終端下調(diào)用kill命令向進(jìn)程發(fā)送任務(wù)信號(hào)、進(jìn)程調(diào)用kill或sigqueue函數(shù)發(fā)送信號(hào)、當(dāng)檢測(cè)到某種軟件條件已經(jīng)具備時(shí)發(fā)出信號(hào),如由alarm或settimer設(shè)置的定時(shí)器超時(shí)時(shí)將生成SIGALRM信號(hào)等多種情景均可產(chǎn)生信號(hào)。

    • 鍵盤輸入:當(dāng)用戶在終端上按下某鍵時(shí),將產(chǎn)生信號(hào)。如按下組合鍵Ctrl+C將產(chǎn)生一個(gè)SIGINT信號(hào),Ctrl+\產(chǎn)生一個(gè)SIGQUIT信號(hào)等。

      以下列出幾個(gè)常用的信號(hào):

      信號(hào)描述
      SIGHUP當(dāng)用戶退出終端時(shí),由該終端開啟的所有進(jìn)程都退接收到這個(gè)信號(hào),默認(rèn)動(dòng)作為終止進(jìn)程。
      SIGINT程序終止(interrupt)信號(hào), 在用戶鍵入INTR字符(通常是Ctrl+C)時(shí)發(fā)出,用于通知前臺(tái)進(jìn)程組終止進(jìn)程。
      SIGQUIT和SIGINT類似, 但由QUIT字符(通常是Ctrl+\)來(lái)控制. 進(jìn)程在因收到SIGQUIT退出時(shí)會(huì)產(chǎn)生core文件, 在這個(gè)意義上類似于一個(gè)程序錯(cuò)誤信號(hào)。
      SIGKILL用來(lái)立即結(jié)束程序的運(yùn)行. 本信號(hào)不能被阻塞、處理和忽略。
      SIGTERM程序結(jié)束(terminate)信號(hào), 與SIGKILL不同的是該信號(hào)可以被阻塞和處理。通常用來(lái)要求程序自己正常退出。
      SIGSTOP停止(stopped)進(jìn)程的執(zhí)行. 注意它和terminate以及interrupt的區(qū)別:該進(jìn)程還未結(jié)束, 只是暫停執(zhí)行. 本信號(hào)不能被阻塞, 處理或忽略.

    代碼示例:

    下面的代碼收到程序退出信號(hào)后會(huì)執(zhí)行用戶定義的信號(hào)處理函數(shù)來(lái)替代系統(tǒng)默認(rèn)的處理程序。

    #include<stdlib.h> #include<stdio.h> #include<signal.h> #include<sys/types.h> #include<unistd.h>void sig_handle(int sig) {printf("received signal: %d, quit.\n", sig);exit(0); }int main () {signal(SIGINT, sig_handle);signal(SIGKILL, sig_handle);signal(SIGSEGV, sig_handle);signal(SIGTERM, sig_handle);int i = 0;while (1) {printf("%d\n", ++i);sleep(2);}printf("main quit.");return 0; } 復(fù)制代碼

    運(yùn)行結(jié)果:

    1 2 received signal: 15, quit.

    1.21 請(qǐng)介紹線程之間的通信方式。

    參考回答

  • 鎖機(jī)制:包括互斥鎖、條件變量、讀寫鎖互斥鎖提供了以排他方式防止數(shù)據(jù)結(jié)構(gòu)被并發(fā)修改的方法。讀寫鎖允許多個(gè)線程同時(shí)讀共享數(shù)據(jù),而對(duì)寫操作是互斥的。條件變量可以以原子的方式阻塞進(jìn)程,直到某個(gè)特定條件為真為止。對(duì)條件的測(cè)試是在互斥鎖的保護(hù)下進(jìn)行的。條件變量始終與互斥鎖一起使用。
  • 信號(hào)量機(jī)制(Semaphore):包括無(wú)名線程信號(hào)量和命名線程信號(hào)量
  • 信號(hào)機(jī)制(Signal):類似進(jìn)程間的信號(hào)處理線程間的通信目的主要是用于線程同步,所以線程沒(méi)有像進(jìn)程通信中的用于數(shù)據(jù)交換的通信機(jī)制。
  • 1.22 說(shuō)一說(shuō)進(jìn)程的狀態(tài)。

    參考回答

  • 進(jìn)程的3種基本狀態(tài):運(yùn)行、就緒和阻塞。
  • (1)就緒:當(dāng)一個(gè)進(jìn)程獲得了除處理機(jī)以外的一切所需資源,一旦得到處理機(jī)即可運(yùn)行,則稱此進(jìn)程處于就緒狀態(tài)。就緒進(jìn)程可以按多個(gè)優(yōu)先級(jí)來(lái)劃分隊(duì)列。例如,當(dāng)一個(gè)進(jìn)程由于時(shí)間片用完而進(jìn)入就緒狀態(tài)時(shí),排入低優(yōu)先級(jí)隊(duì)列;當(dāng)進(jìn)程由I/O操作完成而進(jìn)入就緒狀態(tài)時(shí),排入高優(yōu)先級(jí)隊(duì)列。

    (2)運(yùn)行:當(dāng)一個(gè)進(jìn)程在處理機(jī)上運(yùn)行時(shí),則稱該進(jìn)程處于運(yùn)行狀態(tài)。處于此狀態(tài)的進(jìn)程的數(shù)目小于等于處理器的數(shù)目,對(duì)于單處理機(jī)系統(tǒng),處于運(yùn)行狀態(tài)的進(jìn)程只有一個(gè)。在沒(méi)有其他進(jìn)程可以執(zhí)行時(shí)(如所有進(jìn)程都在阻塞狀態(tài)),通常會(huì)自動(dòng)執(zhí)行系統(tǒng)的空閑進(jìn)程。

    (3)阻塞:也稱為等待或睡眠狀態(tài),一個(gè)進(jìn)程正在等待某一事件發(fā)生(例如請(qǐng)求I/O而等待I/O完成等)而暫時(shí)停止運(yùn)行,這時(shí)即使把處理機(jī)分配給進(jìn)程也無(wú)法運(yùn)行,故稱該進(jìn)程處于阻塞狀態(tài)。

    其轉(zhuǎn)移圖如下:

  • 進(jìn)程的五種狀態(tài)

    創(chuàng)建狀態(tài):進(jìn)程在創(chuàng)建時(shí)需要申請(qǐng)一個(gè)空白PCB,向其中填寫控制和管理進(jìn)程的信息,完成資源分配。如果創(chuàng)建工作無(wú)法完成,比如資源無(wú)法滿足,就無(wú)法被調(diào)度運(yùn)行,把此時(shí)進(jìn)程所處狀態(tài)稱為創(chuàng)建狀態(tài)

    就緒狀態(tài):進(jìn)程已經(jīng)準(zhǔn)備好,已分配到所需資源,只要分配到CPU就能夠立即運(yùn)行

    執(zhí)行狀態(tài):進(jìn)程處于就緒狀態(tài)被調(diào)度后,進(jìn)程進(jìn)入執(zhí)行狀態(tài)

    阻塞狀態(tài):正在執(zhí)行的進(jìn)程由于某些事件(I/O請(qǐng)求,申請(qǐng)緩存區(qū)失敗)而暫時(shí)無(wú)法運(yùn)行,進(jìn)程受到阻塞。在滿足請(qǐng)求時(shí)進(jìn)入就緒狀態(tài)等待系統(tǒng)調(diào)用

    終止?fàn)顟B(tài):進(jìn)程結(jié)束,或出現(xiàn)錯(cuò)誤,或被系統(tǒng)終止,進(jìn)入終止?fàn)顟B(tài)。無(wú)法再執(zhí)行

  • 1.23 CPU調(diào)度的最小單位是什么?線程需要CPU調(diào)度嗎?

    參考回答

  • 進(jìn)程是CPU分配資源的最小單位,線程是CPU調(diào)度的最小單位。
  • 線程是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,需要通過(guò)CPU調(diào)度來(lái)切換上下文,達(dá)到并發(fā)的目的。
  • 1.24 進(jìn)程之間共享內(nèi)存的通信方式有什么好處?

    參考回答

    采用共享內(nèi)存通信的一個(gè)顯而易見的好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫內(nèi)存,而不需要任何數(shù)據(jù)的拷貝。對(duì)于像管道和消息隊(duì)列等通信方式,則需要在內(nèi)核和用戶空間進(jìn)行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則只拷貝兩次數(shù)據(jù):一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。

    實(shí)際上,進(jìn)程之間在共享內(nèi)存時(shí),并不總是讀寫少量數(shù)據(jù)后就解除映射,有新的通信時(shí),再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直到通信完畢為止,這樣,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并沒(méi)有寫回文件。共享內(nèi)存中的內(nèi)容往往是在解除映射時(shí)才寫回文件的。因此,采用共享內(nèi)存的通信方式效率是非常高的。

    1.25 如何殺死一個(gè)進(jìn)程?

    參考回答

  • 殺死父進(jìn)程并不會(huì)同時(shí)殺死子進(jìn)程:每個(gè)進(jìn)程都有一個(gè)父進(jìn)程??梢允褂?pstree 或 ps 工具來(lái)觀察這一點(diǎn)。

    # 啟動(dòng)兩個(gè)虛擬進(jìn)程 $ sleep 100 & $ sleep 101 &$ pstree -p init(1)-+|-bash(29051)-+-pstree(29251)|-sleep(28919)`-sleep(28964)$ ps j -APPID PID PGID SID TTY TPGID STAT UID TIME COMMAND0 1 1 1 ? -1 Ss 0 0:03 /sbin/init 29051 1470 1470 29051 pts/2 2386 SN 1000 0:00 sleep 100 29051 1538 1538 29051 pts/2 2386 SN 1000 0:00 sleep 101 29051 2386 2386 29051 pts/2 2386 R+ 1000 0:00 ps j -A1 29051 29051 29051 pts/2 2386 Ss 1000 0:00 -bash

    調(diào)用 ps 命令可以顯示 PID(進(jìn)程 ID) 和 PPID(父進(jìn)程 ID)。

    殺死父進(jìn)程后,子進(jìn)程將會(huì)成為孤兒進(jìn)程,而 init 進(jìn)程將重新成為它的父進(jìn)程。

  • 殺死進(jìn)程組或會(huì)話中的所有進(jìn)程

    $ kill -SIGTERM -- -19701

    這里用一個(gè)負(fù)數(shù) -19701 向進(jìn)程組發(fā)送信號(hào)。如果傳遞的是一個(gè)正數(shù),這個(gè)數(shù)將被視為進(jìn)程 ID 用于終止進(jìn)程。如果傳遞的是一個(gè)負(fù)數(shù),它被視為 PGID,用于終止整個(gè)進(jìn)程組。負(fù)數(shù)來(lái)自系統(tǒng)調(diào)用的直接定義。

    殺死會(huì)話中的所有進(jìn)程與之完全不同。即使是具有會(huì)話 ID 的系統(tǒng),例如 Linux,也沒(méi)有提供系統(tǒng)調(diào)用來(lái)終止會(huì)話中的所有進(jìn)程。需要遍歷 /proc 輸出的進(jìn)程樹,收集所有的 SID,然后一一終止進(jìn)程。

    Pgrep 實(shí)現(xiàn)了遍歷、收集并通過(guò)會(huì)話 ID 殺死進(jìn)程的算法。可以使用以下命令:

    pkill -s <SID>
  • 1.26 說(shuō)一說(shuō)kill的原理。

    參考回答

    • kill 命令的執(zhí)行原理是這樣的,kill 命令會(huì)向操作系統(tǒng)內(nèi)核發(fā)送一個(gè)信號(hào)(多是終止信號(hào))和目標(biāo)進(jìn)程的 PID,然后系統(tǒng)內(nèi)核根據(jù)收到的信號(hào)類型,對(duì)指定進(jìn)程進(jìn)行相應(yīng)的操作。kill 命令的基本格式如下:

      [root@localhost ~]# kill [信號(hào)] PID

    • kill 命令是按照 PID 來(lái)確定進(jìn)程的,所以 kill 命令只能識(shí)別 PID,而不能識(shí)別進(jìn)程名。

    • kill 命令只是“發(fā)送”一個(gè)信號(hào),因此,只有當(dāng)信號(hào)被程序成功“捕獲”,系統(tǒng)才會(huì)執(zhí)行 kill 命令指定的操作;反之,如果信號(hào)被“封鎖”或者“忽略”,則 kill 命令將會(huì)失效。

    1.27 介紹下你知道的鎖。

    參考回答

  • 悲觀鎖
  • 悲觀鎖并不是某一個(gè)鎖,是一個(gè)鎖類型,無(wú)論是否并發(fā)競(jìng)爭(zhēng)資源,都會(huì)鎖住資源,并等待資源釋放下一個(gè)線程才能獲取到鎖。 這明顯很悲觀,所以就叫悲觀鎖。這明顯可以歸納為一種策略,只要符合這種策略的鎖的具體實(shí)現(xiàn),都是悲觀鎖的范疇。

  • 樂(lè)觀鎖
  • 與悲觀鎖相對(duì)的,樂(lè)觀鎖也是一個(gè)鎖類型。當(dāng)線程開始競(jìng)爭(zhēng)資源時(shí),不是立馬給資源上鎖,而是進(jìn)行一些前后值比對(duì),以此來(lái)操作資源。例如常見的CAS操作,就是典型的樂(lè)觀鎖。示例如下

    int cas(long *addr, long old, long new) {/* 原子執(zhí)行 */if(*addr != old)return 0;*addr = new;return 1; }
  • 自旋鎖
  • 自旋鎖是一種基礎(chǔ)的同步原語(yǔ),用于保障對(duì)共享數(shù)據(jù)的互斥訪問(wèn)。與互斥鎖的相比,在獲取鎖失敗的時(shí)候不會(huì)使得線程阻塞而是一直自旋嘗試獲取鎖。當(dāng)線程等待自旋鎖的時(shí)候,CPU不能做其他事情,而是一直處于輪詢忙等的狀態(tài)。

    自旋鎖主要適用于被持有時(shí)間短,線程不希望在重新調(diào)度上花過(guò)多時(shí)間的情況。實(shí)際上許多其他類型的鎖在底層使用了自旋鎖實(shí)現(xiàn),例如多數(shù)互斥鎖在試圖獲取鎖的時(shí)候會(huì)先自旋一小段時(shí)間,然后才會(huì)休眠。如果在持鎖時(shí)間很長(zhǎng)的場(chǎng)景下使用自旋鎖,則會(huì)導(dǎo)致CPU在這個(gè)線程的時(shí)間片用盡之前一直消耗在無(wú)意義的忙等上,造成計(jì)算資源的浪費(fèi)。

    // 用戶空間用 atomic_flag 實(shí)現(xiàn)自旋互斥 #include <thread> #include <vector> #include <iostream> #include <atomic>std::atomic_flag lock = ATOMIC_FLAG_INIT;void f(int n) {for (int cnt = 0; cnt < 100; ++cnt) {while (lock.test_and_set(std::memory_order_acquire)) // 獲得鎖; // 自旋std::cout << "Output from thread " << n << '\n';lock.clear(std::memory_order_release); // 釋放鎖} }int main() {std::vector<std::thread> v;for (int n = 0; n < 10; ++n) {v.emplace_back(f, n);}for (auto& t : v) {t.join();} }
  • 公平鎖
  • 多個(gè)線程競(jìng)爭(zhēng)同一把鎖,如果依照先來(lái)先得的原則,那么就是一把公平鎖。

  • 非公平鎖
  • 多個(gè)線程競(jìng)爭(zhēng)鎖資源,搶占鎖的所有權(quán)。

  • 共享鎖
  • 多個(gè)線程可以共享這個(gè)鎖的擁有權(quán)。一般用于數(shù)據(jù)的讀操作,防止數(shù)據(jù)被寫修改。共享鎖的代碼示例如下:

    #include <shared_mutex>#include <mutex>#include <iostream>#include <thread>#include <chrono>std::shared_mutex test_lock;std::mutex cout_lock;int arr[3] = {11, 22, 33};void unique_lock_demo(int id){std::unique_lock lock{test_lock};for(int i =0; i < 3; i++){arr[i] = i + 100 * id;}for(int i = 0; i < 3; i++){std::unique_lock pl(cout_lock);std::cout << "In unique: " << id << ": " << arr[i] << std::endl;pl.unlock();std::this_thread::sleep_for(std::chrono::seconds(1));}}void shared_lock_demo(int id){std::shared_lock lock{test_lock};for(int i = 0; i < 3; i++){std::unique_lock pl(cout_lock);std::cout << "In shared " << id << ": " << arr[i] << std::endl;pl.unlock();std::this_thread::sleep_for(std::chrono::seconds(1));}}int main(){std::thread t3(unique_lock_demo,3);std::thread t4(unique_lock_demo,4);std::thread t1(shared_lock_demo,1);std::thread t2(shared_lock_demo,2);t1.join();t2.join();t3.join();t4.join();return 0;}

    輸出為:

    In unique: 3: 300In unique: 3: 301In unique: 3: 302In shared 1: 300In shared 2: 300In shared 1: 301In shared 2: 301In shared 1: 302In shared 2: 302In unique: 4: 400In unique: 4: 401In unique: 4: 402

    從這個(gè)輸出可以看出:

    • 如果一個(gè)線程已經(jīng)獲取了共享鎖,則其他任何線程都無(wú)法獲取互斥鎖,但是可以獲取共享鎖。
    • 從這個(gè)輸出可以看出,驗(yàn)證了如果一個(gè)線程已經(jīng)獲取了互斥鎖,則其他線程都無(wú)法獲取該鎖。
  • 死鎖
  • 死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。

    mutex; //代表一個(gè)全局互斥對(duì)象 void A() {mutex.lock();//這里操作共享數(shù)據(jù)B(); //這里調(diào)用B方法mutex.unlock();return; } void B() {mutex.lock();//這里操作共享數(shù)據(jù)mutex.unlock();return; }

    1.28 什么情況下會(huì)產(chǎn)生死鎖?

    參考回答

    如果在計(jì)算機(jī)系統(tǒng)中同時(shí)具備下面四個(gè)必要條件時(shí),那么會(huì)發(fā)生死鎖。換句話說(shuō),只要下面四個(gè)條件有一個(gè)不具備,系統(tǒng)就不會(huì)出現(xiàn)死鎖。

  • 互斥條件。即某個(gè)資源在一段時(shí)間內(nèi)只能由一個(gè)進(jìn)程占有,不能同時(shí)被兩個(gè)或兩個(gè)以上的進(jìn)程占有。這種獨(dú)占資源如CD-ROM驅(qū)動(dòng)器,打印機(jī)等等,必須在占有該資源的進(jìn)程主動(dòng)釋放它之后,其它進(jìn)程才能占有該資源。這是由資源本身的屬性所決定的。如獨(dú)木橋就是一種獨(dú)占資源,兩方的人不能同時(shí)過(guò)橋。

    #include <list> #include <mutex> #include <algorithm>std::list<int> some_list; // 1 std::mutex some_mutex; // 2void add_to_list(int new_value) {std::lock_guard<std::mutex> guard(some_mutex); // 3some_list.push_back(new_value); }bool list_contains(int value_to_find) {std::lock_guard<std::mutex> guard(some_mutex); // 4return std::find(some_list.begin(),some_list.end(),value_to_find) != some_list.end(); }

    代碼中有一個(gè)全局變量①,這個(gè)全局變量被一個(gè)全局的互斥量保護(hù)②。add_to_list()③和list_contains()④函數(shù)中使用std::lock_guard<std::mutex>,使得這兩個(gè)函數(shù)中對(duì)數(shù)據(jù)的訪問(wèn)是互斥的:list_contains()不可能看到正在被add_to_list()修改的列表。

  • 不剝奪條件。進(jìn)程所獲得的資源在未使用完畢之前,資源申請(qǐng)者不能強(qiáng)行地從資源占有者手中奪取資源,而只能由該資源的占有者進(jìn)程自行釋放。如過(guò)獨(dú)木橋的人不能強(qiáng)迫對(duì)方后退,也不能非法地將對(duì)方推下橋,必須是橋上的人自己過(guò)橋后空出橋面(即主動(dòng)釋放占有資源),對(duì)方的人才能過(guò)橋。

  • 請(qǐng)求和保持條件。進(jìn)程至少已經(jīng)占有一個(gè)資源,但又申請(qǐng)新的資源;由于該資源已被另外進(jìn)程占有,此時(shí)該進(jìn)程阻塞;但是,它在等待新資源之時(shí),仍繼續(xù)占用已占有的資源。還以過(guò)獨(dú)木橋?yàn)槔?#xff0c;甲乙兩人在橋上相遇。甲走過(guò)一段橋面(即占有了一些資源),還需要走其余的橋面(申請(qǐng)新的資源),但那部分橋面被乙占有(乙走過(guò)一段橋面)。甲過(guò)不去,前進(jìn)不能,又不后退;乙也處于同樣的狀況。

  • 循環(huán)等待條件。存在一個(gè)進(jìn)程等待序列{P1,P2,…,Pn},其中P1等待P2所占有的某一資源,P2等待P3所占有的某一源,…,而Pn等待P1所占有的的某一資源,形成一個(gè)進(jìn)程循環(huán)等待環(huán)。就像前面的過(guò)獨(dú)木橋問(wèn)題,甲等待乙占有的橋面,而乙又等待甲占有的橋面,從而彼此循環(huán)等待。

    std::mutex m; void f() { // .... std::lock_guard lock(m); // 1 子線程鎖住互斥量m // ... } int main() { std::thread t(f); std::lock_guard lock(m); // 2 主線程鎖住互斥量m // ... t.join(); // 3 等待子線程結(jié)束 return 0; }

    上述過(guò)程可能導(dǎo)致在2處上鎖,然后子線程在1處發(fā)生阻塞,最后主線程在3處一直等待子線程結(jié)束,無(wú)窮等待下去。

  • 上面提到的這四個(gè)條件在死鎖時(shí)會(huì)同時(shí)發(fā)生。也就是說(shuō),只要有一個(gè)必要條件不滿足,則死鎖就可以排除。

    1.29 說(shuō)一說(shuō)你對(duì)自旋鎖的理解。

    參考回答

    旋鎖的定義:當(dāng)一個(gè)線程嘗試去獲取某一把鎖的時(shí)候,如果這個(gè)鎖此時(shí)已經(jīng)被別人獲取(占用),那么此線程就無(wú)法獲取到這把鎖,該線程將會(huì)等待,間隔一段時(shí)間后會(huì)再次嘗試獲取。這種采用循環(huán)加鎖 -> 等待的機(jī)制被稱為自旋鎖(spinlock)。

    自旋鎖有以下特點(diǎn)

    • 用于臨界區(qū)互斥
    • 在任何時(shí)刻最多只能有一個(gè)執(zhí)行單元獲得鎖
    • 要求持有鎖的處理器所占用的時(shí)間盡可能短
    • 等待鎖的線程進(jìn)入忙循環(huán)

    自旋鎖存在的問(wèn)題

    • 如果某個(gè)線程持有鎖的時(shí)間過(guò)長(zhǎng),就會(huì)導(dǎo)致其它等待獲取鎖的線程進(jìn)入循環(huán)等待,消耗CPU。使用不當(dāng)會(huì)造成CPU使用率極高。
    • 無(wú)法滿足等待時(shí)間最長(zhǎng)的線程優(yōu)先獲取鎖。不公平的鎖就會(huì)存在“線程饑餓”問(wèn)題。

    自旋鎖的優(yōu)點(diǎn)

    • 自旋鎖不會(huì)使線程狀態(tài)發(fā)生切換,一直處于用戶態(tài),即線程一直都是active的;不會(huì)使線程進(jìn)入阻塞狀態(tài),減少了不必要的上下文切換,執(zhí)行速度快
    • 非自旋鎖在獲取不到鎖的時(shí)候會(huì)進(jìn)入阻塞狀態(tài),從而進(jìn)入內(nèi)核態(tài),當(dāng)獲取到鎖的時(shí)候需要從內(nèi)核態(tài)恢復(fù),需要線程上下文切換。(線程被阻塞后便進(jìn)入內(nèi)核(Linux)調(diào)度狀態(tài),這個(gè)會(huì)導(dǎo)致系統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來(lái)回切換,嚴(yán)重影響鎖的性能)

    自旋鎖與互斥鎖的區(qū)別

    • 自旋鎖與互斥鎖都是為了實(shí)現(xiàn)保護(hù)資源共享的機(jī)制。
    • 無(wú)論是自旋鎖還是互斥鎖,在任意時(shí)刻,都最多只能有一個(gè)保持者。
    • 獲取互斥鎖的線程,如果鎖已經(jīng)被占用,則該線程將進(jìn)入睡眠狀態(tài);獲取自旋鎖的線程則不會(huì)睡眠,而是一直循環(huán)等待鎖釋放。

    1.30 說(shuō)一說(shuō)你對(duì)悲觀鎖的理解。

    參考回答

    悲觀鎖總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖(共享資源每次只給一個(gè)線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

    1.31 說(shuō)一說(shuō)你對(duì)樂(lè)觀鎖的理解。

    參考回答

    樂(lè)觀鎖總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)機(jī)制和CAS算法實(shí)現(xiàn)。樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂(lè)觀鎖。

    1.32 CAS在什么地方用到過(guò)嗎?

    參考回答

    • CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較并替換。CAS需要有3個(gè)操作數(shù):內(nèi)存地址V,舊的預(yù)期值A(chǔ),即將要更新的目標(biāo)值B。CAS指令執(zhí)行時(shí),當(dāng)且僅當(dāng)內(nèi)存地址V的值與預(yù)期值A(chǔ)相等時(shí),將內(nèi)存地址V的值修改為B,否則就什么都不做。整個(gè)比較并替換的操作是一個(gè)原子操作。

    • 高并發(fā)環(huán)境下,對(duì)同一個(gè)數(shù)據(jù)的并發(fā)讀(兩邊都讀出余額是100)與并發(fā)寫(一個(gè)寫回28,一個(gè)寫回38)導(dǎo)致的數(shù)據(jù)一致性問(wèn)題。

      解決方案是在set寫回的時(shí)候,加上初始狀態(tài)的條件compare,只有初始狀態(tài)不變時(shí),才允許set寫回成功,這是一種常見的降低讀寫鎖沖突,保證數(shù)據(jù)一致性的方法。

    1.33 談?wù)処O多路復(fù)用。

    參考回答

  • IO多路復(fù)用是一種同步IO模型,實(shí)現(xiàn)一個(gè)線程可以監(jiān)視多個(gè)文件句柄;一旦某個(gè)文件句柄就緒,就能夠通知應(yīng)用程序進(jìn)行相應(yīng)的讀寫操作;沒(méi)有文件句柄就緒時(shí)會(huì)阻塞應(yīng)用程序,交出cpu。多路是指網(wǎng)絡(luò)連接,復(fù)用指的是同一個(gè)線程。

  • IO多路復(fù)用有三種實(shí)現(xiàn)方式:select, poll, epoll

    (1)select:時(shí)間復(fù)雜度O(n),它僅僅知道了,有I/O事件發(fā)生了,卻并不知道是哪那幾個(gè)流(可能有一個(gè),多個(gè),甚至全部),只能無(wú)差別輪詢所有流,找出能讀出數(shù)據(jù),或者寫入數(shù)據(jù)的流,對(duì)他們進(jìn)行操作。所以select具有O(n)的無(wú)差別輪詢復(fù)雜度,同時(shí)處理的流越多,無(wú)差別輪詢時(shí)間就越長(zhǎng)。

    int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    select 函數(shù)監(jiān)視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調(diào)用后select函數(shù)會(huì)阻塞,直到有描述副就緒(有數(shù)據(jù) 可讀、可寫、或者有except),或者超時(shí)(timeout指定等待時(shí)間,如果立即返回設(shè)為null即可),函數(shù)返回。當(dāng)select函數(shù)返回后,可以 通過(guò)遍歷fdset,來(lái)找到就緒的描述符。

    select目前幾乎在所有的平臺(tái)上支持,其良好跨平臺(tái)支持也是它的一個(gè)優(yōu)點(diǎn)。select的一 個(gè)缺點(diǎn)在于單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過(guò)修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會(huì)造成效率的降低。

  • (2)poll:時(shí)間復(fù)雜度O(n),poll本質(zhì)上和select沒(méi)有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài), 但是它沒(méi)有最大連接數(shù)的限制,原因是它是基于鏈表來(lái)存儲(chǔ)的。

    int poll (struct pollfd *fds, unsigned int nfds, int timeout);

    不同與select使用三個(gè)位圖來(lái)表示三個(gè)fdset的方式,poll使用一個(gè) pollfd的指針實(shí)現(xiàn)。

    struct pollfd {int fd; /* file descriptor */short events; /* requested events to watch */short revents; /* returned events witnessed */ };

    pollfd結(jié)構(gòu)包含了要監(jiān)視的event和發(fā)生的event,不再使用select“參數(shù)-值”傳遞的方式。同時(shí),pollfd并沒(méi)有最大數(shù)量限制(但是數(shù)量過(guò)大后性能也是會(huì)下降)。 和select函數(shù)一樣,poll返回后,需要輪詢pollfd來(lái)獲取就緒的描述符。

    (3)epoll:時(shí)間復(fù)雜度O(1),epoll可以理解為event poll,不同于忙輪詢和無(wú)差別輪詢,epoll會(huì)把哪個(gè)流發(fā)生了怎樣的I/O事件通知我們。所以說(shuō)epoll實(shí)際上是事件驅(qū)動(dòng)(每個(gè)事件關(guān)聯(lián)上fd) 的,此時(shí)對(duì)這些流的操作都是有意義的。

    epoll操作過(guò)程需要三個(gè)接口,分別如下:

    int epoll_create(int size);//創(chuàng)建一個(gè)epoll的句柄,size用來(lái)告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    • int epoll_create(int size):創(chuàng)建一個(gè)epoll的句柄,size用來(lái)告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大,這個(gè)參數(shù)不同于select()中的第一個(gè)參數(shù),給出最大監(jiān)聽的fd+1的值,參數(shù)size并不是限制了epoll所能監(jiān)聽的描述符最大個(gè)數(shù),只是對(duì)內(nèi)核初始分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一個(gè)建議。當(dāng)創(chuàng)建好epoll句柄后,它就會(huì)占用一個(gè)fd值,在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個(gè)fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡。
    • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event):函數(shù)是對(duì)指定描述符fd執(zhí)行op操作。- epfd:是epoll_create()的返回值。- op:表示op操作,用三個(gè)宏來(lái)表示:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分別添加、刪除和修改對(duì)fd的監(jiān)聽事件。- fd:是需要監(jiān)聽的fd(文件描述符)- epoll_event:是告訴內(nèi)核需要監(jiān)聽什么事,
    • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout):等待epfd上的io事件,最多返回maxevents個(gè)事件。參數(shù)events用來(lái)從內(nèi)核得到事件的集合,maxevents告之內(nèi)核這個(gè)events有多大,這個(gè)maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size,參數(shù)timeout是超時(shí)時(shí)間(毫秒,0會(huì)立即返回,-1將不確定,也有說(shuō)法說(shuō)是永久阻塞)。該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時(shí)。
  • select、poll、epoll區(qū)別
    • 支持一個(gè)進(jìn)程所能打開的最大連接數(shù)

    • FD劇增后帶來(lái)的IO效率問(wèn)題

    • 消息傳遞方式

    1.34 談?wù)刾oll和epoll的區(qū)別。

    參考回答

  • poll將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊(duì)列中加入一項(xiàng)并繼續(xù)遍歷,如果遍歷完所有fd后沒(méi)有發(fā)現(xiàn)就緒設(shè)備,則掛起當(dāng)前進(jìn)程,直到設(shè)備就緒或者主動(dòng)超時(shí),被喚醒后它又要再次遍歷fd。這個(gè)過(guò)程經(jīng)歷了多次無(wú)謂的遍歷。

    它沒(méi)有最大連接數(shù)的限制,原因是它是基于鏈表來(lái)存儲(chǔ)的,但是同樣有缺點(diǎn):

  • 大量的fd的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核地址空間之間,而不管這樣的復(fù)制是不是有意義。
  • poll還有一個(gè)特點(diǎn)是“水平觸發(fā)”,如果報(bào)告了fd后,沒(méi)有被處理,那么下次poll時(shí)會(huì)再次報(bào)告該fd。
  • epoll是在2.6內(nèi)核中提出的,是之前的select和poll的增強(qiáng)版本。相對(duì)于select和poll來(lái)說(shuō),epoll更加靈活,沒(méi)有描述符限制。epoll使用一個(gè)文件描述符管理多個(gè)描述符,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個(gè)事件表中,這樣在用戶空間和內(nèi)核空間的copy只需一次。

    epoll支持水平觸發(fā)和邊緣觸發(fā),最大的特點(diǎn)在于邊緣觸發(fā),它只告訴進(jìn)程哪些fd剛剛變?yōu)榫途w態(tài),并且只會(huì)通知一次。還有一個(gè)特點(diǎn)是,epoll使用“事件”的就緒通知方式,通過(guò)epoll_ctl注冊(cè)fd,一旦該fd就緒,內(nèi)核就會(huì)采用類似callback的回調(diào)機(jī)制來(lái)激活該fd,epoll_wait便可以收到通知。其優(yōu)點(diǎn)有:

  • 沒(méi)有最大并發(fā)連接的限制,能打開的FD的上限遠(yuǎn)大于1024(1G的內(nèi)存上能監(jiān)聽約10萬(wàn)個(gè)端口)。
  • 效率提升,不是輪詢的方式,不會(huì)隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會(huì)調(diào)用callback函數(shù);即Epoll最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接,而跟連接總數(shù)無(wú)關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中,Epoll的效率就會(huì)遠(yuǎn)遠(yuǎn)高于select和poll。
  • 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞;即epoll使用mmap減少?gòu)?fù)制開銷。
  • 1.35 談?wù)剆elect和epoll的區(qū)別。

    參考回答

  • select 函數(shù)監(jiān)視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調(diào)用后select函數(shù)會(huì)阻塞,直到有描述符就緒(有數(shù)據(jù) 可讀、可寫、或者有except),或者超時(shí)(timeout指定等待時(shí)間,如果立即返回設(shè)為null即可),函數(shù)返回。當(dāng)select函數(shù)返回后,可以通過(guò)遍歷fdset,來(lái)找到就緒的描述符。

    select目前幾乎在所有的平臺(tái)上支持,其良好跨平臺(tái)支持也是它的一個(gè)優(yōu)點(diǎn)。select的一個(gè)缺點(diǎn)在于單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過(guò)修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會(huì)造成效率的降低。

    內(nèi)核需要傳遞消息到用戶空間,需要內(nèi)存拷貝

  • 相對(duì)于select和poll來(lái)說(shuō),epoll更加靈活,沒(méi)有描述符限制。epoll使用一個(gè)文件描述符管理多個(gè)描述符,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個(gè)事件表中,這樣在用戶空間和內(nèi)核空間的copy只需一次。

    epoll能打開的FD的上限遠(yuǎn)大于1024(1G的內(nèi)存上能監(jiān)聽約10萬(wàn)個(gè)端口)。

    效率提升,不是輪詢的方式,不會(huì)隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會(huì)調(diào)用callback函數(shù);即Epoll最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接,而跟連接總數(shù)無(wú)關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中,Epoll的效率就會(huì)遠(yuǎn)遠(yuǎn)高于select和poll。

  • 1.36 epoll有哪兩種模式?

    參考回答

    epoll對(duì)文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認(rèn)模式,LT模式與ET模式的區(qū)別如下:

    LT模式:當(dāng)epoll_wait檢測(cè)到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll_wait時(shí),會(huì)再次響應(yīng)應(yīng)用程序并通知此事件。

    ET模式:當(dāng)epoll_wait檢測(cè)到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件。如果不處理,下次調(diào)用epoll_wait時(shí),不會(huì)再次響應(yīng)應(yīng)用程序并通知此事件。

  • LT模式

    LT(level triggered)是缺省的工作方式,并且同時(shí)支持block和no-block socket。在這種做法中,內(nèi)核告訴你一個(gè)文件描述符是否就緒了,然后你可以對(duì)這個(gè)就緒的fd進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會(huì)繼續(xù)通知你的。

  • ET模式

    ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時(shí),內(nèi)核通過(guò)epoll告訴你。然后它會(huì)假設(shè)你知道文件描述符已經(jīng)就緒,并且不會(huì)再為那個(gè)文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個(gè)文件描述符不再為就緒狀態(tài)了(比如,你在發(fā)送,接收或者接收請(qǐng)求,或者發(fā)送接收的數(shù)據(jù)少于一定量時(shí)導(dǎo)致了一個(gè)EWOULDBLOCK 錯(cuò)誤)。但是請(qǐng)注意,如果一直不對(duì)這個(gè)fd作IO操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會(huì)發(fā)送更多的通知(only once)。

    ET模式在很大程度上減少了epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率要比LT模式高。epoll工作在ET模式的時(shí)候,必須使用非阻塞套接口,以避免由于一個(gè)文件句柄的阻塞讀/阻塞寫操作把處理多個(gè)文件描述符的任務(wù)餓死。

  • 1.37 說(shuō)一下epoll的原理,它的查詢速度是O(1)的嗎?

    參考回答

    epoll是一種更加高效的IO多路復(fù)用的方式,不同于忙輪詢和無(wú)差別輪詢,epoll會(huì)把哪個(gè)流發(fā)生了怎樣的I/O事件通知我們。時(shí)間復(fù)雜度為O(1)。

    epoll的執(zhí)行過(guò)程如圖所示:

  • 創(chuàng)建紅黑樹,調(diào)用epoll_create()創(chuàng)建一顆空的紅黑樹,用于存放FD及其感興趣事件;
  • 注冊(cè)感興趣事件,調(diào)用epoll_ctl()向紅黑樹中添加節(jié)點(diǎn)(FD及其感興趣事件),時(shí)間復(fù)雜度O(logN),向內(nèi)核的中斷處理程序注冊(cè)一個(gè)回調(diào)函數(shù),告訴內(nèi)核,如果這個(gè)句柄的中斷到了,就把它添加到就緒隊(duì)列中。所以,當(dāng)一個(gè)socket上有數(shù)據(jù)到了,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后就來(lái)把socket插入到就緒隊(duì)列中了;
  • 獲取就緒事件,調(diào)用epoll_wait()返回就緒隊(duì)列中的就緒事件,時(shí)間復(fù)雜度O(1);
  • 1.38 介紹域名解析成IP的全過(guò)程。

    參考回答

    第一步:檢查瀏覽器緩存中是否緩存過(guò)該域名對(duì)應(yīng)的IP地址

    用戶通過(guò)瀏覽器瀏覽過(guò)某網(wǎng)站之后,瀏覽器就會(huì)自動(dòng)緩存該網(wǎng)站域名對(duì)應(yīng)的地址,當(dāng)用戶再次訪問(wèn)的時(shí)候,瀏覽器就會(huì)從緩存中查找該域名對(duì)應(yīng)的IP地址,因?yàn)榫彺娌粌H是有大小限制,而且還有時(shí)間限制(域名被緩存的時(shí)間通過(guò)屬性來(lái)設(shè)置),所以存在域名對(duì)應(yīng)的找不到的情況。當(dāng)瀏覽器從緩存中找到了該網(wǎng)站域名對(duì)應(yīng)的地址,那么整個(gè)解析過(guò)程結(jié)束,如果沒(méi)有找到,將進(jìn)行下一步驟。對(duì)于的緩存時(shí)間問(wèn)題,不宜設(shè)置太長(zhǎng)的緩存時(shí)間,時(shí)間太長(zhǎng),如果域名對(duì)應(yīng)的發(fā)生變化,那么用戶將在一段時(shí)間內(nèi)無(wú)法正常訪問(wèn)到網(wǎng)站,如果太短,那么又造成頻繁解析域名。

    第二步:如果在瀏覽器緩存中沒(méi)有找到IP,那么將繼續(xù)查找本機(jī)系統(tǒng)是否緩存過(guò)IP

    如果第一個(gè)步驟沒(méi)有完成對(duì)域名的解析過(guò)程,那么瀏覽器會(huì)去系統(tǒng)緩存中查找系統(tǒng)是否緩存過(guò)這個(gè)域名對(duì)應(yīng)的地址,也可以理解為系統(tǒng)自己也具備域名解析的基本能力。在系統(tǒng)中,可以通過(guò)設(shè)置文件來(lái)將域名手動(dòng)綁定到某上,文件位置在。對(duì)于普通用戶,并不推薦自己手動(dòng)綁定域名和,對(duì)于開發(fā)者來(lái)說(shuō),通過(guò)綁定域名和,可以輕松切換環(huán)境,可以從測(cè)試環(huán)境切換到開發(fā)環(huán)境,方便開發(fā)和測(cè)試。在系統(tǒng)中,黑客常常修改他的電腦的文件,將用戶常常訪問(wèn)的域名綁定到他指定的上,從而實(shí)現(xiàn)了本地解析,導(dǎo)致這些域名被劫持。在或者系統(tǒng)中,文件在,修改該文件也可以實(shí)現(xiàn)同樣的目的。

    前兩步都是在本機(jī)上完成的,所以沒(méi)有在上面示例圖上展示出來(lái),從第三步開始,才正在地向遠(yuǎn)程DNS服務(wù)器發(fā)起解析域名的請(qǐng)求。

    第三步:向本地域名解析服務(wù)系統(tǒng)發(fā)起域名解析的請(qǐng)求

    如果在本機(jī)上無(wú)法完成域名的解析,那么系統(tǒng)只能請(qǐng)求本地域名解析服務(wù)系統(tǒng)進(jìn)行解析,本地域名系統(tǒng)一般都是本地區(qū)的域名服務(wù)器,比如你連接的校園網(wǎng),那么域名解析系統(tǒng)就在你的校園機(jī)房里,如果你連接的是電信、移動(dòng)或者聯(lián)通的網(wǎng)絡(luò),那么本地域名解析服務(wù)器就在本地區(qū),由各自的運(yùn)營(yíng)商來(lái)提供服務(wù)。對(duì)于本地服務(wù)器地址,系統(tǒng)使用命令就可以查看,在和系統(tǒng)下,直接使用命令來(lái)查看服務(wù)地址。一般都緩存了大部分的域名解析的結(jié)果,當(dāng)然緩存時(shí)間也受域名失效時(shí)間控制,大部分的解析工作到這里就差不多已經(jīng)結(jié)束了,負(fù)責(zé)了大部分的解析工作。

    第四步:向根域名解析服務(wù)器發(fā)起域名解析請(qǐng)求

    本地域名解析器還沒(méi)有完成解析的話,那么本地域名解析服務(wù)器將向根域名服務(wù)器發(fā)起解析請(qǐng)求。

    第五步:根域名服務(wù)器返回gTLD域名解析服務(wù)器地址

    本地域名解析向根域名服務(wù)器發(fā)起解析請(qǐng)求,根域名服務(wù)器返回的是所查域的通用頂級(jí)域()地址,常見的通用頂級(jí)域有、、、等。

    第六步:向gTLD服務(wù)器發(fā)起解析請(qǐng)求

    本地域名解析服務(wù)器向gTLD服務(wù)器發(fā)起請(qǐng)求。

    第七步:gTLD服務(wù)器接收請(qǐng)求并返回Name Server服務(wù)器

    服務(wù)器接收本地域名服務(wù)器發(fā)起的請(qǐng)求,并根據(jù)需要解析的域名,找到該域名對(duì)應(yīng)的域名服務(wù)器,通常情況下,這個(gè)服務(wù)器就是你注冊(cè)的域名服務(wù)器,那么你注冊(cè)的域名的服務(wù)商的服務(wù)器將承擔(dān)起域名解析的任務(wù)。

    第八步:Name Server服務(wù)器返回IP地址給本地服務(wù)器

    服務(wù)器查找域名對(duì)應(yīng)的地址,將地址連同值返回給本地域名服務(wù)器。

    第九步:本地域名服務(wù)器緩存解析結(jié)果

    本地域名服務(wù)器緩存解析后的結(jié)果,緩存時(shí)間由時(shí)間來(lái)控制。

    1.39 如何在Linux上配置一個(gè)IP地址,如果給定端口號(hào)如何解析出域名?

    參考回答

  • 配置Linux系統(tǒng)的IP地址的方法,主要有以下三種:
    • ifconfig

      ifconfig 命令主要是用來(lái)查看網(wǎng)卡的配置信息,因?yàn)橛盟鼇?lái)配置網(wǎng)卡的IP地址時(shí),只會(huì)臨時(shí)生效(Linux服務(wù)器重啟后就會(huì)失效)

    • setup

      setup 命令是 redhat 系列的linux系統(tǒng)(如CentOS)中專有的命令工具??梢允褂?setup 命令,來(lái)對(duì)網(wǎng)絡(luò)配置中的IP地址、子網(wǎng)掩碼、默認(rèn)網(wǎng)關(guān)、DNS服務(wù)器進(jìn)行設(shè)置。而且,setup 網(wǎng)絡(luò)配置工具設(shè)置的IP地址會(huì)永久生效。

    • 修改網(wǎng)卡的配置文件

      直接修改網(wǎng)卡的配置文件,設(shè)置方法有兩種:

      • 自動(dòng)獲取動(dòng)態(tài)IP地址
      • 手工配置靜態(tài)的IP地址
  • 使用dig命令解析域名
  • 1.40 解釋一下IP地址、子網(wǎng)掩碼、網(wǎng)關(guān)。

    參考回答

  • IP地址
  • IP地址有一個(gè)32位的連接地址,由4個(gè)8位字段組成,8位字段稱為8位位組,每個(gè)8位位組之間用點(diǎn)號(hào)隔開,用于標(biāo)識(shí)TCP/IP宿主機(jī)。每個(gè)IP地址都包含兩部分:網(wǎng)絡(luò)ID和主機(jī)ID,網(wǎng)絡(luò)ID 標(biāo)識(shí)在同一個(gè)物理網(wǎng)絡(luò)上的所有宿主機(jī),主機(jī)ID標(biāo)識(shí)網(wǎng)絡(luò)上的每一個(gè)宿主機(jī),運(yùn)行TCP/IP的每個(gè)計(jì)算機(jī)都需要唯一的IP地址。

    Intenet委員會(huì)定義了五種地址類型以適應(yīng)不同尺寸的網(wǎng)絡(luò)。地址類型定義網(wǎng)絡(luò)ID使用哪些位,它也定義了網(wǎng)絡(luò)的可能數(shù)目和每個(gè)網(wǎng)絡(luò)可能的宿主機(jī)數(shù)目.

  • 子網(wǎng)掩碼(Subnet Mask)
  • 使用子網(wǎng)可以把單個(gè)大網(wǎng)分成多個(gè)物理網(wǎng)絡(luò),并用路由器把它們連接起來(lái)。子網(wǎng)掩碼用于屏蔽IP地址的一部分,使得TCP/IP能夠區(qū)別網(wǎng)絡(luò)ID和宿主機(jī)ID。當(dāng)TCP/IP宿主機(jī)要通信時(shí),子網(wǎng)掩碼用于判斷一個(gè)宿主機(jī)是在本地網(wǎng)絡(luò)還是在遠(yuǎn)程網(wǎng)絡(luò)。

    缺省的子網(wǎng)掩碼用于不分成子網(wǎng)的TCP/IP網(wǎng)絡(luò),對(duì)應(yīng)于網(wǎng)絡(luò)ID的所有位都置為1,每個(gè)8位位組的十進(jìn)制數(shù)是255,對(duì)應(yīng)于宿主機(jī)ID的所有位都置為0。

    用于子網(wǎng)掩碼的位數(shù)決定可能的子網(wǎng)數(shù)目和每個(gè)子網(wǎng)的宿主機(jī)數(shù)目,子網(wǎng)掩碼的位數(shù)越多,則子網(wǎng)越多,但是宿主機(jī)也較少。

    例:假設(shè)A類地址子網(wǎng)數(shù)是14,則所需位數(shù)至少為4,用于子網(wǎng)的位為:   11111111, 11110000, 00000000, 00000000, 子網(wǎng)掩碼為255.240.0.0,每個(gè)子網(wǎng)的宿主機(jī)數(shù)目為2^20-2=1,048, 574個(gè)。

  • 網(wǎng)關(guān)(Gateway)
  • 網(wǎng)關(guān)就是一個(gè)網(wǎng)絡(luò)連接到另一個(gè)網(wǎng)絡(luò)的“關(guān)口”。 按照不同的分類標(biāo)準(zhǔn),網(wǎng)關(guān)也有很多種。TCP/IP協(xié)議里的網(wǎng)關(guān)是最常用的,在這里我們所講的“網(wǎng)關(guān)”均指TCP/ IP協(xié)議下的網(wǎng)關(guān)。

    網(wǎng)關(guān)實(shí)質(zhì)上是一個(gè)網(wǎng)絡(luò)通向其他網(wǎng)絡(luò)的IP地址。比如有網(wǎng)絡(luò)A和網(wǎng)絡(luò)B,網(wǎng)絡(luò)A的IP地址范圍為“192.168.1.1~192. 168.1.254”,子網(wǎng)掩碼為255.255.255.0;網(wǎng)絡(luò)B的IP地址范圍為“192.168.2.1~192. 168.2.254”,子網(wǎng)掩碼為255.255.255.0。在沒(méi)有路由器的情況下,兩個(gè)網(wǎng)絡(luò)之間是不能進(jìn)行TCP/IP通信的,即使是兩個(gè)網(wǎng)絡(luò)連接在同一臺(tái)交換機(jī)(或集線器)上,TCP/IP協(xié)議也會(huì)根據(jù)子網(wǎng)掩碼(255.255.255.0)判定兩個(gè)網(wǎng)絡(luò)中的主機(jī)處在不同的網(wǎng)絡(luò)里。而要實(shí)現(xiàn)這兩個(gè)網(wǎng)絡(luò)之間的通信,則必須通過(guò)網(wǎng)關(guān)。

    如果網(wǎng)絡(luò)A中的主機(jī)發(fā)現(xiàn)數(shù)據(jù)包的目的主機(jī)不在本地網(wǎng)絡(luò)中,就把數(shù)據(jù)包轉(zhuǎn)發(fā)給它自己的網(wǎng)關(guān),再由網(wǎng)關(guān)轉(zhuǎn)發(fā)給網(wǎng)絡(luò)B的網(wǎng)關(guān),網(wǎng)絡(luò)B的網(wǎng)關(guān)再轉(zhuǎn)發(fā)給網(wǎng)絡(luò)B的某個(gè)主機(jī)。網(wǎng)絡(luò)B向網(wǎng)絡(luò)A轉(zhuǎn)發(fā)數(shù)據(jù)包的過(guò)程也是如此。而要實(shí)現(xiàn)這兩個(gè)網(wǎng)絡(luò)之間的通信,則必須通過(guò)網(wǎng)關(guān)。如果網(wǎng)絡(luò)A中的主機(jī)發(fā)現(xiàn)數(shù)據(jù)包的目的主機(jī)不在本地網(wǎng)絡(luò)中,就把數(shù)據(jù)包轉(zhuǎn)發(fā)給它自己的網(wǎng)關(guān),再由網(wǎng)關(guān)轉(zhuǎn)發(fā)給網(wǎng)絡(luò)B的網(wǎng)關(guān),網(wǎng)絡(luò)B的網(wǎng)關(guān)再轉(zhuǎn)發(fā)給網(wǎng)絡(luò)B的某個(gè)主機(jī)。網(wǎng)絡(luò)B向網(wǎng)絡(luò)A轉(zhuǎn)發(fā)數(shù)據(jù)包的過(guò)程也是如此 所以說(shuō),只有設(shè)置好網(wǎng)關(guān)的IP地址,TCP/IP協(xié)議才能實(shí)現(xiàn)不同網(wǎng)絡(luò)之間的相互通信。那么這個(gè)IP地址是哪臺(tái)機(jī)器的IP地址呢?網(wǎng)關(guān)的IP地址是具有路由功能的設(shè)備的IP地址,具有路由功能的設(shè)備有路由器、啟用了路由協(xié)議的服務(wù)器(實(shí)質(zhì)上相當(dāng)于一臺(tái)路由器)、代理服務(wù)器(也相當(dāng)于一臺(tái)路由器)。

    1.41 說(shuō)說(shuō)IP如何尋址?

    參考回答

    IP尋址包括本地網(wǎng)絡(luò)尋址和非本地網(wǎng)絡(luò)尋址兩部分

  • 本地網(wǎng)絡(luò)尋址

    假設(shè)有2個(gè)主機(jī),他們是屬于同一個(gè)網(wǎng)段。主機(jī)A和主機(jī)B,首先主機(jī)A通過(guò)本機(jī)的hosts表或者wins系統(tǒng)或dns系統(tǒng)先將主機(jī)B的計(jì)算機(jī)名轉(zhuǎn)換為IP地址,然后用自己的IP地址與子網(wǎng)掩碼計(jì)算出自己所出的網(wǎng)段,比較目的主機(jī)B的ip地址與自己的子網(wǎng)掩碼,發(fā)現(xiàn)與自己是出于相同的網(wǎng)段,于是在自己的ARP緩存中查找是否有主機(jī)B的mac地址,如果能找到就直接做數(shù)據(jù)鏈路層封裝并且通過(guò)網(wǎng)卡將封裝好的以太網(wǎng)幀發(fā)送有物理線路上去。

    如果arp緩存中沒(méi)有主機(jī)B的的mac地址,主機(jī)A將啟動(dòng)arp協(xié)議通過(guò)在本地網(wǎng)絡(luò)上的arp廣播來(lái)查詢主機(jī)B的mac地址,獲得主機(jī)B的mac地址厚寫入arp緩存表,進(jìn)行數(shù)據(jù)鏈路層的封裝,發(fā)送數(shù)據(jù)。

  • 非本地網(wǎng)絡(luò)尋址

    假設(shè)2個(gè)主機(jī)不是相同的網(wǎng)段,不同的數(shù)據(jù)鏈路層網(wǎng)絡(luò)必須分配不同網(wǎng)段的IP地址并且由路由器將其連接起來(lái)。主機(jī)A通過(guò)本機(jī)的hosts表或wins系統(tǒng)或dns系統(tǒng)先主機(jī)B的計(jì)算機(jī)名轉(zhuǎn)換為IP地址,然后用自己的IP地址與子網(wǎng)掩碼計(jì)算出自己所處的網(wǎng)段,比較目的目的主機(jī)B的IP地址,發(fā)現(xiàn)與自己處于不同的網(wǎng)段。于是主機(jī)A將知道應(yīng)該將次數(shù)據(jù)包發(fā)送給自己的缺省網(wǎng)關(guān),即路由器的本地接口。

    主機(jī)A在自己的ARP緩存中查找是否有缺省網(wǎng)關(guān)的MAC地址,如果能夠找到就直接做數(shù)據(jù)鏈路層封裝并通過(guò)網(wǎng)卡,將封裝好的以太網(wǎng)數(shù)據(jù)幀發(fā)送到物理線路上去,如果arp緩存表中沒(méi)有缺省網(wǎng)關(guān)的Mac地址,主機(jī)A將啟動(dòng)arp協(xié)議通過(guò)在本地網(wǎng)絡(luò)上的arp廣播來(lái)查詢?nèi)笔【W(wǎng)關(guān)的mac地址,獲得缺省網(wǎng)關(guān)的mac地址后寫入arp緩存表,進(jìn)行數(shù)據(jù)鏈路層的封裝,發(fā)送數(shù)據(jù)。

    數(shù)據(jù)幀到達(dá)路由器的接受接口后首先解封裝,變成IP數(shù)據(jù)包,對(duì)IP包進(jìn)行處理,根據(jù)目的IP地址查找路由表,決定轉(zhuǎn)發(fā)接口后做適應(yīng)轉(zhuǎn)發(fā)接口數(shù)據(jù)鏈路層協(xié)議幀的封裝,并且發(fā)送到下一跳路由器,此過(guò)程繼續(xù)直至到達(dá)目的的網(wǎng)絡(luò)與目的主機(jī)。

  • 1.42 操作系統(tǒng)的地址有幾種,請(qǐng)具體說(shuō)明。

    參考回答

    操作系統(tǒng)有物理地址、邏輯地址、線性地址(也叫虛擬地址)三種地址

  • 物理地址

    在存儲(chǔ)器里以字節(jié)為單位存儲(chǔ)信息,為正確地存放或取得信息,每一個(gè)字節(jié)單元給以一個(gè)唯一的存儲(chǔ)器地址,稱為物理地址(Physical Address),又叫實(shí)際地址或絕對(duì)地址。

    地址從0開始編號(hào),順序地每次加1,因此存儲(chǔ)器的物理地址空間是呈線性增長(zhǎng)的。它是用二進(jìn)制數(shù)來(lái)表示的,是無(wú)符號(hào)整數(shù),書寫格式為十六進(jìn)制數(shù)。它是出現(xiàn)在CPU外部地址總線上的尋址物理內(nèi)存的地址信號(hào),是地址變換的最終結(jié)果。用于內(nèi)存芯片級(jí)的單元尋址,與處理器和CPU連接的地址總線相對(duì)應(yīng)。

  • 邏輯地址

    邏輯地址是指在計(jì)算機(jī)體系結(jié)構(gòu)中是指應(yīng)用程序角度看到的內(nèi)存單元(memory cell)、存儲(chǔ)單元(storage element)、網(wǎng)絡(luò)主機(jī)(network host)的地址。 邏輯地址往往不同于物理地址(physical address),通過(guò)地址翻譯器(address translator)或映射函數(shù)可以把邏輯地址轉(zhuǎn)化為物理地址。

    在有地址變換功能的計(jì)算機(jī)中,訪問(wèn)指令給出的地址 (操作數(shù)) 叫邏輯地址,也叫相對(duì)地址。要經(jīng)過(guò)尋址方式的計(jì)算或變換才得到內(nèi)存儲(chǔ)器中的物理地址。把用戶程序中使用的地址稱為相對(duì)地址即邏輯地址。邏輯地址由兩個(gè)16位的地址分量構(gòu)成,一個(gè)為段基值,另一個(gè)為偏移量。兩個(gè)分量均為無(wú)符號(hào)數(shù)編碼。

  • 線性地址

    線性地址(Linear Address)是邏輯地址到物理地址變換之間的中間層。在分段部件中邏輯地址是段中的偏移地址,然后加上基地址就是線性地址。

    線性地址是一個(gè)32位無(wú)符號(hào)整數(shù),可以用來(lái)表示高達(dá)4GB的地址,也就是,高達(dá)4294967296個(gè)內(nèi)存單元。線性地址通常用十六進(jìn)制數(shù)字表示,值的范圍從0x00000000到0xffffffff)。程序代碼會(huì)產(chǎn)生邏輯地址,通過(guò)邏輯地址變換就可以生成一個(gè)線性地址。如果啟用了分頁(yè)機(jī)制,那么線性地址可以再經(jīng)過(guò)變換以產(chǎn)生一個(gè)物理地址。當(dāng)采用4KB分頁(yè)大小的時(shí)候,線性地址的高10位為頁(yè)目錄項(xiàng)在頁(yè)目錄表中的編號(hào),中間10位為頁(yè)表中的頁(yè)號(hào),其低12位則為偏移地址。如果是使用4MB分頁(yè)機(jī)制,則高10位頁(yè)號(hào),低22位為偏移地址。如果沒(méi)有啟用分頁(yè)機(jī)制,那么線性地址直接就是物理地址。

  • 1.43 Linux的靜態(tài)網(wǎng)絡(luò)怎么配置?

    參考回答

    網(wǎng)絡(luò)配置的配置文件在/etc/sysconfig/network-scripts/下,文件名前綴為ifcfg-后面跟的就是網(wǎng)卡的名稱,可以使用ifconfig查看,也可以使用命令: ls /etc/sysconfig/network-scripts/ifcfg-* 列出所有的設(shè)備配置文件,

    比如這里就是ifcfg-eno16777984這個(gè)文件,ifcfg-lo是本地回環(huán)地址的配置文件,所有計(jì)算機(jī)都有,不用動(dòng)他,

    現(xiàn)在使用: vim /etc/sysconfig/network-scripts/ifcfg-eno16777984 打開配置文件進(jìn)行編輯,默認(rèn)情況是dhcp動(dòng)態(tài)獲取的,如下圖:

    這時(shí)候如果想修改成靜態(tài)的,首先把BOOTPROTO="dhcp"改成BOOTPROTO="static"表示靜態(tài)獲取,然后在最后追加比如下面的配置:

    BROADCAST=192.168.1.255 IPADDR=192.168.1.33 NETMASK=255.255.255.0 GATEWAY=192.168.1.1

    BROADCAST設(shè)置的是局域網(wǎng)廣播地址,IPADDR就是靜態(tài)IP,NETMASK是子網(wǎng)掩碼,GATEWAY就是網(wǎng)關(guān)或者路由地址;需要說(shuō)明,原來(lái)還有個(gè)NETWORK配置的是局域網(wǎng)網(wǎng)絡(luò)號(hào),這個(gè)是ifcalc自動(dòng)計(jì)算的,所以這里配置這些就足夠了,最終配置如下圖:

    配置完成之后保存退出,

    設(shè)置完畢,然后使用命令: /etc/init.d/network restart 或者 service network restart 重啟網(wǎng)絡(luò)服務(wù),重啟后如果路由配置了支持靜態(tài)IP,那么linux就能獲取到剛才配置的IP地址,這樣靜態(tài)IP就配置成功了

    1.44 DNS用了哪些協(xié)議?

    參考回答

  • DNS在進(jìn)行區(qū)域傳輸?shù)臅r(shí)候使用TCP協(xié)議,其它時(shí)候則使用UDP協(xié)議;

    DNS的規(guī)范規(guī)定了2種類型的DNS服務(wù)器,一個(gè)叫主DNS服務(wù)器,一個(gè)叫輔助DNS服務(wù)器。在一個(gè)區(qū)中主DNS服務(wù)器從自己本機(jī)的數(shù)據(jù)文件中讀取該區(qū)的DNS數(shù)據(jù)信息,而輔助DNS服務(wù)器則從區(qū)的主DNS服務(wù)器中讀取該區(qū)的DNS數(shù)據(jù)信息。當(dāng)一個(gè)輔助DNS服務(wù)器啟動(dòng)時(shí),它需要與主DNS服務(wù)器通信,并加載數(shù)據(jù)信息,這就叫做區(qū)傳送(zone transfer)。

  • 為什么既使用TCP又使用UDP?

    UDP報(bào)文的最大長(zhǎng)度為512字節(jié),而TCP則允許報(bào)文長(zhǎng)度超過(guò)512字節(jié)。當(dāng)DNS查詢超過(guò)512字節(jié)時(shí),協(xié)議的TC標(biāo)志出現(xiàn)刪除標(biāo)志,這時(shí)則使用TCP發(fā)送。通常傳統(tǒng)的UDP報(bào)文一般不會(huì)大于512字節(jié)。

  • 區(qū)域傳送時(shí)使用TCP,主要有以下兩點(diǎn)考慮:

  • 輔域名服務(wù)器會(huì)定時(shí)(一般時(shí)3小時(shí))向主域名服務(wù)器進(jìn)行查詢以便了解數(shù)據(jù)是否有變動(dòng)。如有變動(dòng),則會(huì)執(zhí)行一次區(qū)域傳送,進(jìn)行數(shù)據(jù)同步。區(qū)域傳送將使用TCP而不是UDP,因?yàn)閿?shù)據(jù)同步傳送的數(shù)據(jù)量比一個(gè)請(qǐng)求和應(yīng)答的數(shù)據(jù)量要多得多。
  • TCP是一種可靠的連接,保證了數(shù)據(jù)的準(zhǔn)確性。
  • 域名解析時(shí)使用UDP協(xié)議:

    客戶端向DNS服務(wù)器查詢域名,一般返回的內(nèi)容都不超過(guò)512字節(jié),用UDP傳輸即可。不用經(jīng)過(guò)TCP三次握手,這樣DNS服務(wù)器負(fù)載更低,響應(yīng)更快。雖然從理論上說(shuō),客戶端也可以指定向DNS服務(wù)器查詢的時(shí)候使用TCP,但事實(shí)上,很多DNS服務(wù)器進(jìn)行配置的時(shí)候,僅支持UDP查詢包。

  • 1.45 說(shuō)一說(shuō)你對(duì)Linux內(nèi)核的了解。

    參考回答

    內(nèi)核是操作系統(tǒng)的核心,具有很多最基本功能,它負(fù)責(zé)管理系統(tǒng)的進(jìn)程、內(nèi)存、設(shè)備驅(qū)動(dòng)程序、文件和網(wǎng)絡(luò)系統(tǒng),決定著系統(tǒng)的性能和穩(wěn)定性。

    Linux 內(nèi)核有 4 項(xiàng)工作:

  • 內(nèi)存管理: 追蹤記錄有多少內(nèi)存存儲(chǔ)了什么以及存儲(chǔ)在哪里
  • 進(jìn)程管理: 確定哪些進(jìn)程可以使用中央處理器(CPU)、何時(shí)使用以及持續(xù)多長(zhǎng)時(shí)間
  • 設(shè)備驅(qū)動(dòng)程序: 充當(dāng)硬件與進(jìn)程之間的調(diào)解程序/解釋程序
  • 系統(tǒng)調(diào)用和安全防護(hù): 從流程接受服務(wù)請(qǐng)求
  • 在正確實(shí)施的情況下,內(nèi)核對(duì)于用戶是不可見的,它在自己的小世界(稱為內(nèi)核空間)中工作,并從中分配內(nèi)存和跟蹤所有內(nèi)容的存儲(chǔ)位置。用戶所看到的內(nèi)容(例如 Web 瀏覽器和文件則被稱為用戶空間。這些應(yīng)用通過(guò)系統(tǒng)調(diào)用接口(SCI)與內(nèi)核進(jìn)行交互。

    舉例來(lái)說(shuō), 內(nèi)核就像是一個(gè)為高管(硬件)服務(wù)的忙碌的個(gè)人助理。助理的工作就是將員工和公眾(用戶)的消息和請(qǐng)求(進(jìn)程)轉(zhuǎn)交給高管,記住存放的內(nèi)容和位置(內(nèi)存),并確定在任何特定的時(shí)間誰(shuí)可以拜訪高管、會(huì)面時(shí)間有多長(zhǎng)。

    為了更具象地理解內(nèi)核,不妨將 Linux 計(jì)算機(jī)想象成有三層結(jié)構(gòu):

    硬件:物理機(jī)(這是系統(tǒng)的底層結(jié)構(gòu)或基礎(chǔ))是由內(nèi)存(RAM)、處理器(或 CPU)以及輸入/輸出(I/O)設(shè)備(例如存儲(chǔ)、網(wǎng)絡(luò)和圖形)組成的。其中,CPU 負(fù)責(zé)執(zhí)行計(jì)算和內(nèi)存的讀寫操作。

    Linux 內(nèi)核:操作系統(tǒng)的核心。它是駐留在內(nèi)存中的軟件,用于告訴 CPU 要執(zhí)行哪些操作。

    用戶進(jìn)程:這些是內(nèi)核所管理的運(yùn)行程序。用戶進(jìn)程共同構(gòu)成了用戶空間。用戶進(jìn)程有時(shí)也簡(jiǎn)稱為進(jìn)程。內(nèi)核還允許這些進(jìn)程和服務(wù)器彼此進(jìn)行通信(稱為進(jìn)程間通信或 IPC)。

    系統(tǒng)執(zhí)行的代碼通過(guò)以下兩種模式之一在 CPU 上運(yùn)行:內(nèi)核模式或用戶模式。在內(nèi)核模式下運(yùn)行的代碼可以不受限制地訪問(wèn)硬件,而用戶模式則會(huì)限制 SCI 對(duì) CPU 和內(nèi)存的訪問(wèn)。內(nèi)存也存在類似的分隔情況(內(nèi)核空間和用戶空間)。這兩個(gè)小細(xì)節(jié)構(gòu)成了一些復(fù)雜操作的基礎(chǔ),例如安全防護(hù)、構(gòu)建容器和虛擬機(jī)的權(quán)限分隔。

    這也意味著:如果進(jìn)程在用戶模式下失敗,則損失有限,無(wú)傷大雅,可以由內(nèi)核進(jìn)行修復(fù)。另一方面,由于內(nèi)核進(jìn)程要訪問(wèn)內(nèi)存和處理器,因此內(nèi)核進(jìn)程的崩潰可能會(huì)引起整個(gè)系統(tǒng)的崩潰。由于用戶進(jìn)程之間會(huì)有適當(dāng)?shù)谋Wo(hù)措施和權(quán)限要求,因此一個(gè)進(jìn)程的崩潰通常不會(huì)引起太多問(wèn)題。

    1.46 說(shuō)一說(shuō)你對(duì)Linux內(nèi)核態(tài)與用戶態(tài)的了解。

    參考回答

    內(nèi)核態(tài)其實(shí)從本質(zhì)上說(shuō)就是內(nèi)核,它是一種特殊的軟件程序,控制計(jì)算機(jī)的硬件資源,例如協(xié)調(diào)CPU資源,分配內(nèi)存資源,并且提供穩(wěn)定的環(huán)境供應(yīng)用程序運(yùn)行。

    用戶態(tài)就是提供應(yīng)用程序運(yùn)行的空間,為了使應(yīng)用程序訪問(wèn)到內(nèi)核管理的資源例如CPU,內(nèi)存,I/O。內(nèi)核必須提供一組通用的訪問(wèn)接口,這些接口就叫系統(tǒng)調(diào)用。

  • 系統(tǒng)調(diào)用是操作系統(tǒng)的最小功能單位。根據(jù)不同的應(yīng)用場(chǎng)景,不同的Linux發(fā)行版本提供的系統(tǒng)調(diào)用數(shù)量也不盡相同,大致在240-350之間。這些系統(tǒng)調(diào)用組成了用戶態(tài)跟內(nèi)核態(tài)交互的基本接口。
  • 從用戶態(tài)到內(nèi)核態(tài)切換可以通過(guò)三種方式:
  • 系統(tǒng)調(diào)用:系統(tǒng)調(diào)用本身就是中斷,但是是軟件中斷,跟硬中斷不同。
  • 異常:如果當(dāng)前進(jìn)程運(yùn)行在用戶態(tài),如果這個(gè)時(shí)候發(fā)生了異常事件,就會(huì)觸發(fā)切換。例如:缺頁(yè)異常。
  • 外設(shè)中斷:當(dāng)外設(shè)完成用戶的請(qǐng)求時(shí),會(huì)向CPU發(fā)送中斷信號(hào)。
  • 1.47 Linux負(fù)載是什么?

    參考回答

    負(fù)載(load)是linux機(jī)器的一個(gè)重要指標(biāo),直觀了反應(yīng)了機(jī)器當(dāng)前的狀態(tài)。

    在UNIX系統(tǒng)中,系統(tǒng)負(fù)載是對(duì)當(dāng)前CPU工作量的度量,被定義為特定時(shí)間間隔內(nèi)運(yùn)行隊(duì)列中的平均線程數(shù)。load average 表示機(jī)器一段時(shí)間內(nèi)的平均load。這個(gè)值越低越好。負(fù)載過(guò)高會(huì)導(dǎo)致機(jī)器無(wú)法處理其他請(qǐng)求及操作,甚至導(dǎo)致死機(jī)。

    top 或 uptime 等命令會(huì)輸出系統(tǒng)的平均負(fù)載 (Load Average),一般會(huì)有三個(gè)值,分別代表 1 分鐘,5 分鐘和 15 分鐘的平均負(fù)載。

    負(fù)載記錄的是 CPU 的負(fù)荷,能對(duì) CPU 造成負(fù)荷的是進(jìn)程(包括線程)的執(zhí)行。負(fù)載的數(shù)值代表的是 CPU 還沒(méi)處理完的進(jìn)程的數(shù)目。

    系統(tǒng)的負(fù)載采用的是指數(shù)移動(dòng)平均,計(jì)算方法如下:

    S(0) = 0 S(t) = a * X(t) + (1-a)*S(t-1)

    其中,X(t) 為最近一次采樣的值,a 為最近采樣值占的比重,S(t) 則是系統(tǒng)最近一次采樣的負(fù)載。

    指數(shù)移動(dòng)平均的計(jì)算方式會(huì)累計(jì)歷史所有的采樣值,但離現(xiàn)在越久,占的比重越小。更具體的,Linux 系統(tǒng)上對(duì) 1 分鐘的平均負(fù)載取 a 的取值2為 1 - e^(-5/60),5 分鐘為 1 - e^(-5s/5min),以此類推。

    以一分鐘為例,上面的取值能達(dá)到的效果是,最近一分鐘的采樣占所有歷史值的比重約為 63%(準(zhǔn)確值為 1 - 1/e),5 分鐘和 15 分鐘也一樣。

    單核滿載是 1,有 n 核滿載是 n。一般說(shuō)線上運(yùn)行的系統(tǒng)大于 0.7 的時(shí)候就要注意了。

    1.48 Linux如何設(shè)置開機(jī)啟動(dòng)?

    參考回答

  • 編輯rc.loacl腳本

    linux開機(jī)之后會(huì)執(zhí)行/etc/rc.local文件中的腳本。

    所以可以直接在/etc/rc.local中添加啟動(dòng)腳本。

    $ vim /etc/rc.local
  • 添加一個(gè)開機(jī)啟動(dòng)服務(wù)。

    將啟動(dòng)腳本復(fù)制到 /etc/init.d目錄下,并設(shè)置腳本權(quán)限, 假設(shè)腳本為test

    $ mv test /etc/init.d/test$ sudo chmod 755 /etc/init.d/test

    將該腳本放倒啟動(dòng)列表中去

    $ cd .etc/init.d$ sudo update-rc.d test defaults 95

    注:其中數(shù)字95是腳本啟動(dòng)的順序號(hào),按照自己的需要相應(yīng)修改即可。在有多個(gè)啟動(dòng)腳本,而它們之間又有先后啟動(dòng)的依賴關(guān)系時(shí)就知道這個(gè)數(shù)字的具體作用了。

    將該腳本從啟動(dòng)列表中剔除

    $ cd /etc/init.d$ sudo update-rc.d -f test remove
  • 1.49 談?wù)凩inux的內(nèi)存管理。

    參考回答

    常見的計(jì)算機(jī)存儲(chǔ)層次如下:

    • 寄存器:CPU提供的,讀寫ns級(jí)別,容量字節(jié)級(jí)別。
    • CPU緩存:CPU和CPU間的緩存,讀寫10ns級(jí)別,容量較大一些,百到千節(jié)。
    • 主存:動(dòng)態(tài)內(nèi)存,讀寫100ns級(jí)別,容量GB級(jí)別。
    • 外部存儲(chǔ)介質(zhì):磁盤、SSD,讀寫ms級(jí)別,容量可擴(kuò)展到TB級(jí)別。

    CPU內(nèi)的緩存示意圖如下:

    其中 L1d 和 L1i 都是CPU內(nèi)部的cache,

    • L1d 是數(shù)據(jù)cache。
    • L1i 是指令緩存。
    • L2是CPU內(nèi)部的,不區(qū)分指令和數(shù)據(jù)的。
    • 由于現(xiàn)代PC有多個(gè)CPU,L3緩存多個(gè)核心共用一個(gè)。

    對(duì)于編程人員來(lái)說(shuō),絕大部分觀察主存和外部存儲(chǔ)介質(zhì)就可以了。如果要做極致的性能優(yōu)化,可以關(guān)注L1、L2、L3的cache,比如nginx的綁核操作、pthread調(diào)度會(huì)影響CPU cache等。

    1. 虛擬內(nèi)存

    物理內(nèi)存是有限的(即使支持了熱插拔)、非連續(xù)的,不同的CPU架構(gòu)對(duì)物理內(nèi)存的組織都不同。這使得直接使用物理內(nèi)存非常復(fù)雜,為了降低使用內(nèi)存的復(fù)雜度,引入了虛擬內(nèi)存機(jī)制。

    虛擬內(nèi)存抽象了應(yīng)用程序物理內(nèi)存的細(xì)節(jié),只允許物理內(nèi)存保存所需的信息(按需分頁(yè)),并提供了一種保護(hù)和控制進(jìn)程間數(shù)據(jù)共享數(shù)據(jù)的機(jī)制。有了虛擬內(nèi)存機(jī)制之后,每次訪問(wèn)可以使用更易理解的虛擬地址,讓CPU轉(zhuǎn)換成實(shí)際的物理地址訪問(wèn)內(nèi)存,降低了直接使用、管理物理內(nèi)存的門檻。

    物理內(nèi)存按大小被分成頁(yè)框、頁(yè),每塊物理內(nèi)存可以被映射為一個(gè)或多個(gè)虛擬內(nèi)存頁(yè)。這塊映射關(guān)系,由操作系統(tǒng)的頁(yè)表來(lái)保存,頁(yè)表是有層級(jí)的。層級(jí)最低的頁(yè)表,保存實(shí)際頁(yè)面的物理地址,較高層級(jí)的頁(yè)表包含指向低層級(jí)頁(yè)表的物理地址,指向頂級(jí)的頁(yè)表的地址,駐留在寄存器中。當(dāng)執(zhí)行地址轉(zhuǎn)換時(shí),先從寄存器獲取頂級(jí)頁(yè)表地址,然后依次索引,找到具體頁(yè)面的物理地址。

    2. 大頁(yè)機(jī)制

    虛擬地址轉(zhuǎn)換的過(guò)程中,需要好幾個(gè)內(nèi)存訪問(wèn),由于內(nèi)存訪問(wèn)相對(duì)CPU較慢,為了提高性能,CPU維護(hù)了一個(gè)TLB地址轉(zhuǎn)換的cache,TLB是比較重要且珍稀的緩存,對(duì)于大內(nèi)存工作集的應(yīng)用程序,會(huì)因TLB命中率低大大影響到性能。

    為了減少TLB的壓力,增加TLB緩存的命中率,有些系統(tǒng)會(huì)把頁(yè)的大小設(shè)為MB或者GB,這樣頁(yè)的數(shù)目少了,需要轉(zhuǎn)換的頁(yè)表項(xiàng)也小了,足以把虛擬地址和物理地址的映射關(guān)系,全部保存于TLB中。

    3. 區(qū)域概念

    通常硬件會(huì)對(duì)訪問(wèn)不同的物理內(nèi)存的范圍做出限制,在某些情況下設(shè)備無(wú)法對(duì)所有的內(nèi)存區(qū)域做DMA。在其他情況下,物理內(nèi)存的大小也會(huì)超過(guò)了虛擬內(nèi)存的最大可尋址大小,需要執(zhí)行特殊操作,才能訪問(wèn)這些區(qū)域。這些情況下,Linux對(duì)內(nèi)存頁(yè)的可能使用情況將其分組到各自的區(qū)域中(方便管理和限制)。比如ZONE_DMA用于指明哪些可以用于DMA的區(qū)域,ZONE_HIGHMEM包含未永久映射到內(nèi)核地址空間的內(nèi)存,ZONE_NORMAL標(biāo)識(shí)正常的內(nèi)存區(qū)域。

    4. 節(jié)點(diǎn)

    多核CPU的系統(tǒng)中,通常是NUMA系統(tǒng)(非統(tǒng)一內(nèi)存訪問(wèn)系統(tǒng))。在這種系統(tǒng)中,內(nèi)存被安排成具有不同訪問(wèn)延遲的存儲(chǔ)組,這取決于與處理器的距離。每一個(gè)庫(kù),被稱為一個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)Linux構(gòu)建了一個(gè)獨(dú)立的內(nèi)存管理子系統(tǒng)。一個(gè)節(jié)點(diǎn)有自己的區(qū)域集、可用頁(yè)和已用頁(yè)表和各種統(tǒng)計(jì)計(jì)數(shù)器。

    5. page cache

    從外部存儲(chǔ)介質(zhì)中加載數(shù)據(jù)到內(nèi)存中,這個(gè)過(guò)程是比較耗時(shí)的,因?yàn)橥獠看鎯?chǔ)介質(zhì)讀寫性能毫秒級(jí)。為了減少外部存儲(chǔ)設(shè)備的讀寫,Linux內(nèi)核提供了Page cache。最常見的操作,每次讀取文件時(shí),數(shù)據(jù)都會(huì)被放入頁(yè)面緩存中,以避免后續(xù)讀取時(shí)所進(jìn)行昂貴的磁盤訪問(wèn)。同樣,當(dāng)寫入文件時(shí),數(shù)據(jù)被重新放置在緩存中,被標(biāo)記為臟頁(yè),定期的更新到存儲(chǔ)設(shè)備上,以提高讀寫性能。

    6. 匿名內(nèi)存

    匿名內(nèi)存或者匿名映射表示不受文件系統(tǒng)支持的內(nèi)存,比如程序的堆棧隱式創(chuàng)立的,或者顯示通過(guò)mmap創(chuàng)立的。

    7. 內(nèi)存回收

    貫穿系統(tǒng)的生命周期,一個(gè)物理頁(yè)可存儲(chǔ)不同類型的數(shù)據(jù),可以是內(nèi)核的數(shù)據(jù)結(jié)構(gòu),或是DMA訪問(wèn)的buffer,或是從文件系統(tǒng)讀取的數(shù)據(jù),或是用戶程序分配的內(nèi)存等。

    根據(jù)頁(yè)面的使用情況,Linux內(nèi)存管理對(duì)其進(jìn)行了不同的處理,可以隨時(shí)釋放的頁(yè)面,稱之為可回收頁(yè)面,這類頁(yè)面為:頁(yè)面緩存或者是匿名內(nèi)存(被再次交換到硬盤上)

    大多數(shù)情況下,保存內(nèi)部?jī)?nèi)核數(shù)據(jù)并用DMA緩沖區(qū)的頁(yè)面是不能重新被回收的,但是某些情況下,可以回收使用內(nèi)核數(shù)據(jù)結(jié)構(gòu)的頁(yè)面。例如:文件系統(tǒng)元數(shù)據(jù)的內(nèi)存緩存,當(dāng)系統(tǒng)處于內(nèi)存壓力情況下,可以從主存中丟棄它們。

    釋放可回收的物理內(nèi)存頁(yè)的過(guò)程,被稱之為回收,可以同步或者異步的回收操作。當(dāng)系統(tǒng)負(fù)載增加到一定程序時(shí),kswapd守護(hù)進(jìn)程會(huì)異步的掃描物理頁(yè),可回收的物理頁(yè)被釋放,并逐出備份到存儲(chǔ)設(shè)備。

    8. compaction

    系統(tǒng)運(yùn)行一段時(shí)間,內(nèi)存就會(huì)變得支離破碎。雖然使用虛擬村內(nèi)可以將分散的物理頁(yè)顯示為連續(xù)的物理頁(yè),但有時(shí)需要分配較大的物理連續(xù)內(nèi)存區(qū)域。比如設(shè)備驅(qū)動(dòng)程序需要一個(gè)用于DMA的大緩沖區(qū)時(shí),或者大頁(yè)內(nèi)存機(jī)制分頁(yè)時(shí)。內(nèi)存compact可以解決了內(nèi)存碎片的問(wèn)題,這個(gè)機(jī)制將被占用的頁(yè)面,從內(nèi)存區(qū)域合適的移動(dòng),以換取大塊的空閑物理頁(yè)的過(guò)程,由kcompactd守護(hù)進(jìn)程完成。

    9. OOM killer

    機(jī)器上的內(nèi)存可能會(huì)被耗盡,并且內(nèi)核將無(wú)法回收足夠的內(nèi)存用于運(yùn)行新的程序,為了保存系統(tǒng)的其余部分,內(nèi)核會(huì)調(diào)用OOM killer殺掉一些進(jìn)程,以釋放內(nèi)存。

  • 段頁(yè)機(jī)制
  • 段頁(yè)機(jī)制是操作系統(tǒng)管理內(nèi)存的一種方式,簡(jiǎn)單的來(lái)說(shuō),就是如何管理、組織系統(tǒng)中的內(nèi)存。要理解這種機(jī)制,需要了解一下內(nèi)存尋址的發(fā)展歷程。

    • 直接尋址:早期的內(nèi)存很小,通過(guò)硬編碼的形式,直接定位到內(nèi)存地址。這種方式有著明顯的缺點(diǎn):可控性弱、難以重定位、難以維護(hù)
    • 分段機(jī)制:8086處理器,尋址空間達(dá)到1MB,即地址線擴(kuò)展了20位,由于制作20位的寄存器較為困難,為了能在16位的寄存器的基礎(chǔ)上,尋址20位的地址空間,引入了段的概念,即內(nèi)存地址=段基址左移4位+偏移
    • 分頁(yè)機(jī)制:隨著尋址空間的進(jìn)一步擴(kuò)大、虛擬內(nèi)存技術(shù)的引入,操作系統(tǒng)引入了分頁(yè)機(jī)制。引入分頁(yè)機(jī)制后,邏輯地址經(jīng)過(guò)段機(jī)制轉(zhuǎn)換得到的地址僅是中間地址,還需要通過(guò)頁(yè)機(jī)制轉(zhuǎn)換,才能得到實(shí)際的物理地址。邏輯地址 -->(分段機(jī)制) 線性地址 -->(分頁(yè)機(jī)制) 物理地址。

    1.50 談?wù)剝?nèi)存映射文件。

    參考回答

  • 內(nèi)存映射(mmap) 是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其他對(duì)象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和應(yīng)用程序進(jìn)程虛擬地址空間中一段虛擬地址的一一映射關(guān)系。實(shí)現(xiàn)這樣的映射關(guān)系后,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會(huì)自動(dòng)回寫藏頁(yè)面到對(duì)應(yīng)的文件磁盤上。應(yīng)用程序處理映射部分如同訪問(wèn)主存。
  • mmap內(nèi)存映射原理
  • (1)線程啟動(dòng)映射過(guò)程,并在虛擬地址空間中為映射創(chuàng)建虛擬映射區(qū)域。

    先在用戶空間調(diào)用庫(kù)函數(shù)mmap,并在進(jìn)程當(dāng)前進(jìn)程的虛擬地址空間中,尋找一段空閑的滿足要求的連續(xù)虛擬地址作為內(nèi)存虛擬映射區(qū)域,對(duì)此區(qū)域初始化并插入進(jìn)程的虛擬地址區(qū)域鏈表或樹中。

    (2)系統(tǒng)在內(nèi)核空間調(diào)用內(nèi)核函數(shù)mmap,實(shí)現(xiàn)文件物理地址和進(jìn)程虛擬地址之間的一一映射關(guān)系。

    (3)進(jìn)程發(fā)起堆這片映射空間的訪問(wèn)

    進(jìn)程讀寫操作訪問(wèn)虛擬地址,查詢頁(yè)表,發(fā)現(xiàn)這一段地址并不在內(nèi)存的物理頁(yè)面上,因?yàn)殡m然建立了映射關(guān)系,但是還沒(méi)有將文件從磁盤移到內(nèi)存中。由此發(fā)生缺頁(yè)中斷,內(nèi)核請(qǐng)求從磁盤調(diào)入頁(yè)面。調(diào)頁(yè)過(guò)程先在交換緩存空間(swap cache)中查找,若沒(méi)有則通過(guò)nopage函數(shù)把缺失頁(yè)從磁盤調(diào)入內(nèi)存。之后進(jìn)程會(huì)對(duì)其做讀寫操作,若寫操作改變了頁(yè)面內(nèi)容,一段時(shí)間后系統(tǒng)會(huì)自動(dòng)回寫臟頁(yè)面到磁盤中。(修改過(guò)的臟頁(yè)面不會(huì)立即更新到文件中,可以調(diào)用msync來(lái)強(qiáng)制同步,寫入文件)

  • mmap和分頁(yè)文件操作的區(qū)別
  • 區(qū)別在于分頁(yè)文件操作在進(jìn)程訪存時(shí)是需要先查詢頁(yè)面緩存 (page cache) 的,若發(fā)生缺頁(yè)中斷,需要通過(guò)inode定位文件磁盤地址,先把缺失文件復(fù)制到page cache,再?gòu)膒age cache復(fù)制到內(nèi)存中,才能進(jìn)行訪問(wèn)。這樣訪存需要經(jīng)過(guò)兩次文件復(fù)制,寫操作也是一樣??偨Y(jié)來(lái)說(shuō),常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤,使用了頁(yè)緩存機(jī)制。這樣造成讀文件時(shí)需要先將文件頁(yè)從磁盤拷貝到頁(yè)緩存中,由于頁(yè)緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址,所以還需要將頁(yè)緩存中數(shù)據(jù)頁(yè)再次拷貝到內(nèi)存對(duì)應(yīng)的用戶空間中。但mmap的優(yōu)勢(shì)在于,把磁盤文件與進(jìn)程虛擬地址做了映射,這樣可以跳過(guò)page cache,只使用一次數(shù)據(jù)拷貝。

    1.51 談?wù)勌摂M內(nèi)存模型。

    參考回答

    虛擬內(nèi)存分成五大區(qū),分別為棧區(qū)、堆區(qū)、全局區(qū)(靜態(tài)區(qū))、文字常量區(qū)(常量存儲(chǔ)區(qū))、程序代碼區(qū)。五大區(qū)特性如下:

  • 棧區(qū)(stack): 由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
  • 堆區(qū)(heap): 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。
  • 全局區(qū)(靜態(tài)區(qū))(static):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后由系統(tǒng)釋放。
  • 文字常量區(qū)(常量存儲(chǔ)區(qū)) :常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放。這是一塊比較特殊的存儲(chǔ)區(qū),他們里面存放的是常量,不允許修改。
  • 程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼。
  • 答案解析

    以32位的操作系統(tǒng)為例,32位的操作系統(tǒng)每個(gè)進(jìn)程對(duì)應(yīng)的虛擬內(nèi)存為4G(2的32次方),其中內(nèi)核區(qū)1G,用戶區(qū)3G。結(jié)構(gòu)圖如下:

    1.52 什么是物理內(nèi)存和虛擬內(nèi)存,為什么要有虛擬內(nèi)存?

    參考回答

  • 物理內(nèi)存及虛擬內(nèi)存定義

    物理內(nèi)存是相對(duì)于虛擬內(nèi)存而言的。物理內(nèi)存指通過(guò)物理內(nèi)存條而獲得的內(nèi)存空間,而虛擬內(nèi)存則是指將硬盤的一塊區(qū)域劃分來(lái)作為內(nèi)存。內(nèi)存主要作用是在計(jì)算機(jī)運(yùn)行時(shí)為操作系統(tǒng)和各種程序提供臨時(shí)儲(chǔ)存。

  • 為什么要有虛擬內(nèi)存

    在早期的計(jì)算機(jī)中,要運(yùn)行一個(gè)程序,會(huì)把這些程序全都裝入內(nèi)存,程序都是直接運(yùn)行在內(nèi)存上的,也就是說(shuō)程序中訪問(wèn)的內(nèi)存地址都是實(shí)際的物理內(nèi)存地址。當(dāng)計(jì)算機(jī)同時(shí)運(yùn)行多個(gè)程序時(shí),必須保證這些程序用到的內(nèi)存總量要小于計(jì)算機(jī)實(shí)際物理內(nèi)存的大小。 早期內(nèi)存分配方法實(shí)例:

    某臺(tái)計(jì)算機(jī)總的內(nèi)存大小是 128M ,現(xiàn)在同時(shí)運(yùn)行兩個(gè)程序 A 和 B , A 需占用內(nèi)存 10M , B 需占用內(nèi)存 110 。計(jì)算機(jī)在給程序分配內(nèi)存時(shí)會(huì)采取這樣的方法:先將內(nèi)存中的前 10M 分配給程序 A ,接著再?gòu)膬?nèi)存中剩余的 118M 中劃分出 110M 分配給程序 B 。這種分配方法可以保證程序 A 和程序 B 都能運(yùn)行,但是這種簡(jiǎn)單的內(nèi)存分配策略問(wèn)題很多。如下圖:

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/早期內(nèi)存分配截圖.png” alt=“早期內(nèi)存分配截圖” style=“zoom:80%;” />

    早期內(nèi)存分配方法

    早期的內(nèi)存分配方法存在如下幾個(gè)問(wèn)題**(為什么要有虛擬內(nèi)存的原因)**:

    問(wèn)題 1 :進(jìn)程地址空間不隔離。由于程序都是直接訪問(wèn)物理內(nèi)存,所以惡意程序可以隨意修改別的進(jìn)程的內(nèi)存數(shù)據(jù),以達(dá)到破壞的目的。有些非惡意的,但是有 bug 的程序也可能不小心修改了其它程序的內(nèi)存數(shù)據(jù),就會(huì)導(dǎo)致其它程序的運(yùn)行出現(xiàn)異常。這種情況對(duì)用戶來(lái)說(shuō)是無(wú)法容忍的,因?yàn)橛脩粝M褂糜?jì)算機(jī)的時(shí)候,其中一個(gè)任務(wù)失敗了,至少不能影響其它的任務(wù)。

    問(wèn)題 2 :內(nèi)存使用效率低。在 A 和 B 都運(yùn)行的情況下,如果用戶又運(yùn)行了程序 C,而程序 C 需要 20M 大小的內(nèi)存才能運(yùn)行,而此時(shí)系統(tǒng)只剩下 8M 的空間可供使用,所以此時(shí)系統(tǒng)必須在已運(yùn)行的程序中選擇一個(gè)將該程序的數(shù)據(jù)暫時(shí)拷貝到硬盤上,釋放出部分空間來(lái)供程序 C 使用,然后再將程序 C 的數(shù)據(jù)全部裝入內(nèi)存中運(yùn)行??梢韵胂蟮玫?#xff0c;在這個(gè)過(guò)程中,有大量的數(shù)據(jù)在裝入裝出,導(dǎo)致效率十分低下。

    問(wèn)題 3 :程序運(yùn)行的地址不確定。當(dāng)內(nèi)存中的剩余空間可以滿足程序 C 的要求后,操作系統(tǒng)會(huì)在剩余空間中隨機(jī)分配一段連續(xù)的 20M 大小的空間給程序 C 使用,因?yàn)槭请S機(jī)分配的,所以程序運(yùn)行的地址是不確定的。

  • 虛擬內(nèi)存的實(shí)現(xiàn)(可以在頁(yè)式或段式內(nèi)存管理的基礎(chǔ)上實(shí)現(xiàn))

    (1)在裝入程序時(shí),不必將其全部裝入到內(nèi)存,而只需將當(dāng)前要執(zhí)行的部分頁(yè)面或段裝入到內(nèi)存,就可讓程序開始執(zhí)行;

    (2)在程序執(zhí)行過(guò)程中,如果需執(zhí)行的指令或訪的數(shù)據(jù)尚未在內(nèi)存(稱為缺頁(yè)或缺段),則由處理器通知操作系線將相應(yīng)的頁(yè)面或段調(diào)入到內(nèi)存,然后繼續(xù)執(zhí)訂程序;

    (3)另一方面,操作系統(tǒng)將內(nèi)存中暫時(shí)不用的頁(yè)面或段調(diào)出保存在外存上,從而騰出更多空困空間存放將要裝入的程字以及將要調(diào)入的頁(yè)畫或段。

    **虛擬技術(shù)基本特征:**大的用戶空間(物理內(nèi)存和外存相結(jié)合形成虛擬空間)、部分交換(調(diào)入和調(diào)出是對(duì)部分虛擬地址空間進(jìn)行的)、不連續(xù)性(物理內(nèi)存分配的不連續(xù),虛擬地址空間使用的不連續(xù))。

  • 答案解析

  • 分段

    為了解決早期內(nèi)存分配方式帶來(lái)的問(wèn)題,人們想到了一種變通的方法,就是增加一個(gè)中間層,利用一種間接的地址訪問(wèn)方法訪問(wèn)物理內(nèi)存。按照這種方法,程序中訪問(wèn)的內(nèi)存地址不再是實(shí)際的物理內(nèi)存地址,而是一個(gè)虛擬地址,然后由操作系統(tǒng)將這個(gè)虛擬地址映射到適當(dāng)?shù)奈锢韮?nèi)存地址上。這樣,只要操作系統(tǒng)處理好虛擬地址到物理內(nèi)存地址的映射,就可以保證不同的程序最終訪問(wèn)的內(nèi)存地址位于不同的區(qū)域,彼此沒(méi)有重疊,就可以達(dá)到內(nèi)存地址空間隔離的效果。

    當(dāng)創(chuàng)建一個(gè)進(jìn)程時(shí),操作系統(tǒng)會(huì)為該進(jìn)程分配一個(gè) 4GB 大小的虛擬進(jìn)程地址空間。之所以是 4GB ,是因?yàn)樵?32 位的操作系統(tǒng)中,一個(gè)指針長(zhǎng)度是 4 字節(jié),而 4 字節(jié)指針的尋址能力是從 0x000000000xFFFFFFFF,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對(duì)的,還有一個(gè)物理地址空間,這個(gè)地址空間對(duì)應(yīng)的是真實(shí)的物理內(nèi)存。如果你的計(jì)算機(jī)上安裝了 512M 大小的內(nèi)存,那么這個(gè)物理地址空間表示的范圍是 0x000000000x1FFFFFFF 。當(dāng)操作系統(tǒng)做虛擬地址到物理地址映射時(shí),只能映射到這一范圍,操作系統(tǒng)也只會(huì)映射到這一范圍。當(dāng)進(jìn)程創(chuàng)建時(shí),每個(gè)進(jìn)程都會(huì)有一個(gè)自己的 4GB 虛擬地址空間。要注意的是這個(gè) 4GB 的地址空間是“虛擬”的,并不是真實(shí)存在的,而且每個(gè)進(jìn)程只能訪問(wèn)自己虛擬地址空間中的數(shù)據(jù),無(wú)法訪問(wèn)別的進(jìn)程中的數(shù)據(jù),通過(guò)這種方法實(shí)現(xiàn)了進(jìn)程間的地址隔離。那是不是這 4GB 的虛擬地址空間應(yīng)用程序可以隨意使用呢?很遺憾,在 Windows 系統(tǒng)下,這個(gè)虛擬地址空間被分成了 4 部分: NULL 指針區(qū)、用戶區(qū)、 64KB 禁入?yún)^(qū)、內(nèi)核區(qū)。

    (1)NULL指針區(qū) (0x00000000~0x0000FFFF): 如果進(jìn)程中的一個(gè)線程試圖操作這個(gè)分區(qū)中的數(shù)據(jù),CPU就會(huì)引發(fā)非法訪問(wèn)。他的作用是,調(diào)用 malloc 等內(nèi)存分配函數(shù)時(shí),如果無(wú)法找到足夠的內(nèi)存空間,它將返回 NULL。而不進(jìn)行安全性檢查。它只是假設(shè)地址分配成功,并開始訪問(wèn)內(nèi)存地址 0x00000000(NULL)。由于禁止訪問(wèn)內(nèi)存的這個(gè)分區(qū),因此會(huì)發(fā)生非法訪問(wèn)現(xiàn)象,并終止這個(gè)進(jìn)程的運(yùn)行。

    (2)用戶模式分區(qū) ( 0x00010000~0xBFFEFFFF):這個(gè)分區(qū)中存放進(jìn)程的私有地址空間。一個(gè)進(jìn)程無(wú)法以任何方式訪問(wèn)另外一個(gè)進(jìn)程駐留在這個(gè)分區(qū)中的數(shù)據(jù) (相同 exe,通過(guò) copy-on-write 來(lái)完成地址隔離)。(在windows中,所有 .exe 和動(dòng)態(tài)鏈接庫(kù)都載入到這一區(qū)域。系統(tǒng)同時(shí)會(huì)把該進(jìn)程可以訪問(wèn)的所有內(nèi)存映射文件映射到這一分區(qū))。

    (3)隔離區(qū) (0xBFFF0000~0xBFFFFFFF):這個(gè)分區(qū)禁止進(jìn)入。任何試圖訪問(wèn)這個(gè)內(nèi)存分區(qū)的操作都是違規(guī)的。微軟保留這塊分區(qū)的目的是為了簡(jiǎn)化操作系統(tǒng)的現(xiàn)實(shí)。

    (4)內(nèi)核區(qū) (0xC0000000~0xFFFFFFFF):這個(gè)分區(qū)存放操作系統(tǒng)駐留的代碼。線程調(diào)度、內(nèi)存管理、文件系統(tǒng)支持、網(wǎng)絡(luò)支持和所有設(shè)備驅(qū)動(dòng)程序代碼都在這個(gè)分區(qū)加載。這個(gè)分區(qū)被所有進(jìn)程共享。

    應(yīng)用程序能使用的只是用戶區(qū)而已,大約 2GB 左右 ( 最大可以調(diào)整到 3GB) 。內(nèi)核區(qū)為 2GB ,內(nèi)核區(qū)保存的是系統(tǒng)線程調(diào)度、內(nèi)存管理、設(shè)備驅(qū)動(dòng)等數(shù)據(jù),這部分?jǐn)?shù)據(jù)供所有的進(jìn)程共享,但應(yīng)用程序是不能直接訪問(wèn)的。

    **人們之所以要?jiǎng)?chuàng)建一個(gè)虛擬地址空間,目的是為了解決進(jìn)程地址空間隔離的問(wèn)題。**但程序要想執(zhí)行,必須運(yùn)行在真實(shí)的內(nèi)存上,所以,必須在虛擬地址與物理地址間建立一種映射關(guān)系。這樣,通過(guò)映射機(jī)制,當(dāng)程序訪問(wèn)虛擬地址空間上的某個(gè)地址值時(shí),就相當(dāng)于訪問(wèn)了物理地址空間中的另一個(gè)值。人們想到了一種分段(Sagmentation) 的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說(shuō)虛擬地址空間中某個(gè) 10M 大小的空間映射到物理地址空間中某個(gè) 10M 大小的空間。這種思想理解起來(lái)并不難,操作系統(tǒng)保證不同進(jìn)程的地址空間被映射到物理地址空間中不同的區(qū)域上,這樣每個(gè)進(jìn)程最終訪問(wèn)到的。

    物理地址空間都是彼此分開的。通過(guò)這種方式,就實(shí)現(xiàn)了進(jìn)程間的地址隔離。還是以實(shí)例說(shuō)明,假設(shè)有兩個(gè)進(jìn)程 A 和 B ,進(jìn)程 A 所需內(nèi)存大小為 10M ,其虛擬地址空間分布在 0x00000000 到 0x00A00000 ,進(jìn)程 B 所需內(nèi)存為 100M ,其虛擬地址空間分布為 0x00000000 到 0x06400000 。那么按照分段的映射方法,進(jìn)程 A 在物理內(nèi)存上映射區(qū)域?yàn)?0x00100000 到 0x00B00000 ,,進(jìn)程 B 在物理內(nèi)存上映射區(qū)域?yàn)?x00C00000 到 0x07000000 。于是進(jìn)程 A 和進(jìn)程 B 分別被映射到了不同的內(nèi)存區(qū)間,彼此互不重疊,實(shí)現(xiàn)了地址隔離。從應(yīng)用程序的角度看來(lái),進(jìn)程 A 的地址空間就是分布在 0x00000000 到 0x00A00000 ,在做開發(fā)時(shí),開發(fā)人員只需訪問(wèn)這段區(qū)間上的地址即可。應(yīng)用程序并不關(guān)心進(jìn)程 A 究竟被映射到物理內(nèi)存的那塊區(qū)域上了,所以程序的運(yùn)行地址也就是相當(dāng)于說(shuō)是確定的了。 下圖顯示的是分段方式的內(nèi)存映射方法:

  • 分段方式的內(nèi)存映射方法

    這種分段的映射方法雖然解決了上述中的問(wèn)題1和問(wèn)題3,但并沒(méi)能解決問(wèn)題2,即內(nèi)存的使用效率問(wèn)題。在分段的映射方法中,每次換入換出內(nèi)存的都是整個(gè)程序, 這樣會(huì)造成大量的磁盤訪問(wèn)操作,導(dǎo)致效率低下。所以這種映射方法還是稍顯粗糙,粒度比較大。實(shí)際上,程序的運(yùn)行有局部性特點(diǎn),在某個(gè)時(shí)間段內(nèi),程序只是訪問(wèn)程序的一小部分?jǐn)?shù)據(jù),也就是說(shuō),程序的大部分?jǐn)?shù)據(jù)在一個(gè)時(shí)間段內(nèi)都不會(huì)被用到。基于這種情況,人們想到了粒度更小的內(nèi)存分割和映射方法,這種方法就是分頁(yè) (Paging) 。

  • 分頁(yè)

    分頁(yè)的基本方法是,將地址空間分成許多的頁(yè)。每頁(yè)的大小由 CPU 決定,然后由操作系統(tǒng)選擇頁(yè)的大小。目前 Inter 系列的 CPU 支持 4KB 或 4MB 的頁(yè)大小,而 PC上目前都選擇使用 4KB 。按這種選擇, 4GB 虛擬地址空間共可以分成 1048576 頁(yè), 512M 的物理內(nèi)存可以分為 131072 個(gè)頁(yè)。顯然虛擬空間的頁(yè)數(shù)要比物理空間的頁(yè)數(shù)多得多。

    在分段的方法中,每次程序運(yùn)行時(shí)總是把程序全部裝入內(nèi)存,而分頁(yè)的方法則有所不同。分頁(yè)的思想是程序運(yùn)行時(shí)用到哪頁(yè)就為哪頁(yè)分配內(nèi)存,沒(méi)用到的頁(yè)暫時(shí)保留在硬盤上。當(dāng)用到這些頁(yè)時(shí)再在物理地址空間中為這些頁(yè)分配內(nèi)存,然后建立虛擬地址空間中的頁(yè)和剛分配的物理內(nèi)存頁(yè)間的映射。下面通過(guò)介紹一個(gè)可執(zhí)行文件的裝載過(guò)程來(lái)說(shuō)明分頁(yè)機(jī)制的實(shí)現(xiàn)方法。

    一個(gè)可執(zhí)行文件 (PE 文件 ) 其實(shí)就是一些編譯鏈接好的數(shù)據(jù)和指令的集合,它也會(huì)被分成很多頁(yè),在 PE 文件執(zhí)行的過(guò)程中,它往內(nèi)存中裝載的單位就是頁(yè)。當(dāng)一個(gè) PE 文件被執(zhí)行時(shí),操作系統(tǒng)會(huì)先為該程序創(chuàng)建一個(gè) 4GB 的進(jìn)程虛擬地址空間。前面介紹過(guò),虛擬地址空間只是一個(gè)中間層而已,它的功能是利用一種映射機(jī)制將虛擬地址空間映射到物理地址空間,所以,創(chuàng)建 4GB 虛擬地址空間其實(shí)并不是要真的創(chuàng)建空間,只是要?jiǎng)?chuàng)建那種映射機(jī)制所需要的數(shù)據(jù)結(jié)構(gòu)而已,這種數(shù)據(jù)結(jié)構(gòu)就是頁(yè)目和頁(yè)表。

    當(dāng)創(chuàng)建完虛擬地址空間所需要的數(shù)據(jù)結(jié)構(gòu)后,進(jìn)程開始讀取 PE 文件的第一頁(yè)。在PE 文件的第一頁(yè)包含了 PE 文件頭和段表等信息,進(jìn)程根據(jù)文件頭和段表等信息,將 PE 文件中所有的段一一映射到虛擬地址空間中相應(yīng)的頁(yè) (PE 文件中的段的長(zhǎng)度都是頁(yè)長(zhǎng)的整數(shù)倍 ) 。這時(shí) PE 文件的真正指令和數(shù)據(jù)還沒(méi)有被裝入內(nèi)存中,操作系統(tǒng)只是據(jù) PE 文件的頭部等信息建立了 PE 文件和進(jìn)程虛擬地址空間中頁(yè)的映射關(guān)系而已。當(dāng) CPU 要訪問(wèn)程序中用到的某個(gè)虛擬地址時(shí),當(dāng) CPU 發(fā)現(xiàn)該地址并沒(méi)有相相關(guān)聯(lián)的物理地址時(shí), CPU 認(rèn)為該虛擬地址所在的頁(yè)面是個(gè)空頁(yè)面, CPU 會(huì)認(rèn)為這是個(gè)頁(yè)錯(cuò)誤 (Page Fault) , CPU 也就知道了操作系統(tǒng)還未給該 PE 頁(yè)面分配內(nèi)存,CPU 會(huì)將控制權(quán)交還給操作系統(tǒng)。操作系統(tǒng)于是為該 PE 頁(yè)面在物理空間中分配一個(gè)頁(yè)面,然后再將這個(gè)物理頁(yè)面與虛擬空間中的虛擬頁(yè)面映射起來(lái),然后將控制權(quán)再還給進(jìn)程,進(jìn)程從剛才發(fā)生頁(yè)錯(cuò)誤的位置重新開始執(zhí)行。由于此時(shí)已為 PE 文件的那個(gè)頁(yè)面分配了內(nèi)存,所以就不會(huì)發(fā)生頁(yè)錯(cuò)誤了。隨著程序的執(zhí)行,頁(yè)錯(cuò)誤會(huì)不斷地產(chǎn)生,操作系統(tǒng)也會(huì)為進(jìn)程分配相應(yīng)的物理頁(yè)面來(lái)滿足進(jìn)程執(zhí)行的需求。

    分頁(yè)方法的核心思想就是當(dāng)可執(zhí)行文件執(zhí)行到第 x 頁(yè)時(shí),就為第 x 頁(yè)分配一個(gè)內(nèi)存頁(yè) y ,然后再將這個(gè)內(nèi)存頁(yè)添加到進(jìn)程虛擬地址空間的映射表中 , 這個(gè)映射表就相當(dāng)于一個(gè) y=f(x) 函數(shù)。應(yīng)用程序通過(guò)這個(gè)映射表就可以訪問(wèn)到 x 頁(yè)關(guān)聯(lián)的 y 頁(yè)了。

  • 1.53 內(nèi)存和緩存有什么區(qū)別?

    參考回答

    內(nèi)存和緩存是計(jì)算機(jī)不同的組成部件。

  • 內(nèi)存特性

    內(nèi)存也被稱作內(nèi)存儲(chǔ)器,其作用是用于暫時(shí)存放CPU的運(yùn)算數(shù)據(jù),以及與硬盤等外部存儲(chǔ)交換的數(shù)據(jù)。只要計(jì)算機(jī)在運(yùn)行中,CPU就會(huì)把需要進(jìn)行運(yùn)算的數(shù)據(jù)調(diào)到內(nèi)存中進(jìn)行運(yùn)算,當(dāng)運(yùn)算完成后CPU再將結(jié)果傳送出來(lái),內(nèi)存的運(yùn)行也決定了計(jì)算機(jī)的穩(wěn)定運(yùn)行。

  • 緩存特性

    CPU芯片面積和成本的因素影響,決定了緩存都很小?,F(xiàn)在一般的緩存不過(guò)幾M,CPU緩存的運(yùn)行頻率極高,一般是和處理器同頻運(yùn)作,工作效率遠(yuǎn)遠(yuǎn)大于系統(tǒng)內(nèi)存和硬盤。實(shí)際工作時(shí),CPU往往需要重復(fù)讀取讀取同樣的數(shù)據(jù)塊,而緩存容量的增大,可以大幅度提升CPU內(nèi)部讀取數(shù)據(jù)的命中率,而不用再到內(nèi)存或者硬盤上尋找,以此提高系統(tǒng)性能。

  • 1.54 請(qǐng)你說(shuō)說(shuō)緩存溢出。

    參考回答

  • 緩存溢出及其危害
  • 緩存溢出是指輸入到一個(gè)緩沖區(qū)或者數(shù)據(jù)保存區(qū)域的數(shù)據(jù)量超過(guò)了其容量,從而導(dǎo)致覆蓋了其它區(qū)域數(shù)據(jù)的狀況。攻擊者造成并利用這種狀況使系統(tǒng)崩潰或者通過(guò)插入特制的代碼來(lái)控制系統(tǒng)。被覆蓋的區(qū)域可能存有其它程序的變量、參數(shù)、類似于返回地址或指向前一個(gè)棧幀的指針等程序控制流數(shù)據(jù)。緩沖區(qū)可以位于堆、棧或進(jìn)程的數(shù)據(jù)段。這種錯(cuò)誤可能產(chǎn)生如下后果:

    (1)破壞程序的數(shù)據(jù);

    (2)改變程序的控制流,因此可能訪問(wèn)特權(quán)代碼。

    最終很有可能造成程序終止。當(dāng)攻擊者成功地攻擊了一個(gè)系統(tǒng)之后,作為攻擊的一部分,程序的控制流可能會(huì)跳轉(zhuǎn)到攻擊者選擇的代碼處,造成的結(jié)果是被攻擊的進(jìn)程可以執(zhí)行任意的特權(quán)代碼(比如通過(guò)判斷輸入是否和密碼匹配來(lái)訪問(wèn)特權(quán)代碼,如果存在緩沖區(qū)漏洞,非法輸入導(dǎo)致存放“密碼”的內(nèi)存區(qū)被覆蓋,從而使得“密碼”被改寫,因此判斷為匹配進(jìn)而獲得了特權(quán)代碼的訪問(wèn)權(quán))

    緩沖區(qū)溢出攻擊是最普遍和最具危害性的計(jì)算機(jī)安全攻擊類型之一。

  • 如何預(yù)防緩存溢出

    廣義上分為兩類

    (1)編譯時(shí)防御系統(tǒng),目的是強(qiáng)化系統(tǒng)以抵御潛伏于新程序中的惡意攻擊

    (2)運(yùn)行時(shí)預(yù)防系統(tǒng),目的是檢測(cè)并終止現(xiàn)有程序中的惡意攻擊

  • 1.55 深拷貝和淺拷貝的區(qū)別是什么,它們各自的使用場(chǎng)景是什么?

    參考回答

    淺拷貝只是對(duì)指針的拷貝,拷貝后兩個(gè)指針指向同一個(gè)內(nèi)存空間;深拷貝不斷對(duì)指針進(jìn)行拷貝,而且對(duì)指針指向的內(nèi)容進(jìn)行拷貝,經(jīng)深拷貝后的指針是指向兩個(gè)不同的地址空間。

  • 淺拷貝

    對(duì)一個(gè)已知對(duì)象進(jìn)行拷貝時(shí),編譯系統(tǒng)會(huì)自動(dòng)調(diào)用一次構(gòu)造函數(shù)(拷貝構(gòu)造函數(shù)),如果用戶未定義拷貝構(gòu)造函數(shù),則會(huì)調(diào)用默認(rèn)拷貝構(gòu)造函數(shù),調(diào)用一次構(gòu)造函數(shù),調(diào)用兩次析構(gòu)函數(shù),兩個(gè)對(duì)象的指針成員所指內(nèi)存相同,但是程序結(jié)束時(shí)該內(nèi)存被釋放了兩次,會(huì)造成內(nèi)存泄漏問(wèn)題。

  • 深拷貝

    在對(duì)含有指針成員的對(duì)象進(jìn)行拷貝時(shí),必須要自己定義拷貝構(gòu)造函數(shù),使拷貝后的對(duì)象指針成員有自己的內(nèi)存空間,即進(jìn)行深拷貝,這樣就避免了內(nèi)存泄漏的發(fā)生,調(diào)用一次構(gòu)造函數(shù),一次自定義拷貝構(gòu)造函數(shù),兩次析構(gòu)函數(shù)。兩個(gè)對(duì)象的指針成員所指內(nèi)容不同。

  • 1.56 說(shuō)說(shuō)IO模型。

    參考回答

  • 什么是IO

    我們都知道unix世界里,一切皆文件。而文件是什么呢?文件就是一串二進(jìn)制流而已。無(wú)論是socket,還是FIFO、管道、終端,對(duì)我們來(lái)說(shuō),一切都是文件,一切都是流。在信息交換的過(guò)程中,我們都是對(duì)這些流進(jìn)行數(shù)據(jù)的收發(fā)操作簡(jiǎn)稱為I/O操作(input and output)。往流中讀出數(shù)據(jù),系統(tǒng)調(diào)用read;寫入數(shù)據(jù),系統(tǒng)調(diào)用write。

    計(jì)算機(jī)里有這么多的流,我怎么知道要操作哪個(gè)流呢?

    做到這個(gè)的就是文件描述符,即通常所說(shuō)的fd,一個(gè)fd就是一個(gè)整數(shù),所以對(duì)這個(gè)整數(shù)的操作就是對(duì)這個(gè)文件(流)的操作。我們創(chuàng)建一個(gè)socket,通過(guò)系統(tǒng)調(diào)用會(huì)返回一個(gè)文件描述符,那么剩下對(duì)socket的操作就會(huì)轉(zhuǎn)化為對(duì)這個(gè)描述符的操作。不能不說(shuō)這又是一種分層和抽象的思想。

  • IO交互

    對(duì)于一個(gè)輸入操作來(lái)說(shuō),進(jìn)程IO系統(tǒng)調(diào)用后,內(nèi)核會(huì)先看緩沖區(qū)有沒(méi)有相應(yīng)的緩存數(shù)據(jù),沒(méi)有的話再到設(shè)備中讀取,因?yàn)樵O(shè)備IO一般速度較慢,需要等待,內(nèi)核緩沖區(qū)有數(shù)據(jù)則直接復(fù)制到進(jìn)程空間。所以,對(duì)于一個(gè)網(wǎng)絡(luò)輸入操作通常包括兩個(gè)不同階段

    (1)等待網(wǎng)絡(luò)數(shù)據(jù)到達(dá)網(wǎng)卡->讀取到內(nèi)核緩沖區(qū)

    (2)從內(nèi)核緩沖區(qū)復(fù)制數(shù)據(jù)->用戶空間

    IO有內(nèi)存IO、網(wǎng)絡(luò)IO和磁盤IO三種,通常我們所說(shuō)的IO指的是網(wǎng)絡(luò)IO磁盤IO兩者。

  • 五大I/O模型

    Linux有五大I/O模型,分別為阻塞IO、同步非阻塞IO、IO多路復(fù)用、信號(hào)驅(qū)動(dòng)IO、異步IO。五種IO模型特性分別如下:

    (1)阻塞IO(blocking IO)

    最傳統(tǒng)的一種IO模型,即在讀寫數(shù)據(jù)過(guò)程中會(huì)發(fā)生阻塞現(xiàn)象。

    當(dāng)用戶線程發(fā)出IO請(qǐng)求之后,內(nèi)核會(huì)去查看數(shù)據(jù)是否就緒,如果沒(méi)有就緒就會(huì)等待數(shù)據(jù)就緒,而用戶線程就會(huì)處于阻塞狀態(tài),用戶線程交出CPU。當(dāng)數(shù)據(jù)就緒之后,內(nèi)核會(huì)將數(shù)據(jù)拷貝到用戶線程,并返回結(jié)果給用戶線程,用戶線程才解除block狀態(tài)。

    典型的阻塞IO模型的例子為:

    data = socket.read();

    如果數(shù)據(jù)沒(méi)有就緒,就會(huì)一直阻塞在read方法。

  • <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/阻塞IO模型.png” alt=“阻塞IO模型” style=“zoom:80%;” />

    阻塞I/O模型

    (2)同步非阻塞IO(nonblocking IO)

    當(dāng)用戶線程發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。如果結(jié)果是一個(gè)error時(shí),它就知道數(shù)據(jù)還沒(méi)有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦內(nèi)核中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶線程的請(qǐng)求,那么它馬上就將數(shù)據(jù)拷貝到了用戶線程,然后返回。

    所以事實(shí)上,在非阻塞IO模型中,用戶線程需要不斷地詢問(wèn)內(nèi)核數(shù)據(jù)是否就緒,也就說(shuō)非阻塞IO不會(huì)交出CPU,而會(huì)一直占用CPU。

    典型的非阻塞IO模型一般如下:

    while(true){ data = socket.read();if(data!= error){處理數(shù)據(jù)break;} }

    但是對(duì)于非阻塞IO就有一個(gè)非常嚴(yán)重的問(wèn)題,在while循環(huán)中需要不斷地去詢問(wèn)內(nèi)核數(shù)據(jù)是否就緒,這樣會(huì)導(dǎo)致CPU占用率非常高,因此一般情況下很少使用while循環(huán)這種方式來(lái)讀取數(shù)據(jù)。

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/非阻塞IO模型.png” alt=“非阻塞IO模型” style=“zoom:80%;” />

    同步非阻塞I/O模型

    (3)IO多路復(fù)用(IO multiplexing)

    多路復(fù)用IO模型是目前使用得比較多的模型。Java NIO實(shí)際上就是多路復(fù)用IO。

    在多路復(fù)用IO模型中,會(huì)有一個(gè)線程不斷去輪詢多個(gè)socket的狀態(tài),只有當(dāng)socket真正有讀寫事件時(shí),才真正調(diào)用實(shí)際的IO讀寫操作。因?yàn)樵诙嗦窂?fù)用IO模型中,只需要使用一個(gè)線程就可以管理多個(gè)socket,系統(tǒng)不需要建立新的進(jìn)程或者線程,也不必維護(hù)這些線 程和 進(jìn)程,并且只有在真正有socket讀寫事件進(jìn)行時(shí),才會(huì)使用IO資源,所以它大大減少了資源占用。

    在Java NIO中,是通過(guò)selector.select()去查詢每個(gè)通道是否有到達(dá)事件,如果沒(méi)有事件,則一直阻塞在那里,因此這種方式會(huì)導(dǎo)致用戶線程的阻塞。

    也許有朋友會(huì)說(shuō),我可以采用多線程+ 阻塞IO 達(dá)到類似的效果,但是由于在多線程 + 阻塞IO 中,每個(gè)socket對(duì)應(yīng)一個(gè)線程,這樣會(huì)造成很大的資源占用,并且尤其是對(duì)于長(zhǎng)連接來(lái)說(shuō),線程的資源一直不會(huì)釋放,如果后面陸續(xù)有很多連接的話,就會(huì)造成性能上的瓶頸。

    而多路復(fù)用IO模式,通過(guò)一個(gè)線程就可以管理多個(gè)socket,只有當(dāng)socket真正有讀寫事件發(fā)生才會(huì)占用資源來(lái)進(jìn)行實(shí)際的讀寫操作。因此,多路復(fù)用IO比較適合連接數(shù)比較多的情況。

    另外多路復(fù)用IO為何比非阻塞IO模型的效率高是因?yàn)樵诜亲枞鸌O中,不斷地詢問(wèn)socket狀態(tài)時(shí)通過(guò)用戶線程去進(jìn)行的,而在多路復(fù)用IO中,輪詢每個(gè)socket狀態(tài)是內(nèi)核在進(jìn)行的,這個(gè)效率要比用戶線程要高的多。

    **注意:**多路復(fù)用IO模型是通過(guò)輪詢的方式來(lái)檢測(cè)是否有事件到達(dá),并且對(duì)到達(dá)的事件逐一進(jìn)行響應(yīng)。因此對(duì)于多路復(fù)用IO模型來(lái)說(shuō),一旦事件響應(yīng)體很大,那么就會(huì)導(dǎo)致后續(xù)的事件遲遲得不到處理,并且會(huì)影響新的事件輪詢。

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/IO復(fù)用模型.png” alt=“IO復(fù)用模型” style=“zoom:80%;” />

    I/O多路復(fù)用模型

    (4)信號(hào)驅(qū)動(dòng)IO(signal driven IO)

    在信號(hào)驅(qū)動(dòng)IO模型中,當(dāng)用戶線程發(fā)起一個(gè)IO請(qǐng)求操作,會(huì)給對(duì)應(yīng)的socket注冊(cè)一個(gè)信號(hào)函數(shù),然后用戶線程會(huì)繼續(xù)執(zhí)行,當(dāng)內(nèi)核數(shù)據(jù)就緒時(shí)會(huì)發(fā)送一個(gè)信號(hào)給用戶線程,用戶線程接收到信號(hào)之后,便在信號(hào)函數(shù)中調(diào)用IO讀寫操作來(lái)進(jìn)行實(shí)際的IO請(qǐng)求操作。這個(gè)一般用于UDP中,對(duì)TCP套接口幾乎是沒(méi)用的,原因是該信號(hào)產(chǎn)生得過(guò)于頻繁,并且該信號(hào)的出現(xiàn)并沒(méi)有說(shuō)明發(fā)生了什么事情。

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/信號(hào)驅(qū)動(dòng)IO模型.png” alt=“信號(hào)驅(qū)動(dòng)IO模型” style=“zoom:80%;” />

    信號(hào)驅(qū)動(dòng)I/O模型

    (5)異步IO(asynchronous IO)

    異步IO模型才是最理想的IO模型,在異步IO模型中,當(dāng)用戶線程發(fā)起read操作之后,立刻就可以開始去做其它的事。而另一方面,從內(nèi)核的角度,當(dāng)它收到一個(gè)asynchronous read之后,它會(huì)立刻返回,說(shuō)明read請(qǐng)求已經(jīng)成功發(fā)起了,因此不會(huì)對(duì)用戶線程產(chǎn)生任何阻塞。然后,內(nèi)核會(huì)等待數(shù)據(jù)準(zhǔn)備完成,再將數(shù)據(jù)拷貝到用戶線程,當(dāng)這一切都完成之后,內(nèi)核會(huì)給用戶線程發(fā)送一個(gè)信號(hào),告訴它read操作完成了。也就說(shuō)用戶線程完全不需要關(guān)心實(shí)際的整個(gè)IO操作是如何進(jìn)行的,只需要先發(fā)起一個(gè)請(qǐng)求,當(dāng)接收內(nèi)核返回的成功信號(hào)時(shí)表示IO操作已經(jīng)完成,可以直接去使用數(shù)據(jù)了。

    也就說(shuō)在異步IO模型中,IO操作的兩個(gè)階段都不會(huì)阻塞用戶線程,這兩個(gè)階段都是由內(nèi)核自動(dòng)完成,然后發(fā)送一個(gè)信號(hào)告知用戶線程操作已完成。用戶線程中不需要再次調(diào)用IO函數(shù)進(jìn)行具體的讀寫。這點(diǎn)是和信號(hào)驅(qū)動(dòng)模型有所不同的,在信號(hào)驅(qū)動(dòng)模型中,當(dāng)用戶線程接收到信號(hào)表示數(shù)據(jù)已經(jīng)就緒,然后需要用戶線程調(diào)用IO函數(shù)進(jìn)行實(shí)際的讀寫操作;而在異步IO模型中,收到信號(hào)表示IO操作已經(jīng)完成,不需要再在用戶線程中調(diào)用IO函數(shù)進(jìn)行實(shí)際的讀寫操作。

    **注意:**異步IO是需要操作系統(tǒng)的底層支持,在Java 7中,提供了Asynchronous IO(簡(jiǎn)稱AIO)。

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/異步IO模型.png” alt=“異步IO模型” style=“zoom:80%;” />

    異步I/O模型

    前四種IO模型實(shí)際上都屬于同步IO,只有最后一種是真正的異步IO,因?yàn)闊o(wú)論是多路復(fù)用IO還是信號(hào)驅(qū)動(dòng)模型,IO操作的第2個(gè)階段都會(huì)引起用戶線程阻塞,也就是內(nèi)核進(jìn)行數(shù)據(jù)拷貝的過(guò)程都會(huì)讓用戶線程阻塞。

    1.57 Linux中的軟鏈接和硬鏈接有什么區(qū)別?

    參考回答

  • inode概念

    inode是文件系統(tǒng)中存儲(chǔ)文件元信息的區(qū)域,中文叫節(jié)點(diǎn)索引,每個(gè)節(jié)點(diǎn)索引包含了文件的創(chuàng)建者,大小,日期等等??梢酝ㄟ^(guò)ls -i file 命令查看inode的值。

  • 根據(jù) Linux 系統(tǒng)存儲(chǔ)文件的特點(diǎn),鏈接的方式分為軟鏈接和硬鏈接2 種

    軟鏈接相當(dāng)于建立了一個(gè)新的快捷方式文件,該文件有自己的名稱和inode以及物理存儲(chǔ)的文件數(shù)據(jù),文件數(shù)據(jù)里記錄著如何跳轉(zhuǎn)的設(shè)置數(shù)據(jù),訪問(wèn)該快捷文件會(huì)被重新定向到原始文件,刪除原始文件,軟鏈文件失效;硬鏈接相當(dāng)于為當(dāng)前文件名對(duì)應(yīng)的文件再建立了一個(gè)文件別名,別名對(duì)應(yīng)的inode以及物理數(shù)據(jù)都是一樣的,一旦建立,我們甚至根本無(wú)法區(qū)分誰(shuí)是原始文件的原始名稱,刪除文件的其中一個(gè)名稱,文件不會(huì)丟失,除非把所有的名稱都刪除。

    如下圖:

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/軟硬連接.png” alt=“軟硬連接” style=“zoom:100%;” />

    hard link(硬鏈) 和file 都指向同一個(gè) inode,inode對(duì)應(yīng)了一個(gè)實(shí)際物理存儲(chǔ)的文件。soft link(軟鏈) 對(duì)應(yīng)一個(gè)新的inode, 新的inode對(duì)應(yīng)一個(gè)新的物理存儲(chǔ)文件,物理存儲(chǔ)文件又指向了目標(biāo)文件 file。

  • 軟連接和硬鏈接的區(qū)別

    (1) 軟鏈接可以為文件和目錄(哪怕是不存在的)創(chuàng)建鏈接;硬鏈接只能為文件創(chuàng)建鏈接。
    (2) 軟鏈接可以跨文件系統(tǒng);硬鏈接必須是同一個(gè)文件系統(tǒng)
    (3) 硬鏈接因?yàn)橹皇俏募囊粋€(gè)別名,所以不重復(fù)占用內(nèi)存;軟鏈接因?yàn)橹皇且粋€(gè)訪問(wèn)文件的快捷方式文件,文件內(nèi)只包含快捷指向信息,所以占用很小的內(nèi)存。
    (4) 軟鏈接的文件權(quán)限和源文件可以不一樣;硬鏈接文件權(quán)限肯定是一樣的,因?yàn)樗麄儽緛?lái)就是一個(gè)文件的不同名稱而已。

  • 二者使用場(chǎng)景

    一般比較重要的文件我們擔(dān)心文件被誤刪除且傳統(tǒng)復(fù)制備份方式占用double數(shù)量的空間會(huì)造成浪費(fèi),可以使用硬鏈做備份來(lái)解決;軟鏈接一般被用來(lái)設(shè)置可執(zhí)行文件的快捷方式的路徑。

  • 答案解析

  • 創(chuàng)建硬鏈接

    [root@localhost ~]# touch cangls [root@localhost ~]# ln /root/cangls /tmp #建立硬鏈接文件,目標(biāo)文件沒(méi)有寫文件名,會(huì)和原名一致 #也就是/tmp/cangls 是硬鏈接文件
  • 創(chuàng)建軟鏈接

    [root@localhost ~]# touch bols [root@localhost ~]# In -s /root/bols /tmp #建立軟鏈接文件

    **注意:**軟鏈接文件的源文件必須寫成絕對(duì)路徑,而不能寫成相對(duì)路徑(硬鏈接沒(méi)有這樣的要求);否則軟鏈接文件會(huì)報(bào)錯(cuò)。

  • 1.58 說(shuō)說(shuō)缺頁(yè)中斷機(jī)制。

    參考回答

  • 缺頁(yè)中斷

    在請(qǐng)求分頁(yè)系統(tǒng)中,可以通過(guò)查詢頁(yè)表中的狀態(tài)位來(lái)確定所要訪問(wèn)的頁(yè)面是否存在于內(nèi)存中。當(dāng)所要訪問(wèn)的頁(yè)面不在內(nèi)存時(shí),會(huì)產(chǎn)生一次缺頁(yè)中斷,此時(shí)操作系統(tǒng)會(huì)根據(jù)頁(yè)表中的外存地址在外存中找到所缺的一頁(yè),將其調(diào)入內(nèi)存。
     缺頁(yè)中斷的處理流程如下:

    A. 在內(nèi)存中有空閑物理頁(yè)面時(shí),分配一物理頁(yè)幀f,轉(zhuǎn)第E步;

    B. 依據(jù)頁(yè)面置換算法選擇被替換的物理頁(yè)幀f,對(duì)應(yīng)邏輯頁(yè)q;

    C. 如果q被修改過(guò),則把它寫回外存;

    D. 修改q的頁(yè)表項(xiàng)中駐留位置為0;

    E. 將需要訪問(wèn)的頁(yè)p裝入到物理頁(yè)f;

    F. 修改p的頁(yè)表項(xiàng)駐留位為1,物理頁(yè)幀號(hào)為f;

    G. 重新執(zhí)行產(chǎn)生缺頁(yè)的指令。

  • 缺頁(yè)中斷與一般的中斷存在區(qū)別

    (1)范圍不同

  • 一般中斷只需要保護(hù)現(xiàn)場(chǎng)然后就直接跳到需及時(shí)處理的地方;

    缺頁(yè)中斷除了保護(hù)現(xiàn)場(chǎng)之外,還要判斷內(nèi)存中是否有足夠的空間存儲(chǔ)所需的頁(yè)或段,然后再把所需頁(yè)調(diào)進(jìn)來(lái)再使用。

    (2)結(jié)果不同

    一般中斷在處理完之后返回時(shí),執(zhí)行下一條指令;

    缺頁(yè)中斷返回時(shí),執(zhí)行產(chǎn)生中斷的那一條指令。

    (3)次數(shù)不同

    一般中斷只產(chǎn)生一次,發(fā)生中斷指令后轉(zhuǎn)入相應(yīng)處理程序進(jìn)行處理,恢復(fù)被中斷程序現(xiàn)場(chǎng);

    在指令執(zhí)行期間產(chǎn)生和處理缺頁(yè)中斷信號(hào),一條指令在執(zhí)行期間,可能產(chǎn)生多次缺頁(yè)中斷。

    答案解析

    產(chǎn)生缺頁(yè)中斷的幾種情況

  • 當(dāng)內(nèi)存管理單元(MMU)中確實(shí)沒(méi)有創(chuàng)建虛擬物理頁(yè)映射關(guān)系,并且在該虛擬地址之后再?zèng)]有當(dāng)前進(jìn)程的線性區(qū)(vma)的時(shí)候,這將殺掉該進(jìn)程;
  • 當(dāng)MMU中確實(shí)沒(méi)有創(chuàng)建虛擬頁(yè)物理頁(yè)映射關(guān)系,并且在該虛擬地址之后存在當(dāng)前進(jìn)程的線性區(qū)vma的時(shí)候,這很可能是缺頁(yè)中斷,并且可能是棧溢出導(dǎo)致的缺頁(yè)中斷;
  • 當(dāng)使用malloc/mmap等希望訪問(wèn)物理空間的庫(kù)函數(shù)/系統(tǒng)調(diào)用后,由于linux并未真正給新創(chuàng)建的vma映射物理頁(yè),此時(shí)若先進(jìn)行寫操作,將和2產(chǎn)生缺頁(yè)中斷的情況一樣;若先進(jìn)行讀操作雖然也會(huì)產(chǎn)生缺頁(yè)異常,將被映射給默認(rèn)的零頁(yè),等再進(jìn)行寫操作時(shí),仍會(huì)產(chǎn)生缺頁(yè)中斷,這次必須分配1物理頁(yè)了,進(jìn)入寫時(shí)復(fù)制的流程;
  • 當(dāng)使用fork等系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程時(shí),子進(jìn)程不論有無(wú)自己的vma,它的vma都有對(duì)于物理頁(yè)的映射,但它們共同映射的這些物理頁(yè)屬性為只讀,即linux并未給子進(jìn)程真正分配物理頁(yè),當(dāng)父子進(jìn)程任何一方要寫相應(yīng)物理頁(yè)時(shí),導(dǎo)致缺頁(yè)中斷的寫時(shí)復(fù)制。
  • 1.59 軟中斷和硬中斷有什么區(qū)別?

    參考回答

  • 硬中斷

    由與系統(tǒng)相連的外設(shè)(比如網(wǎng)卡、硬盤)自動(dòng)產(chǎn)生的。主要是用來(lái)通知操作系統(tǒng)外設(shè)狀態(tài)的變化。比如當(dāng)網(wǎng)卡收到數(shù)據(jù)包的時(shí)候,就會(huì)發(fā)出一個(gè)中斷。我們通常所說(shuō)的中斷指的是硬中斷(hardirq)。

  • 軟終端

    為了滿足實(shí)時(shí)系統(tǒng)的要求,中斷處理應(yīng)該是越快越好。Linux為了實(shí)現(xiàn)這個(gè)特點(diǎn),當(dāng)中斷發(fā)生的時(shí)候,硬中斷處理那些短時(shí)間就可以完成的工作,而將那些處理事件比較長(zhǎng)的工作,放到中斷之后來(lái)完成,也就是軟中斷(softirq)來(lái)完成。

  • 中斷嵌套

    Linux下硬中斷是可以嵌套的,但是沒(méi)有優(yōu)先級(jí)的概念,也就是說(shuō)任何一個(gè)新的中斷都可以打斷正在執(zhí)行的中斷,但同種中斷除外。軟中斷不能嵌套,但相同類型的軟中斷可以在不同CPU上并行執(zhí)行。

  • 軟中斷與硬中斷之間的區(qū)別

    (1)硬中斷是由外部事件引起的因此具有隨機(jī)性和突發(fā)性;軟中斷是執(zhí)行中斷指令產(chǎn)生的,無(wú)外面事件中斷請(qǐng)求信號(hào),因此軟中斷的發(fā)生不是隨機(jī)的而是由程序安排好的;

    (2)硬中斷的中斷號(hào)是由中斷控制器提供的;軟中斷的中斷號(hào)是由指令直接給出的,無(wú)需使用中斷控制器。

    (3)硬中斷的中斷響應(yīng)周期,CPU需要發(fā)中斷回合信號(hào);軟中斷的中斷響應(yīng)周期,CPU不需要發(fā)中斷回合信號(hào)。

    (4)硬中斷是可屏蔽的;軟中斷是不可屏蔽的。

  • 1.60 介紹一下你對(duì)CopyOnWrite的了解。

    參考回答

  • CopyOnWrite(寫時(shí)拷貝技術(shù))

    Linux的fork()使用寫時(shí)拷貝頁(yè)來(lái)實(shí)現(xiàn)新進(jìn)程的創(chuàng)建,它是一種可推遲甚至避免數(shù)據(jù)拷貝的技術(shù),開始時(shí)內(nèi)核并不會(huì)復(fù)制整個(gè)地址空間,而是讓父子進(jìn)程共享地址空間,只有在寫時(shí)才復(fù)制地址空間,使得父子進(jìn)程都擁有獨(dú)立的地址空間,即資源的復(fù)制是在只有需要寫入時(shí)才會(huì)發(fā)生。在此之前都是以讀的方式去和父進(jìn)程共享資源,這樣,在頁(yè)根本不會(huì)被寫入的場(chǎng)景下,fork()立即執(zhí)行exec(),無(wú)需對(duì)地址空間進(jìn)行復(fù)制,fork()的實(shí)際開銷就是復(fù)制父進(jìn)程的一個(gè)頁(yè)表和為子進(jìn)程創(chuàng)建一個(gè)進(jìn)程描述符,也就是說(shuō)只有當(dāng)進(jìn)程空間中各段的內(nèi)存內(nèi)容發(fā)生變化時(shí),父進(jìn)程才將其內(nèi)容復(fù)制一份傳給子進(jìn)程,大大提高了效率。

  • 寫時(shí)拷貝技術(shù)優(yōu)缺點(diǎn)

    寫時(shí)拷貝技術(shù)是一種很重要的優(yōu)化手段,核心是懶惰處理實(shí)體資源請(qǐng)求,在多個(gè)實(shí)體資源之間只是共享資源,起初是并不真正實(shí)現(xiàn)資源拷貝,只有當(dāng)實(shí)體有需要對(duì)資源進(jìn)行修改時(shí)才真正為實(shí)體分配私有資源。但寫時(shí)拷貝技術(shù)技術(shù)也有它的優(yōu)點(diǎn)和缺點(diǎn):

    **優(yōu)點(diǎn):**寫時(shí)拷貝技術(shù)可以減少分配和復(fù)制大量資源時(shí)帶來(lái)的瞬間延時(shí),但實(shí)際上是將這種延時(shí)附加到了后續(xù)的操作之中。

    **缺點(diǎn):**寫時(shí)拷貝技術(shù)可以減少不必要的資源分配。比如fork進(jìn)程時(shí),并不是所有的頁(yè)面都需要復(fù)制,父進(jìn)程的代碼段和只讀數(shù)據(jù)段都不被允許修改,所以無(wú)需復(fù)制。

  • 答案解析

    寫時(shí)復(fù)制技術(shù)詳述:

    現(xiàn)在有一個(gè)父進(jìn)程P1,這是一個(gè)主體,那么它是有靈魂也就身體的?,F(xiàn)在在其虛擬地址空間(有相應(yīng)的數(shù)據(jù)結(jié)構(gòu)表示)上有:正文段,數(shù)據(jù)段,堆,棧這四個(gè)部分,相應(yīng)的,內(nèi)核要為這四個(gè)部分分配各自的物理塊。即:正文段塊,數(shù)據(jù)段塊,堆塊,棧塊。至于如何分配,這是內(nèi)核去做的事,在此不詳述。

  • 現(xiàn)在P1用fork()函數(shù)為進(jìn)程創(chuàng)建一個(gè)子進(jìn)程P2,

    內(nèi)核:

    (1)復(fù)制P1的正文段,數(shù)據(jù)段,堆,棧這四個(gè)部分,注意是其內(nèi)容相同。

    (2)為這四個(gè)部分分配物理塊,P2的:正文段->PI的正文段的物理塊,其實(shí)就是不為P2分配正文段塊,讓P2的正文段指向P1的正文段塊,數(shù)據(jù)段->P2自己的數(shù)據(jù)段塊(為其分配對(duì)應(yīng)的塊),堆->P2自己的堆塊,棧->P2自己的棧塊。如下圖所示:從左到右大的方向箭頭表示復(fù)制內(nèi)容。

    <https://static.nowcoder.com/images/activity/2021jxy/java/img src=“https://static.nowcoder.com/images/activity/2021jxy/java/img/asset/寫時(shí)復(fù)制1.jpg” alt=“寫時(shí)復(fù)制1” style=“zoom:100%;” />

  • 寫時(shí)復(fù)制技術(shù):內(nèi)核只為新生成的子進(jìn)程創(chuàng)建虛擬空間結(jié)構(gòu),它們來(lái)復(fù)制于父進(jìn)程的虛擬究竟結(jié)構(gòu),但是不為這些段分配物理內(nèi)存,它們共享父進(jìn)程的物理空間,當(dāng)父子進(jìn)程中有更改相應(yīng)段的行為發(fā)生時(shí),再為子進(jìn)程相應(yīng)的段分配物理空間。

  • vfork():這個(gè)做法更加火爆,內(nèi)核連子進(jìn)程的虛擬地址空間結(jié)構(gòu)也不創(chuàng)建了,直接共享了父進(jìn)程的虛擬空間,當(dāng)然了,這種做法就順

    水推舟的共享了父進(jìn)程的物理空間!

  • 通過(guò)以上的分析,相信大家對(duì)進(jìn)程有個(gè)深入的認(rèn)識(shí),它是怎么一層層體現(xiàn)出自己來(lái)的,進(jìn)程是一個(gè)主體,那么它就有靈魂與身體,系統(tǒng)必須為實(shí)現(xiàn)它創(chuàng)建相應(yīng)的實(shí)體, 靈魂實(shí)體與物理實(shí)體。這兩者在系統(tǒng)中都有相應(yīng)的數(shù)據(jù)結(jié)構(gòu)表示,物理實(shí)體更是體現(xiàn)了它的物理意義。以下援引LKD

    傳統(tǒng)的fork()系統(tǒng)調(diào)用直接把所有的資源復(fù)制給新創(chuàng)建的進(jìn)程。這種實(shí)現(xiàn)過(guò)于簡(jiǎn)單并且效率低下,因?yàn)樗截惖臄?shù)據(jù)也許并不共享,更糟的情況是,如果新進(jìn)程打算立即執(zhí)行一個(gè)新的映像,那么所有的拷貝都將前功盡棄。Linux的fork()使用寫時(shí)拷貝(copy-on-write)頁(yè)實(shí)現(xiàn)。寫時(shí)拷貝是一種可以推遲甚至免除拷貝數(shù)據(jù)的技術(shù)。內(nèi)核此時(shí)并不復(fù)制整個(gè)進(jìn)程地址空間,而是讓父進(jìn)程和子進(jìn)程共享同一個(gè)拷貝。只有在需要寫入的時(shí)候,數(shù)據(jù)才會(huì)被復(fù)制,從而使各個(gè)進(jìn)程擁有各自的拷貝。也就是說(shuō),資源的復(fù)制只有在需要寫入的時(shí)候才進(jìn)行,在此之前,只是以只讀方式共享。這種技術(shù)使地址空間上的頁(yè)的拷貝被推遲到實(shí)際發(fā)生寫入的時(shí)候。在頁(yè)根本不會(huì)被寫入的情況下—舉例來(lái)說(shuō),fork()后立即調(diào)用exec()—它們就無(wú)需復(fù)制了。fork()的實(shí)際開銷就是復(fù)制父進(jìn)程的頁(yè)表以及給子進(jìn)程創(chuàng)建惟一的進(jìn)程描述符。在一般情況下,進(jìn)程創(chuàng)建后都會(huì)馬上運(yùn)行一個(gè)可執(zhí)行的文件,這種優(yōu)化可以避免拷貝大量根本就不會(huì)被使用的數(shù)據(jù)(地址空間里常常包含數(shù)十兆的數(shù)據(jù))。由于Unix強(qiáng)調(diào)進(jìn)程快速執(zhí)行的能力,所以這個(gè)優(yōu)化是很重要的。這里補(bǔ)充一點(diǎn):Linux COW與exec沒(méi)有必然聯(lián)系

    實(shí)際上COW技術(shù)不僅僅在Linux進(jìn)程上有應(yīng)用,其他例如C++的String在有的IDE環(huán)境下也支持COW技術(shù),即例如:

    string str1 = "hello world"; string str2 = str1;

    之后執(zhí)行代碼:

    str1[1]='q'; str2[1]='w';

    在開始的兩個(gè)語(yǔ)句后,str1和str2存放數(shù)據(jù)的地址是一樣的,而在修改內(nèi)容后,str1的地址發(fā)生了變化,而str2的地址還是原來(lái)的,這就是C++中的COW技術(shù)的應(yīng)用,不過(guò)VS2005似乎已經(jīng)不支持COW。

    1.61 Linux替換文本該如何操作呢?

    參考回答

  • 通過(guò)vi編輯器來(lái)替換
    vi/vim 中可以使用 😒 命令來(lái)替換字符串。
    😒/well/good/ 替換當(dāng)前行第一個(gè) well 為 good
    😒/well/good/g 替換當(dāng)前行所有 well 為 good
    :n,[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-VClxZXz9-1641561861422)(https://www.nowcoder.com/equation?tex=s%2Fwell%2Fgood%2F%20%E6%9B%BF%E6%8D%A2%E7%AC%AC%20n%20%E8%A1%8C%E5%BC%80%E5%A7%8B%E5%88%B0%E6%9C%80%E5%90%8E%E4%B8%80%E8%A1%8C%E4%B8%AD%E6%AF%8F%E4%B8%80%E8%A1%8C%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%20well%20%E4%B8%BA%20good%0A%3An%2C&preview=true)]s/well/good/g 替換第 n 行開始到最后一行中每一行所有 well 為 good
    n 為數(shù)字,若 n 為 .,表示從當(dāng)前行開始到最后一行
    :%s/well/good/(等同于 :g/well/s//good/) 替換每一行的第一個(gè) well 為 good
    :%s/well/good/g(等同于 :g/well/s//good/g) 替換每一行中所有 well 為 good,可以使用 # 作為分隔符,此時(shí)中間出現(xiàn)的 / 不會(huì)作為分隔符
    😒#well/#good/# 替換當(dāng)前行第一個(gè) well/ 為 good/
    :%s#/usr/bin#/bin#g 可以把文件中所有路徑/usr/bin換成/bin

  • 直接替換文件中的字符串。(此法不用打開文件即可替換字符串,而且可以批量替換多個(gè)文件。)

    (1)perl命令替換,參數(shù)含義如下:

    -a 自動(dòng)分隔模式,用空格分隔$_并保存到@F中。相當(dāng)于@F = split ”。分隔符可以使用-F參數(shù)指定 -F 指定-a的分隔符,可以使用正則表達(dá)式 -e 執(zhí)行指定的腳本。 -i<擴(kuò)展名> 原地替換文件,并將舊文件用指定的擴(kuò)展名備份。不指定擴(kuò)展名則不備份。 -l 對(duì)輸入內(nèi)容自動(dòng)chomp,對(duì)輸出內(nèi)容自動(dòng)添加換行 -n 自動(dòng)循環(huán),相當(dāng)于 while(<>) { 腳本; } -p 自動(dòng)循環(huán)+自動(dòng)輸出,相當(dāng)于 while(<>) { 腳本; print; } **用法示例:** perl -p -i.bak -e 's/\bfoo\b/bar/g' *.c 將所有C程序中的foo替換成bar,舊文件備份成.bak

    perl -p -i -e “s/shan/hua/g” ./lishan.txt ./lishan.txt.bak
    ? 將當(dāng)前文件夾下lishan.txt和lishan.txt.bak中的“shan”都替換為“hua”

    perl -i.bak -pe ‘s/(\d+)/ 1 + $1 /ge’ file1 file2
    ? 將每個(gè)文件中出現(xiàn)的數(shù)值都加一

    (2)sed命令下批量替換文件內(nèi)容

    格式: sed -i “s/查找字段/替換字段/g” grep 查找字段 -rl 路徑 文件名

    -i 表示inplace edit,就地修改文件

    -r 表示搜索子目錄

    -l 表示輸出匹配的文件名
    ? s表示替換,d表示刪除

    用法示例:

    sed -i “s/shan/hua/g” lishan.txt

    把當(dāng)前目錄下lishan.txt里的shan都替換為hua

  • 答案解析

    sed的其他用法如下:

  • 刪除行首空格
    sed ‘s/1//g’ filename
    sed ‘s/^ *//g’ filename
    sed 's/2//g’ filename
  • 行后和行前添加新行
    行后:sed ‘s/pattern/&\n/g’ filename
    行前:sed ‘s/pattern/\n&/g’ filename
    &代表pattern
  • 使用變量替換(使用雙引號(hào))
    sed -e “s/[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-EUFj54xO-1641561861423)(https://www.nowcoder.com/equation?tex=var1%2F&preview=true)]var2/g” filename
  • 在第一行前插入文本
    sed -i ‘1 i\插入字符串’ filename
  • 在最后一行插入
    sed -i ‘$ a\插入字符串’ filename
  • 在匹配行前插入
    sed -i ‘/pattern/ i “插入字符串”’ filename
  • 在匹配行后插入
    sed -i ‘/pattern/ a “插入字符串”’ filename
  • 刪除文本中空行和空格組成的行以及#號(hào)注釋的行
    馬上運(yùn)行一個(gè)可執(zhí)行的文件,這種優(yōu)化可以避免拷貝大量根本就不會(huì)被使用的數(shù)據(jù)(地址空間里常常包含數(shù)十兆的數(shù)據(jù))。由于Unix強(qiáng)調(diào)進(jìn)程快速執(zhí)行的能力,所以這個(gè)優(yōu)化是很重要的。這里補(bǔ)充一點(diǎn):Linux COW與exec沒(méi)有必然聯(lián)系。
  • 實(shí)際上COW技術(shù)不僅僅在Linux進(jìn)程上有應(yīng)用,其他例如C++的String在有的IDE環(huán)境下也支持COW技術(shù),即例如:

    string str1 = "hello world"; string str2 = str1;

    之后執(zhí)行代碼:

    str1[1]='q'; str2[1]='w';

    在開始的兩個(gè)語(yǔ)句后,str1和str2存放數(shù)據(jù)的地址是一樣的,而在修改內(nèi)容后,str1的地址發(fā)生了變化,而str2的地址還是原來(lái)的,這就是C++中的COW技術(shù)的應(yīng)用,不過(guò)VS2005似乎已經(jīng)不支持COW。

    1.61 Linux替換文本該如何操作呢?

    參考回答

  • 通過(guò)vi編輯器來(lái)替換
    vi/vim 中可以使用 😒 命令來(lái)替換字符串。
    😒/well/good/ 替換當(dāng)前行第一個(gè) well 為 good
    😒/well/good/g 替換當(dāng)前行所有 well 為 good
    :n,[外鏈圖片轉(zhuǎn)存中…(img-VClxZXz9-1641561861422)]s/well/good/g 替換第 n 行開始到最后一行中每一行所有 well 為 good
    n 為數(shù)字,若 n 為 .,表示從當(dāng)前行開始到最后一行
    :%s/well/good/(等同于 :g/well/s//good/) 替換每一行的第一個(gè) well 為 good
    :%s/well/good/g(等同于 :g/well/s//good/g) 替換每一行中所有 well 為 good,可以使用 # 作為分隔符,此時(shí)中間出現(xiàn)的 / 不會(huì)作為分隔符
    😒#well/#good/# 替換當(dāng)前行第一個(gè) well/ 為 good/
    :%s#/usr/bin#/bin#g 可以把文件中所有路徑/usr/bin換成/bin

  • 直接替換文件中的字符串。(此法不用打開文件即可替換字符串,而且可以批量替換多個(gè)文件。)

    (1)perl命令替換,參數(shù)含義如下:

    -a 自動(dòng)分隔模式,用空格分隔$_并保存到@F中。相當(dāng)于@F = split ”。分隔符可以使用-F參數(shù)指定 -F 指定-a的分隔符,可以使用正則表達(dá)式 -e 執(zhí)行指定的腳本。 -i<擴(kuò)展名> 原地替換文件,并將舊文件用指定的擴(kuò)展名備份。不指定擴(kuò)展名則不備份。 -l 對(duì)輸入內(nèi)容自動(dòng)chomp,對(duì)輸出內(nèi)容自動(dòng)添加換行 -n 自動(dòng)循環(huán),相當(dāng)于 while(<>) { 腳本; } -p 自動(dòng)循環(huán)+自動(dòng)輸出,相當(dāng)于 while(<>) { 腳本; print; } **用法示例:** perl -p -i.bak -e 's/\bfoo\b/bar/g' *.c 將所有C程序中的foo替換成bar,舊文件備份成.bak

    perl -p -i -e “s/shan/hua/g” ./lishan.txt ./lishan.txt.bak
    ? 將當(dāng)前文件夾下lishan.txt和lishan.txt.bak中的“shan”都替換為“hua”

    perl -i.bak -pe ‘s/(\d+)/ 1 + $1 /ge’ file1 file2
    ? 將每個(gè)文件中出現(xiàn)的數(shù)值都加一

    (2)sed命令下批量替換文件內(nèi)容

    格式: sed -i “s/查找字段/替換字段/g” grep 查找字段 -rl 路徑 文件名

    -i 表示inplace edit,就地修改文件

    -r 表示搜索子目錄

    -l 表示輸出匹配的文件名
    ? s表示替換,d表示刪除

    用法示例:

    sed -i “s/shan/hua/g” lishan.txt

    把當(dāng)前目錄下lishan.txt里的shan都替換為hua

  • 答案解析

    sed的其他用法如下:

  • 刪除行首空格
    sed ‘s/3//g’ filename
    sed ‘s/^ *//g’ filename
    sed 's/4//g’ filename
  • 行后和行前添加新行
    行后:sed ‘s/pattern/&\n/g’ filename
    行前:sed ‘s/pattern/\n&/g’ filename
    &代表pattern
  • 使用變量替換(使用雙引號(hào))
    sed -e “s/[外鏈圖片轉(zhuǎn)存中…(img-EUFj54xO-1641561861423)]var2/g” filename
  • 在第一行前插入文本
    sed -i ‘1 i\插入字符串’ filename
  • 在最后一行插入
    sed -i ‘$ a\插入字符串’ filename
  • 在匹配行前插入
    sed -i ‘/pattern/ i “插入字符串”’ filename
  • 在匹配行后插入
    sed -i ‘/pattern/ a “插入字符串”’ filename
  • 刪除文本中空行和空格組成的行以及#號(hào)注釋的行
    grep -v ^# filename | sed /5*[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-Kuj7fz2H-1641561861424)(https://gitee.com/RedemptionXU/pic-md/raw/master/20220106210352.svg+xml;%20charset=utf-8)]/d

  • ??

  • [:space:] ??

  • ??

  • [:space:] ??

  • [:space:] ??

  • 總結(jié)

    以上是生活随笔為你收集整理的操作系统面试题目详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。