騰訊後臺開發面試題及答案

簡單歸納:fd只是一個整數,在open時產生。起到一個索引的作用,進程通過PCB中的文件描述符表找到該fd所指向的文件指針filp。

文件描述符的操作(如: open)返回的是一個文件描述符,內核會在每個進程空間中維護一個文件描述符表, 所有打開的文件都將通過此表中的文件描述符來引用; 
而流(如: fopen)返回的是一個FILE結構指針, FILE結構是包含有文件描述符的,FILE結構函數可以看作是對fd直接操作的系統調用的封裝, 它的優點是帶有I/O緩存

每個進程在PCB(Process Control Block)即進程控制塊中都保存着一份文件描述符表,文件描述符就是這個表的索引,文件描述表中每個表項都有一個指向已打開文件的指針,現在我們明確一下:已打開的文件在內核中用file結構體表示,文件描述符表中的指針指向file結構體。

linuxos:

netstat 顯示網絡狀態

Netstat 命令用於顯示各種網絡相關信息,如網絡連接,路由表,接口狀態 (Interface Statistics),masquerade 連接,多播成員 (Multicast Memberships) 等等。

從整體上看,netstat的輸出結果可以分爲兩個部分:

一個是Active Internet connections,稱爲有源TCP連接,其中"Recv-Q"和"Send-Q"指%0A的是接收隊列和發送隊列。這些數字一般都應該是0。如果不是則表示軟件包正在隊列中堆積。這種情況只能在非常少的情況見到。

另一個是Active UNIX domain sockets,稱爲有源Unix域套接口(和網絡套接字一樣,但是隻能用於本機通信,性能可以提高一倍)。
Proto顯示連接使用的協議,RefCnt表示連接到本套接口上的進程號,Types顯示套接口的類型,State顯示套接口當前的狀態,Path表示連接到套接口的其它進程使用的路徑名。

 

tcpdump主要是截獲通過本機網絡接口的數據,用以分析。能夠截獲當前所有通過本機網卡的數據包。它擁有靈活的過濾機制,可以確保得到想要的數據。

用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據使用者的定義對網絡上的數據包進行截獲的包分析工具。 tcpdump可以將網絡中傳送的數據包的“頭”完全截獲下來提供分析。它支持針對網絡層、協議、主機、網絡或端口的過濾,並提供and、or、not等邏輯語句來幫助你去掉無用的信息。

 

ipcs:檢查系統上共享內存的分配

用途

報告進程間通信設施狀態。

ipcs 命令往標準輸出寫入一些關於活動進程間通信設施的信息。如果沒有指定任何標誌,ipcs 命令用簡短格式寫入一些關於當前活動消息隊列共享內存段、信號量、遠程隊列和本地隊列標題。

 

 

 

Linuxipcs指令的用法詳解。ipcsLinux下顯示進程間通信設施狀態的工具。可以顯示消息隊列、共享內存和信號量的信息。對於程序員可能更有用些,普通的系統管理員一般用不到此指令。

ipcrm:手動解除系統上共享內存的分配

用途
  刪除消息隊列、信號集、或者共享內存標識。

 

 

 

(如果這四個命令沒聽說過或者不能熟練使用,基本上可以回家,通過的概率較小 ^_^ ,這四個命令的熟練掌握程度基本上能體現面試者實際開發和調試程序的經驗)

 

查看cpu信息

        #cat /proc/cpuinfo

   # cat /proc/meminfo

查看硬盤信息

        # df -lh

更詳細的信息

       # cat /proc/scsi/scsi

查看網卡信息

        # dmesg | grep eth

更常用的命令(顯示系統核心版本號、名稱、機器類型等)

       # uname -a

 

cpu 內存硬盤等等與系統性能調試相關的命令必須熟練掌握,設置修改權限 tcp網絡狀態查看各進程狀態抓包相關等相關命令必須熟練掌握

awk sed需掌握

共享內存的使用實現原理(必考必問,然後共享內存段被映射進進程空間之後,存在於進程空間的什麼位置?共享內存段最大限制是多少?)

$sysctl kern.ipc.shmmax
kern.ipc.shmmax: 33554432

Linux的2.2.x以後的內核版本支持多種共享內存方式,比如:內存映射mmap、POSIX共享內存、System V共享內存;

 

共享內存定義:共享內存是最快的可用IPC(進程間通信)形式。它允許多個不相關的進程去訪問同一部分邏輯內存。共享內存是由IPC爲一個進程創建的一個特殊的地址範圍,它將出現在進程的地址空間中。其他進程可以把同一段共享內存段“連接到”它們自己的地址空間裏去。所有進程都可以訪問共享內存中的地址。如果一個進程向這段共享內存寫了數據,所做的改動會立刻被有訪問同一段共享內存的其他進程看到。因此共享內存對於數據的傳輸是非常高效的。

共享內存的原理:共享內存是最有用的進程間通信方式之一,也是最快的IPC形式。兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。

c++進程內存空間分佈(注意各部分的內存地址誰高誰低,注意棧從高到低分配,堆從低到高分配)

ELF是什麼?其大小與程序中全局變量的是否初始化有什麼關係(注意未初始化的數據放在bss段)

Linux ELF  ELF = Executable and Linkable Format,可執行連接格式,是UNIX系統實驗室(USL)作爲應用程序二進制接口(Application Binary Interface,ABI)而開發和發佈的。擴展名爲elf。工具接口標準委員會(TIS)選擇了正在發展中的ELF標準作爲工作在32位INTEL體系上不同操作系統之間可移植的二進制文件格式。假定開發者定義了一個二進制接口集合,ELF標準用它來支持流線型的軟件發展。應該減少不同執行接口的數量。因此可以減少重新編程重新編譯的代碼。

 

BSS段

可執行程序包括BSS段、數據段代碼段(也稱文本段)。

BSS(Block Started by Symbol)通常是指用來存放程序中未初始化的全局變量靜態變量的一塊內存區域。特點是:可讀寫的,在程序執行之前BSS段會自動清0。所以,未初始的全局變量在程序執行之前已經成0了。

注意和數據段的區別,BSS存放的是未初始化的全局變量靜態變量,數據段存放的是初始化後的全局變量和靜態變量。

UNIX下可使用size命令查看可執行文件的段大小信息。如size a.out。

 

可執行文件:包含了代碼和數據。具有可執行的程序。

可重定位文件:包含了代碼和數據(這些數據是和其他重定位文件和共享的 
object
文件一起連接時使用的)

共享object文件(又可叫做共享庫):包含了代碼和數據(這些數據是在連接
時候被連接器ld和運行時動態連接器使用的)。

使創建共享庫容易,使動態裝載和共享庫的結合更加容易。在ELF下,在C++ 
中,全局的構造函數和析構函數在共享庫和靜態庫中用同樣方法處理。

使用過哪些進程間通訊機制,並詳細說明(重點)

makefile編寫,雖然比較基礎,但是會被問到

mkdir mf

cd mf

vim makefile

hello.o:hello.c hello.h

       gcc –c hello.o -Lm

make

./hello

gdb調試相關的經驗,會被問到

GDB是GNU開源組織發佈的一個強大的UNIX下的程序調試工具。或許,各位比較喜歡那種圖形界面方式的,像VC、BCB等IDE的調試,但如果你是在 UNIX平臺下做軟件,你會發現GDB這個調試工具有比VC、BCB的圖形化調試器更強大的功能。所謂“寸有所長,尺有所短”就是這個道理。

一般來說,GDB主要幫助你完成下面四個方面的功能:

1、啓動你的程序,可以按照你的自定義的要求隨心所欲的運行程序。

2、可讓被調試的程序在你所指定的調置的斷點處停住。(斷點可以是條件表達式

3、當程序被停住時,可以檢查此時你的程序中所發生的事。

4、動態的改變你程序的執行環境。

《GDB使用手冊》

gcc -g -o hello hello.c 。

GDB常用命令簡介
  GDB的命令很多,本文不會全部介紹,僅會介紹一些最常用的。在介紹之前,先介紹GDB中的一個非常有用的功能:補齊功能。它就如同Linux下SHELL中的命令補齊一樣。當你輸入一個命令的前幾個字符,然後輸入TAB鍵,如果沒有其它命令的前幾個字符與此相同,SHELL將補齊此命令。如果有其它命令的前幾個字符與此相同,你會聽到一聲警告聲,再輸入TAB鍵,SHELL將所有前幾個字符與此相同的命令全部列出。而GDB中的補齊功能不僅能補齊GDB命令,而且能補齊參數。
  本文將先介紹常用的命令,然後結合一個具體的例子來演示如何實際使用這些命令。下面的所有命令除了第一條啓動GDB命令是在SHELL下輸入的,其餘都是GDB內的命令。大部分GDB內的命令都可以僅輸入前幾個字符,只要不與其它指令衝突。如quit可以簡寫爲q,因爲以q打頭的命令只有quit。List可以簡寫爲l,等等
3.1 啓動GDB
  你可以輸入GDB來啓動GDB程序。GDB程序有許多參數,在此沒有必要詳細介紹,但一個最爲常用的還是要介紹的:如果你已經編譯好一個程序,我們假設文件名爲hello,你想用GDB調試它,可以輸入gdb hello來啓動GDB並載入你的程序。如果你僅僅啓動了GDB,你必須在啓動後,在GDB中再載入你的程序。
3.2 載入程序 === file
  在GDB內,載入程序很簡單,使用file命令。如file hello。當然,程序的路徑名要正確。
  退出GDB === quit
  在GDB的命令方式下,輸入quit,你就可以退出GDB。你也可以輸入'C-d'來退出GDB。
3.3 運行程序 === run
  當你在GDB中已將要調試的程序載入後,你可以用run命令來執行。如果你的程序需要參數,你可以在run指令後接着輸入參數,就象你在SHELL下執行一個需要參數的命令一樣。
3.4 查看程序信息 === info
  info指令用來查看程序的信息,當你用help info查看幫助的話,info指令的參數足足佔了兩個屏幕,它的參數非常多,但大部分不常用。我用info指令最多的是用它來查看斷點信息。
3.4.1 查看斷點信息
info br
br是斷點break的縮寫,記得GDB的補齊功能吧。用這條指令,你可以得到你所設置的所有斷點的詳細信息。包括斷點號,類型,狀態,內存地址,斷點在源程序中的位置等。
3.4.2 查看當前源程序
info source
3.4.3 查看堆棧信息
info stack
用這條指令你可以看清楚程序的調用層次關係。
3.4.4 查看當前的參數
info args
3.5 列出源一段源程序 === list
3.5.1 列出某個函數
list FUNCTION
3.5.2 以當前源文件的某行爲中間顯示一段源程序
list LINENUM
3.5.3 接着前一次繼續顯示
list
3.5.4 顯示前一次之前的源程序
list -
3.5.5 顯示另一個文件的一段程序
list FILENAME:FUNCTION 或 listFILENAME:LINENUM
3.6 設置斷點 === break
  現在我們將要介紹的也許是最常用和最重要的命令:設置斷點。無論何時,只要你的程序已被載入,並且當前沒有正在運行,你就能設置,修改,刪除斷點。設置斷點的命令是break。有許多種設置斷點的方法。如下:
3.6.1 在函數入口設置斷點

如何定位內存泄露?

內存泄漏是指堆內存的泄漏。堆內存是指程序從堆中分配的、大小任意的(內存塊的大小可以在程序運行期決定)、使用完後必須顯示釋放的內存。應用程序一般使用mallocreallocnew等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用freedelete釋放該內存塊。否則,這塊內存就不能被再次使用,我們就說這塊內存泄漏了。

C++程序缺乏相應的手段來檢測內存信息,只能使用top指令觀察進程的動態內存總額。而且程序退出時,我們無法獲知任何內存泄漏信息

使用Linux命令回收內存,可以使用pskill兩個命令檢測內存使用情況和進行回收。在使用超級用戶權限時使用命令“ps”,它會列出所有正在運行的程序名稱和對應的進程號(PID)。kill命令的工作原理是向Linux操作系統的內核送出一個系統操作信號和程序的進程號(PID

動態鏈接和靜態鏈接的區別

動態鏈接是指在生成可執行文件時不將所有程序用到的函數鏈接到一個文件,因爲有許多函數在操作系統帶的dll文件中,當程序運行時直接從操作系統中找。 而靜態鏈接就是把所有用到的函數全部鏈接到exe文件中。
動態鏈接是隻建立一個引用的接口,而真正的代碼和數據存放在另外的可執行模塊中,在運行時再裝入;而靜態鏈接是把所有的代碼和數據都複製到本模塊中,運行時就不再需要庫了。

32位系統一個進程最多有多少堆內存

多線程和多進程的區別(重點面試官最最關心的一個問題,必須從cpu調度,上下文切換,數據共享,多核cup利用率,資源佔用,等等各方面回答,然後有一個問題必須會被問到:哪些東西是一個線程私有的?答案中必須包含寄存器,否則悲催)

我們按照多個不同的維度,來看看多進程和多線程的對比(注:都是相對的,不是說一個好得不得了,另一個差的無法忍受)

維度

多進程

多線程

總結

數據共享、同步

數據是分開的:共享複雜,需要用IPC;同步簡單

多線程共享進程數據:共享簡單;同步複雜

各有優勢

內存、CPU

佔用內存多,切換複雜,CPU利用率低

佔用內存少,切換簡單,CPU利用率高

線程佔優

創建銷燬、切換

創建銷燬、切換複雜,速度慢 

創建銷燬、切換簡單,速度快 

線程佔優 

編程調試

編程簡單,調試簡單

編程複雜,調試複雜

進程佔優 

可靠性

進程間不會相互影響 

一個線程掛掉將導致整個進程掛掉

進程佔優

分佈式 

適應於多核、多機分佈;如果一臺機器不夠,擴展到多臺機器比較簡單

適應於多核分佈

進程佔優

然後我們來看下線程和進程間的比較

 

子進程繼承父進程的屬性:

子線程繼承主線程的屬性:

實際用戶ID,實際組ID,有效用戶ID,有效組ID

附加組ID

進程組ID

會話ID

控制終端;

設置用戶ID標誌和設置組ID標誌;

當前工作目錄;

根目錄;

文件模式創建屏蔽字(umask);

信號屏蔽和安排;

針對任一打開文件描述符的在執行時關閉(close-on-exec)標誌;

環境;

連接的共享存儲段;

存儲映射;

資源限制;

進程中的所有信息對該進程的所有線程都是共享的;

可執行的程序文本;

程序的全局內存;

堆內存;

棧;

文件描述符;

信號的處理是進程中所有線程共享的(注意:如果信號的默認處理是終止該進程那麼即是把信號傳給某個線程也一樣會將進程殺掉);

 

父子進程之間的區別:

子線程特有的:

fork的返回值(=0子進程)

進程ID不同;

兩個進程具有不同的父進程ID

子進程的tms_utime,tms_stime,tms_cutime以及tms_ustime均被設置爲0

不繼承父進程設置的文件鎖;

子進程的未處理鬧鐘被清除;

子進程的未處理信號集設置爲空集;

線程ID

一組寄存器值;

棧;

調度優先級和策略;

信號屏蔽字;

errno變量;

線程私有數據;

 


1)
需要頻繁創建銷燬的優先用線程。
實例:web服務器。來一個建立一個線程,斷了就銷燬線程。要是用進程,創建和銷燬的代價是很難承受的。
2
)需要進行大量計算的優先使用線程。
所謂大量計算,當然就是要消耗很多cpu,切換頻繁了,這種情況先線程是最合適的。
實例:圖像處理、算法處理
3
)強相關的處理用線程,若相關的處理用進程。
什麼叫強相關、弱相關?理論上很難定義,給個簡單的例子就明白了。
一般的server需要完成如下任務:消息收發和消息處理。消息收發和消息處理就是弱相關的任務,而消息處理裏面可能又分爲消息解碼、業務處理,這兩個任務相對來說相關性就要強多了。因此消息收發和消息處理可以分進程設計,消息解碼和業務處理可以分線程設計。
4
)可能擴展到多機分佈的用進程,多核分佈的用線程。
5
)都滿足需求的情況下,用你最熟悉、最拿手的方式。

至於數據共享、同步編程、調試可靠性這幾個維度的所謂的複雜、簡單應該怎麼取捨,只能說:沒有明確的選擇方法。一般有一個選擇原則:如果多進程和多線程都能夠滿足要求,那麼選擇你最熟悉、最拿手的那個。

 

一般運行一個程序稱爲一個進程。

進程可以創建線程,也可以創建進程。

線程是由進程管理的,線程之間、線程和父進程(創建線程的進程)之間可以共享內存變量(需要使用策略的)。

進程之間一般不可以直接共享內存變量,需要使用一些進程間的控制共享內存變量。

如果你使用並行計算,建議使用線程。

 

 

 

進程是個容器或者說資源管理者,有獨立的內存地址空間。
線程依賴於它所在的進程,共享進程的資源和內存地址空間。

unix
特別是linux裏面,線程與進程接近;windows的進程完全是個容器,線程更輕量級。具體可以瞭解linux下的fork以及clonewindowscreateprocesscreatethread

 

什麼是進程。最直觀的就是一個個pid,官方的說法就:進程是程序在計算機上的一次執行活動。

TCP和UDP分析

主要參看2篇博文:

http://blog.csdn.net/dog250/article/details/6612496

http://blog.csdn.net/dog250/article/details/6896949

還有就是謝老師寫的《計算機網絡》第五版,.TCP/IP詳解(卷一,卷二)以及《Unix網絡編程》以及Linux源代碼之外,RFC

一、概念:

key:TCP是一種面向連接的、可靠的、字節流服務

  

1.面向鏈接:TCP面向鏈接,面向連接意味着兩個使用TCP的應用(通常是一個客戶和一個服務器)在彼此交換數據之前必須通過三次握手先建立一個TCP連接。在一個TCP中僅有兩方彼此通信,多播和廣播不能用於TCPUDP是不可靠的傳輸,傳輸前不需要建立鏈接,可以應用多播和廣播實現一對多的通信。

 
2.
可靠性:TCP提供端到端的流量控制,對收到的數據進行確認,採用超時重發,對失序的數據進行重新排序等機制保證數據通信的可靠性。而UDP是一種不可靠的服務,接收方可能不能收到發送方的數據報。

 
3.TCP
是一種流模式的協議,UDP是一種數據報模式的協議。進程的每個輸出操作都正好產生一個UDP數據報,並組裝成一份待發送的IP數據報。TCP應用程序產生的全體數據與真正發送的單個IP數據報可能沒有什麼聯繫。TCP會有粘包和半包的現象。

 
4.
效率上:速度上,一般TCP速度慢,傳輸過程中需要對數據進行確認,超時重發,還要對數據進行排序。UDP沒有這些機制所以速度快。數據比例,TCP頭至少20個字節,UDP8個字節,相對效率高。組裝效率上:TCP頭至少20個字節,UDP8個字節,系統組裝上TCP相對慢。

 
5.
用途上:用於TCP可靠性,httpftp使用。而由於UDP速度快,視頻,在線遊戲多用UDP,保證實時性

 
對於第三點的理解。TCP可能發送100,而接收到50,不是丟了,而是每次接受的都比發送的多,其實TCP並沒有包的概念。例如,每次發10個字節,可能讀得時候一次讀了20個字節。TCP是一種流模式的協議,在接收到的緩存中按照發送的包得順序自動按照順序拼接好,因爲數據基本來自同一個主機,而且是按照順序發送過來的,TCP的緩存中存放的就是,連續的數據。感覺好像是多封裝了一步比UDP。而UDP因爲可能兩個不同的主機,給同一個主機發送,(一個端口可能收到多個應用程序的數據),或者按照TCP那樣合併數據,必然會造成數據錯誤。我覺得關鍵的原因還是,TCP是面向連接,而UDP是無連接的,這就導致,TCP接收的數據爲一個主機發來且有序無誤的,而UDP可能是多個主機發來的無序,可能錯誤的。

 

寫一個c程序辨別系統是16or32

法一:int k=~0;

if((unsigned int)k >63356) cout<<"at least 32bits"<<endl;

else cout<<"16 bits"<<endl;

法二://32爲系統

int i=65536;

cout<<i<<endl;

int j=65535;

cout<<j<<endl;

寫一個c程序辨別系統是大端or小端字節序

用聯合體:如char類型的,可以看他輸出的是int的高字節還是低字節

 

信號:列出常見的信號,信號怎麼處理?

信號是Linux編程中非常重要的部分,本文將詳細介紹信號機制的基本概念、Linux對信號機制的大致實現方法、如何使用信號,以及有關信號的幾個系統調用。 

信號機制是進程之間相互傳遞消息的一種方法,信號全稱爲軟中斷信號,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。所以,信號可以說是進程控制的一部分。 

一、信號的基本概念 

本節先介紹信號的一些基本概念,然後給出一些基本的信號類型和信號對應的事件。基本概念對於理解和使用信號,對於理解信號機制都特別重要。下面就來看看什麼是信號。 

1、基本概念 

軟中斷信號(signal,又簡稱爲信號)用來通知進程發生了異步事件。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因爲內部事件而給進程發送信號,通知進程發生了某個事件。注意,信號只是用來通知某進程發生了什麼事件,並不給該進程傳遞任何數據。 

收 到信號的進程對各種信號有不同的處理方法。處理方法可以分爲三類:第一種是類似中斷的處理程序,對於需要處理的信號,進程可以指定處理函數,由該函數來處 理。第二種方法是,忽略某個信號,對該信號不做任何處理,就象未發生過一樣。第三種方法是,對該信號的處理保留系統的默認值,這種缺省操作,對大部分的信 號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行爲。 

在進程表的表項中有一個軟中斷信號域,該域中每一位對應一個信號,當有信號發送給進程時,對應位置位。由此可以看出,進程對不同的信號可以同時保留,但對於同一個信號,進程並不知道在處理之前來過多少個。 

2、信號的類型 

發出信號的原因很多,這裏按發出信號的原因簡單分類,以瞭解各種信號: 

(1) 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。 
(2) 與進程例外事件相關的信號。如進程越界,或企圖寫一個只讀的內存區域(如程序正文區),或執行一個特權指令及其他各種硬件錯誤。 
(3) 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。 
(4) 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。 
(5) 在用戶態下的進程發出的信號。如進程調用系統調用kill向其他進程發送信號。 
(6) 與終端交互相關的信號。如用戶關閉一個終端,或按下break鍵等情況。 
(7) 跟蹤進程執行的信號。 

Linux支持的信號列表如下。很多信號是與機器的體系結構相關的,首先列出的是POSIX.1中列出的信號: 

信號 值 處理動作 發出信號的原因 
---------------------------------------------------------------------- 
SIGHUP 1 A 終端掛起或者控制進程終止 
SIGINT 2 A 鍵盤中斷(如break鍵被按下) 
SIGQUIT 3 C 鍵盤的退出鍵被按下 
SIGILL 4 C 非法指令 
SIGABRT 6 C 由abort(3)發出的退出指令 
SIGFPE 8 C 浮點異常 
SIGKILL 9 AEF Kill信號 
SIGSEGV 11 C 無效的內存引用 
SIGPIPE 13 A 管道破裂: 寫一個沒有讀端口的管道 
SIGALRM 14 A 由alarm(2)發出的信號 
SIGTERM 15 A 終止信號 
SIGUSR1 30,10,16 A 用戶自定義信號1 
SIGUSR2 31,12,17 A 用戶自定義信號2 
SIGCHLD 20,17,18 B 子進程結束信號 
SIGCONT 19,18,25 進程繼續(曾被停止的進程) 
SIGSTOP 17,19,23 DEF 終止進程 
SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵 
SIGTTIN 21,21,26 D 後臺進程企圖從控制終端讀 
SIGTTOU 22,22,27 D 後臺進程企圖從控制終端寫 

下面的信號沒在POSIX.1中列出,而在SUSv2列出 

信號 值 處理動作 發出信號的原因 
-------------------------------------------------------------------- 
SIGBUS 10,7,10 C 總線錯誤(錯誤的內存訪問) 
SIGPOLL A Sys V定義的Pollable事件,與SIGIO同義 
SIGPROF 27,27,29 A Profiling定時器到 
SIGSYS 12,-,12 C 無效的系統調用 (SVID) 
SIGTRAP 5 C 跟蹤/斷點捕獲 
SIGURG 16,23,21 B Socket出現緊急條件(4.2 BSD) 
SIGVTALRM 26,26,28 A 實際時間報警時鐘信號(4.2 BSD) 
SIGXCPU 24,24,30 C 超出設定的CPU時間限制(4.2 BSD) 
SIGXFSZ 25,25,31 C 超出設定的文件大小限制(4.2 BSD) 

(對於SIGSYS,SIGXCPU,SIGXFSZ,以及某些機器體系結構下的SIGBUS,Linux缺省的動作是A (terminate),SUSv2 是C (terminate and dump core))。 

下面是其它的一些信號 

信號 值 處理動作 發出信號的原因 
---------------------------------------------------------------------- 
SIGIOT 6 C IO捕獲指令,與SIGABRT同義 
SIGEMT 7,-,7 
SIGSTKFLT -,16,- A 協處理器堆棧錯誤 
SIGIO 23,29,22 A 某I/O操作現在可以進行了(4.2 BSD) 
SIGCLD -,-,18 A 與SIGCHLD同義 
SIGPWR 29,30,19 A 電源故障(System V) 
SIGINFO 29,-,- A 與SIGPWR同義 
SIGLOST -,-,- A 文件鎖丟失 
SIGWINCH 28,28,20 B 窗口大小改變(4.3 BSD, Sun) 
SIGUNUSED -,31,- A 未使用的信號(will be SIGSYS) 

(在這裏,- 表示信號沒有實現;有三個值給出的含義爲,第一個值通常在Alpha和Sparc上有效,中間的值對應i386和ppc以及sh,最後一個值對應mips。信號29在Alpha上爲SIGINFO / SIGPWR ,在Sparc上爲SIGLOST。) 

處理動作一項中的字母含義如下 
A 缺省的動作是終止進程 
B 缺省的動作是忽略此信號 
C 缺省的動作是終止進程並進行內核映像轉儲(dump core) 
D 缺省的動作是停止進程 
E 信號不能被捕獲 
F 信號不能被忽略 

上 面介紹的信號是常見系統所支持的。以表格的形式介紹了各種信號的名稱、作用及其在默認情況下的處理動作。各種默認處理動作的含義是:終止程序是指進程退 出;忽略該信號是將該信號丟棄,不做處理;停止程序是指程序掛起,進入停止狀況以後還能重新進行下去,一般是在調試的過程中(例如ptrace系統調 用);內核映像轉儲是指將進程數據在內存的映像和進程在內核結構中存儲的部分內容以一定格式轉儲到文件系統,並且進程退出執行,這樣做的好處是爲程序員提 供了方便,使得他們可以得到進程當時執行時的數據值,允許他們確定轉儲的原因,並且可以調試他們的程序。 

注意 信號SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信號SIGIOT與SIGABRT是一個信號。可以看出,同一個信號在不同的系統中值可能不一樣,所以建議最好使用爲信號定義的名字,而不要直接使用信號的值。 

二、信 號 機 制 

上 一節中介紹了信號的基本概念,在這一節中,我們將介紹內核如何實現信號機制。即內核如何向一個進程發送信號、進程如何接收一個信號、進程怎樣控制自己對信 號的反應、內核在什麼時機處理和怎樣處理進程收到的信號。還要介紹一下setjmp和longjmp在信號中起到的作用。 

1、內核對信號的基本處理方法 

內 核給一個進程發送軟中斷信號的方法,是在進程所在的進程表項的信號域設置對應於該信號的位。這裏要補充的是,如果信號發送給一個正在睡眠的進程,那麼要看 該進程進入睡眠的優先級,如果進程睡眠在可被中斷的優先級上,則喚醒進程;否則僅設置進程表中信號域相應的位,而不喚醒進程。這一點比較重要,因爲進程檢 查是否收到信號的時機是:一個進程在即將從內核態返回到用戶態時;或者,在一個進程要進入或離開一個適當的低調度優先級睡眠狀態時。 

內核處理一個進程收到的信號的時機是在一個進程從內核態返回用戶態時。所以,當一個進程在內核態下運行時,軟中斷信號並不立即起作用,要等到將返回用戶態時才處理。進程只有處理完信號纔會返回用戶態,進程在用戶態下不會有未處理完的信號。 

內 核處理一個進程收到的軟中斷信號是在該進程的上下文中,因此,進程必須處於運行狀態。前面介紹概念的時候講過,處理信號有三種類型:進程接收到信號後退 出;進程忽略該信號;進程收到信號後執行用戶設定用系統調用signal的函數。當進程接收到一個它忽略的信號時,進程丟棄該信號,就象沒有收到該信號似 的繼續運行。如果進程收到一個要捕捉的信號,那麼進程從內核態返回用戶態時執行用戶定義的函數。而且執行用戶定義的函數的方法很巧妙,內核是在用戶棧上創 建一個新的層,該層中將返回地址的值設置成用戶定義的處理函數的地址,這樣進程從內核返回彈出棧頂時就返回到用戶定義的函數處,從函數返回再彈出棧頂時, 才返回原先進入內核的地方。這樣做的原因是用戶定義的處理函數不能且不允許在內核態下執行(如果用戶定義的函數在內核態下運行的話,用戶就可以獲得任何權 限)。 

在信號的處理方法中有幾點特別要引起注意。第一,在一些系統中,當一個進程處理完中斷信號返回用戶態之前,內核清除用戶區中設 定的對該信號的處理例程的地址,即下一次進程對該信號的處理方法又改爲默認值,除非在下一次信號到來之前再次使用signal系統調用。這可能會使得進程 在調用signal之前又得到該信號而導致退出。在BSD中,內核不再清除該地址。但不清除該地址可能使得進程因爲過多過快的得到某個信號而導致堆棧溢 出。爲了避免出現上述情況。在BSD系統中,內核模擬了對硬件中斷的處理方法,即在處理某個中斷時,阻止接收新的該類中斷。 

第二個要 引起注意的是,如果要捕捉的信號發生於進程正在一個系統調用中時,並且該進程睡眠在可中斷的優先級上,這時該信號引起進程作一次longjmp,跳出睡眠 狀態,返回用戶態並執行信號處理例程。當從信號處理例程返回時,進程就象從系統調用返回一樣,但返回了一個錯誤代碼,指出該次系統調用曾經被中斷。這要注 意的是,BSD系統中內核可以自動地重新開始系統調用。 

第三個要注意的地方:若進程睡眠在可中斷的優先級上,則當它收到一個要忽略的信號時,該進程被喚醒,但不做longjmp,一般是繼續睡眠。但用戶感覺不到進程曾經被喚醒,而是象沒有發生過該信號一樣。 

第 四個要注意的地方:內核對子進程終止(SIGCLD)信號的處理方法與其他信號有所區別。當進程檢查出收到了一個子進程終止的信號時,缺省情況下,該進程 就象沒有收到該信號似的,如果父進程執行了系統調用wait,進程將從系統調用wait中醒來並返回wait調用,執行一系列wait調用的後續操作(找 出僵死的子進程,釋放子進程的進程表項),然後從wait中返回。SIGCLD信號的作用是喚醒一個睡眠在可被中斷優先級上的進程。如果該進程捕捉了這個 信號,就象普通信號處理一樣轉到處理例程。如果進程忽略該信號,那麼系統調用wait的動作就有所不同,因爲SIGCLD的作用僅僅是喚醒一個睡眠在可被 中斷優先級上的進程,那麼執行wait調用的父進程被喚醒繼續執行wait調用的後續操作,然後等待其他的子進程。 

如果一個進程調用signal系統調用,並設置了SIGCLD的處理方法,並且該進程有子進程處於僵死狀態,則內核將向該進程發一個SIGCLD信號。 

2、setjmp和longjmp的作用 

前面在介紹信號處理機制時,多次提到了setjmp和longjmp,但沒有仔細說明它們的作用和實現方法。這裏就此作一個簡單的介紹。 

在 介紹信號的時候,我們看到多個地方要求進程在檢查收到信號後,從原來的系統調用中直接返回,而不是等到該調用完成。這種進程突然改變其上下文的情況,就是 使用setjmp和longjmp的結果。setjmp將保存的上下文存入用戶區,並繼續在舊的上下文中執行。這就是說,進程執行一個系統調用,當因爲資 源或其他原因要去睡眠時,內核爲進程作了一次setjmp,如果在睡眠中被信號喚醒,進程不能再進入睡眠時,內核爲進程調用longjmp,該操作是內核 爲進程將原先setjmp調用保存在進程用戶區的上下文恢復成現在的上下文,這樣就使得進程可以恢復等待資源前的狀態,而且內核爲setjmp返回1,使 得進程知道該次系統調用失敗。這就是它們的作用。 

三、有關信號的系統調用 

前面兩節已經介紹了有關信號的大部分知 識。這一節我們來了解一下這些系統調用。其中,系統調用signal是進程用來設定某個信號的處理方法,系統調用kill是用來發送信號給指定進程的。這 兩個調用可以形成信號的基本操作。後兩個調用pause和alarm是通過信號實現的進程暫停和定時器,調用alarm是通過信號通知進程定時器到時。所 以在這裏,我們還要介紹這兩個調用。 

1、signal 系統調用 

系統調用signal用來設定某個信號的處理方法。該調用聲明的格式如下: 
void (*signal(int signum, void (*handler)(int)))(int); 
在使用該調用的進程中加入以下頭文件: 
#include <signal.h> 

上述聲明格式比較複雜,如果不清楚如何使用,也可以通過下面這種類型定義的格式來使用(POSIX的定義): 
typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler); 
但這種格式在不同的系統中有不同的類型定義,所以要使用這種格式,最好還是參考一下聯機手冊。 

在調用中,參數signum指出要設置處理方法的信號。第二個參數handler是一個處理函數,或者是 
SIG_IGN:忽略參數signum所指的信號。 
SIG_DFL:恢復參數signum所指信號的處理方法爲默認值。 

傳遞給信號處理例程的整數參數是信號值,這樣可以使得一個信號處理例程處理多個信號。系統調用signal返回值是指定信號signum前一次的處理例程或者錯誤時返回錯誤代碼SIG_ERR。下面來看一個簡單的例子: 

#include <signal.h> 
#include <unistd.h> 
#include <stdio.h> 
void sigroutine(int dunno) { /* 信號處理例程,其中dunno將會得到信號的值 */ 
switch (dunno) { 
case 1: 
printf("Get a signal -- SIGHUP "); 
break; 
case 2: 
printf("Get a signal -- SIGINT "); 
break; 
case 3: 
printf("Get a signal -- SIGQUIT "); 
break; 

return; 


int main() { 
printf("process id is %d ",getpid()); 
signal(SIGHUP, sigroutine); //* 下面設置三個信號的處理方法 
signal(SIGINT, sigroutine); 
signal(SIGQUIT, sigroutine); 
for (;;) ; 


其中信號SIGINT由按下Ctrl-C發出,信號SIGQUIT由按下Ctrl-發出。該程序執行的結果如下: 

localhost:~$ ./sig_test 
process id is 463 
Get a signal -SIGINT //按下Ctrl-C得到的結果 
Get a signal -SIGQUIT //按下Ctrl-得到的結果 
//按下Ctrl-z將進程置於後臺 
[1]+ Stopped ./sig_test 
localhost:~$ bg 
[1]+ ./sig_test & 
localhost:~$ kill -HUP 463 //向進程發送SIGHUP信號 
localhost:~$ Get a signal – SIGHUP 
kill -9 463 //向進程發送SIGKILL信號,終止進程 
localhost:~$ 

2、kill 系統調用 

系統調用kill用來向進程發送一個信號。該調用聲明的格式如下: 
int kill(pid_t pid, int sig); 
在使用該調用的進程中加入以下頭文件: 
#include <sys/types.h> 
#include <signal.h> 

該 系統調用可以用來向任何進程或進程組發送任何信號。如果參數pid是正數,那麼該調用將信號sig發送到進程號爲pid的進程。如果pid等於0,那麼信 號sig將發送給當前進程所屬進程組裏的所有進程。如果參數pid等於-1,信號sig將發送給除了進程1和自身以外的所有進程。如果參數pid小於- 1,信號sig將發送給屬於進程組-pid的所有進程。如果參數sig爲0,將不發送信號。該調用執行成功時,返回值爲0;錯誤時,返回-1,並設置相應 的錯誤代碼errno。下面是一些可能返回的錯誤代碼: 
EINVAL:指定的信號sig無效。 
ESRCH:參數pid指定的進程或進程組不存在。注意,在進程表項中存在的進程,可能是一個還沒有被wait收回,但已經終止執行的僵死進程。 
EPERM: 進程沒有權力將這個信號發送到指定接收信號的進程。因爲,一個進程被允許將信號發送到進程pid時,必須擁有root權力,或者是發出調用的進程的UID 或EUID與指定接收的進程的UID或保存用戶ID(savedset-user-ID)相同。如果參數pid小於-1,即該信號發送給一個組,則該錯誤 表示組中有成員進程不能接收該信號。 

3、pause系統調用 

系統調用pause的作用是等待一個信號。該調用的聲明格式如下: 
int pause(void); 
在使用該調用的進程中加入以下頭文件: 
#include <unistd.h> 

該調用使得發出調用的進程進入睡眠,直到接收到一個信號爲止。該調用總是返回-1,並設置錯誤代碼爲EINTR(接收到一個信號)。下面是一個簡單的範例: 

#include <unistd.h> 
#include <stdio.h> 
#include <signal.h> 
void sigroutine(int unused) { 
printf("Catch a signal SIGINT "); 


int main() { 
signal(SIGINT, sigroutine); 
pause(); 
printf("receive a signal "); 


在這個例子中,程序開始執行,就象進入了死循環一樣,這是因爲進程正在等待信號,當我們按下Ctrl-C時,信號被捕捉,並且使得pause退出等待狀態。 

4、alarm和 setitimer系統調用 

系統調用alarm的功能是設置一個定時器,當定時器計時到達時,將發出一個信號給進程。該調用的聲明格式如下: 
unsigned int alarm(unsigned int seconds); 
在使用該調用的進程中加入以下頭文件: 
#include <unistd.h> 

系 統調用alarm安排內核爲調用進程在指定的seconds秒後發出一個SIGALRM的信號。如果指定的參數seconds爲0,則不再發送 SIGALRM信號。後一次設定將取消前一次的設定。該調用返回值爲上次定時調用到發送之間剩餘的時間,或者因爲沒有前一次定時調用而返回0。 

注意,在使用時,alarm只設定爲發送一次信號,如果要多次發送,就要多次使用alarm調用。 

對於alarm,這裏不再舉例。現在的系統中很多程序不再使用alarm調用,而是使用setitimer調用來設置定時器,用getitimer來得到定時器的狀態,這兩個調用的聲明格式如下: 
int getitimer(int which, struct itimerval *value); 
int setitimer(int which, const struct itimerval *value, struct itimerval*ovalue); 
在使用這兩個調用的進程中加入以下頭文件: 
#include <sys/time.h> 

該系統調用給進程提供了三個定時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信號給進程,並使得計時器重新開始。三個計時器由參數which指定,如下所示: 
TIMER_REAL:按實際時間計時,計時到達將給進程發送SIGALRM信號。 
ITIMER_VIRTUAL:僅當進程執行時才進行計時。計時到達將發送SIGVTALRM信號給進程。 
ITIMER_PROF:當進程執行時和系統爲該進程執行動作時都計時。與ITIMER_VIR-TUAL是一對,該定時器經常用來統計進程在用戶態和內核態花費的時間。計時到達將發送SIGPROF信號給進程。 

定時器中的參數value用來指明定時器的時間,其結構如下: 
struct itimerval { 
struct timeval it_interval; /* 下一次的取值 */ 
struct timeval it_value; /* 本次的設定值 */ 
}; 

該結構中timeval結構定義如下: 
struct timeval { 
long tv_sec; /* 秒 */ 
long tv_usec; /* 微秒,1秒 = 1000000 微秒*/ 
}; 

在setitimer 調用中,參數ovalue如果不爲空,則其中保留的是上次調用設定的值。定時器將it_value遞減到0時,產生一個信號,並將it_value的值設 定爲it_interval的值,然後重新開始計時,如此往復。當it_value設定爲0時,計時器停止,或者當它計時到期,而it_interval 爲0時停止。調用成功時,返回0;錯誤時,返回-1,並設置相應的錯誤代碼errno: 
EFAULT:參數value或ovalue是無效的指針。 
EINVAL:參數which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。 

下面是關於setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM信號: 

#include <signal.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <sys/time.h> 
int sec; 

void sigroutine(int signo) { 
switch (signo) { 
case SIGALRM: 
printf("Catch a signal -- SIGALRM "); 
break; 
case SIGVTALRM: 
printf("Catch a signal -- SIGVTALRM "); 
break; 

return; 


int main() { 
struct itimerval value,ovalue,value2; 
sec = 5; 

printf("process id is %d ",getpid()); 
signal(SIGALRM, sigroutine); 
signal(SIGVTALRM, sigroutine); 

value.it_value.tv_sec = 1; 
value.it_value.tv_usec = 0; 
value.it_interval.tv_sec = 1; 
value.it_interval.tv_usec = 0; 
setitimer(ITIMER_REAL, &value, &ovalue); 

value2.it_value.tv_sec = 0; 
value2.it_value.tv_usec = 500000; 
value2.it_interval.tv_sec = 0; 
value2.it_interval.tv_usec = 500000; 
setitimer(ITIMER_VIRTUAL, &value2, &ovalue); 

for (;;) ; 


該例子的屏幕拷貝如下: 

localhost:~$ ./timer_test 
process id is 579 
Catch a signal – SIGVTALRM 
Catch a signal – SIGALRM 
Catch a signal – SIGVTALRM 
Catch a signal – SIGVTALRM 
Catch a signal – SIGALRM 
Catch a signal –GVTALRM 

本文簡單介紹了Linux下的信號,如果希望瞭解其他調用,請參考聯機手冊或其他文檔。

 

 

i++是否原子操作?並解釋爲什麼?

說出你所知道的linux系統的各類同步機制(重點),什麼是死鎖?如何避免死鎖(每個技術面試官必問)

死鎖的條件。(互斥條件Mutualexclusion):1資源不能被共享,只能由一個進程使用。2、請求與保持條件(Hold andwait):已經得到資源的進程可以再次申請新的資源。3、非剝奪條件(Nopre-emption):已經分配的資源不能從相應的進程中被強制地剝奪。4、循環等待條件(Circularwait):系統中若干進程組成環路,該環路中每個進程都在等待相鄰進程正佔用的資源。處理死鎖的策略1.忽略該問題。例如鴕鳥算法,該算法可以應用在極少發生死鎖的的情況下。爲什麼叫鴕鳥算法呢,因爲傳說中鴕鳥看到危險就把頭埋在地底下,可能鴕鳥覺得看不到危險也就沒危險了吧。跟掩耳盜鈴有點像。2.檢測死鎖並且恢復。3.仔細地對資源進行動態分配,以避免死鎖。4.通過破除死鎖四個必要條件之一,來防止死鎖產生。

列舉說明linux系統的各類異步機制

exit()_exit()的區別?

_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩衝輸出內容將刷新定義,並調用所有已刷新的“出口函數”(由atexit定義)。

‘exit()’與‘_exit()’有不少區別在使用‘fork()’,特別是‘vfork()’時變得很突出。

 ‘exit()’與‘_exit()’的基本區別在於前一個調用實施與調用庫裏用戶狀態結構(user-mode constructs)有關的清除工作(clean-up),而且調用用戶自定義的清除程序

如何實現守護進程?

守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。守護進程是一種很有用的進程。 Linux的大多數服務器就是用守護進程實現的。比如,Internet服務器inetd,Web服務器httpd等。同時,守護進程完成許多系統任務。比如,作業規劃進程crond,打印進程lpd等。

守護進程的編程本身並不複雜,複雜的是各種版本的Unix的實現機制不盡相同,造成不同 Unix環境下守護進程的編程規則並不一致。需要注意,照搬某些書上的規則(特別是BSD4.3和低版本的System V)到Linux會出現錯誤的。下面將給出Linux下守護進程的編程要點和詳細實例。

一. 守護進程及其特性

守護進程最重要的特性是後臺運行。在這一點上DOS下的常駐內存程序TSR與之相似。其次,守護進程必須與其運行前的環境隔離開來。這些環境包括未關閉的文件描述符,控制終端,會話和進程組,工作目錄以及文件創建掩模等。這些環境通常是守護進程從執行它的父進程(特別是shell)中繼承下來的。最後,守護進程的啓動方式有其特殊之處。它可以在Linux系統啓動時從啓動腳本/etc/rc.d中啓動,可以由作業規劃進程crond啓動,還可以由用戶終端(shell)執行。

總之,除開這些特殊性以外,守護進程與普通進程基本上沒有什麼區別。因此,編寫守護進程實際上是把一個普通進程按照上述的守護進程的特性改造成爲守護進程。如果對進程有比較深入的認識就更容易理解和編程了。

二. 守護進程的編程要點

前面講過,不同Unix環境下守護進程的編程規則並不一致。所幸的是守護進程的編程原則其實都一樣,區別在於具體的實現細節不同。這個原則就是要滿足守護進程的特性。同時,Linux是基於Syetem V的SVR4並遵循Posix標準,實現起來與BSD4相比更方便。編程要點如下;

1. 在後臺運行。

爲避免掛起控制終端將Daemon放入後臺執行。方法是在進程中調用fork使父進程終止,讓Daemon在子進程中後臺執行。

if(pid=fork())
exit(0); //是父進程,結束父進程,子進程繼續
2. 脫離控制終端,登錄會話和進程組

有必要先介紹一下Linux中的進程與控制終端,登錄會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創建進程的登錄終端。控制終端,登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第1點的基礎上,調用setsid()使進程成爲會話組長:

setsid();

說明:當進程是會話組長時setsid()調用失敗。但第一點已經保證進程不是會話組長。setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離。由於會話過程對控制終端的獨佔性,進程同時與控制終端脫離。

3. 禁止進程重新打開控制終端

現在,進程已經成爲無終端的會話組長。但它可以重新申請打開一個控制終端。可以通過使進程不再成爲會話組長來禁止進程重新打開控制終端:

if(pid=fork()) exit(0); //結束第一子進程,第二子進程繼續(第二子進程不再是會話組長)

4. 關閉打開的文件描述符

進程從創建它的父進程那裏繼承了打開的文件描述符。如不關閉,將會浪費系統資源,造成進程所在的文件系統無法卸下以及引起無法預料的錯誤。按如下方法關閉它們:

for(i=0;i 關閉打開的文件描述符close(i);>

5. 改變當前工作目錄

進程活動時,其工作目錄所在的文件系統不能卸下。一般需要將工作目錄改變到根目錄。對於需要轉儲核心,寫運行日誌的進程將工作目錄改變到特定目錄如 /tmpchdir("/")

6. 重設文件創建掩模

進程從創建它的父進程那裏繼承了文件創建掩模。它可能修改守護進程所創建的文件的存取位。爲防止這一點,將文件創建掩模清除:umask(0);

7. 處理SIGCHLD信號

處理SIGCHLD信號並不是必須的。但對於某些進程,特別是服務器進程往往在請求到來時生成子進程處理請求。如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。如果父進程等待子進程結束,將增加父進程的負擔,影響服務器進程的併發性能。在Linux下可以簡單地將 SIGCHLD信號的操作設爲SIG_IGN。

signal(SIGCHLD,SIG_IGN);

這樣,內核在子進程結束時不會產生殭屍進程。這一點與BSD4不同,BSD4下必須顯式等待子進程結束才能釋放殭屍進程。

三. 守護進程實例

守護進程實例包括兩部分:主程序test.c和初始化程序init.c。主程序每隔一分鐘向/tmp目錄中的日誌test.log報告運行狀態。初始化程序中的init_daemon函數負責生成守護進程。讀者可以利用init_daemon函數生成自己的守護進程。

 

linux的內存管理機制是什麼?

Linux虛擬內存的實現需要6種機制的支持:地址映射機制、內存分配回收機制、緩存和刷新機制、請求頁機制、交換機制和內存共享機制

內存管理程序通過映射機制把用戶程序的邏輯地址映射到物理地址。當用戶程序運行時,如果發現程序中要用的虛地址沒有對應的物理內存,就發出了請求頁要求。如果有空閒的內存可供分配,就請求分配內存(於是用到了內存的分配和回收),並把正在使用的物理頁記錄在緩存中(使用了緩存機制)。如果沒有足夠的內存可供分配,那麼就調用交換機制;騰出一部分內存。另外,在地址映射中要通過TLB(翻譯後援存儲器)來尋找物理頁;交換機制中也要用到交換緩存,並且把物理頁內容交換到交換文件中,也要修改頁表來映射文件地址。

linux的任務調度機制是什麼?

在每個進程的task_struct結構中有以下四項:policyprioritycounterrt_priority。這四項是選擇進程的依據。其中,policy是進程的調度策略,用來區分實時進程和普通進程,實時進程優先於普通進程運行;priority是進程(包括實時和普通)的靜態優先級;counter是進程剩餘的時間片,它的起始值就是priority的值;由於counter在後面計算一個處於可運行狀態的進程值得運行的程度goodness時起重要作用,因此,counter 也可以看作是進程的動態優先級。rt_priority是實時進程特有的,用於實時進程間的選擇。 
Linux
用函數goodness()來衡量一個處於可運行狀態的進程值得運行的程度。該函數綜合了以上提到的四項,還結合了一些其他的因素,給每個處於可運行狀態的進程賦予一個權值(weight),調度程序以這個權值作爲選擇進程的唯一依據。關於goodness()的情況在後面將會詳細分析。

五種I/O 模式——

阻塞(默認IO模式),

非阻塞(常用於管道),

I/O多路複用(IO多路複用的應用場景),

信號I/O

異步I/O  

五種I/O 模式:
1       阻塞I/O           (Linux下的I/O操作默認是阻塞I/O,即opensocket創建的I/O都是阻塞I/O)
2       非阻塞 I/O        (可以通過fcntl或者open時使用O_NONBLOCK參數,將fd設置爲非阻塞的I/O)
3       I/O 多路複用     (I/O多路複用,通常需要非阻塞I/O配合使用)
4       信號驅動 I/O    (SIGIO)
5        異步 I/O

 

一般來說,程序進行輸入操作有兩步:
1.等待有數據可以讀
2
.將數據從系統內核中拷貝到程序的數據區。

對於sock編程來說:

         第一步:   一般來說是等待數據從網絡上傳到本地。當數據包到達的時候,數據將會從網絡層拷貝到內核的緩存中;

         第二步:   是從內核中把數據拷貝到程序的數據區中。

 

阻塞I/O模式                           //進程處於阻塞模式時,讓出CPU,進入休眠狀態
        阻塞 I/O 模式是最普遍使用的 I/O 模式。Linux系統下缺省的IO模式。

       大部分程序使用的都是阻塞模式的 I/O 

       一個套接字建立後所處於的模式就是阻塞 I/O 模式。(因爲Linux系統默認的IO模式是阻塞模式)


對於一個UDP 套接字來說,數據就緒的標誌比較簡單:
1)已經收到了一整個數據報
2)沒有收到。
 TCP 這個概念就比較複雜,需要附加一些其他的變量。

       一個進程調用 recvfrom  ,然後系統調用並不返回知道有數據報到達本地系統,然後系統將數據拷貝到進程的緩存中。(如果系統調用收到一箇中斷信號,則它的調用會被中斷)

   我們稱這個進程在調用recvfrom一直到從recvfrom返回這段時間是阻塞的。recvfrom正常返回時,我們的進程繼續它的操作。

 

 

 

 

 

非阻塞模式I/O                          //非阻塞模式的使用並不普遍,因爲非阻塞模式會浪費大量的CPU資源。
       當我們將一個套接字設置爲非阻塞模式,我們相當於告訴了系統內核: “當我請求的I/O 操作不能夠馬上完成,你想讓我的進程進行休眠等待的時候,不要這麼做,請馬上返回一個錯誤給我。
      
我們開始對 recvfrom 的三次調用,因爲系統還沒有接收到網絡數據,所以內核馬上返回一個EWOULDBLOCK的錯誤。

      第四次我們調用 recvfrom 函數,一個數據報已經到達了,內核將它拷貝到我們的應用程序的緩衝區中,然後recvfrom 正常返回,我們就可以對接收到的數據進行處理了。
      
當一個應用程序使用了非阻塞模式的套接字,它需要使用一個循環來不聽的測試是否一個文件描述符有數據可讀(稱做 polling(輪詢))應用程序不停的 polling 內核來檢查是否 I/O操作已經就緒。這將是一個極浪費 CPU資源的操作。這種模式使用中不是很普遍。

 

 例如:

         對管道的操作,最好使用非阻塞方式!

 

 

 

 

I/O多路複用                            //針對批量IP操作時,使用I/O多路複用,非常有好。

       在使用 I/O 多路技術的時候,我們調用select()函數和 poll()函數或epoll函數(2.6內核開始支持),在調用它們的時候阻塞,而不是我們來調用 recvfrom(或recv)的時候阻塞。
       
當我們調用 select函數阻塞的時候,select 函數等待數據報套接字進入讀就緒狀態。當select函數返回的時候,也就是套接字可以讀取數據的時候。這時候我們就可以調用 recvfrom函數來將數據拷貝到我們的程序緩衝區中。
        
對於單個I/O操作,和阻塞模式相比較,select()poll()epoll並沒有什麼高級的地方

       而且,在阻塞模式下只需要調用一個函數:

                            讀取或發送函數。

                  在使用了多路複用技術後,我們需要調用兩個函數了:

                             先調用 select()函數或poll()函數,然後才能進行真正的讀寫。

       多路複用的高級之處在於::

             它能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。

 

IO 多路技術一般在下面這些情況中被使用:
1
、當一個客戶端需要同時處理多個文件描述符的輸入輸出操作的時候(一般來說是標準的輸入輸出和網絡套接字)I/O多路複用技術將會有機會得到使用。
2
、當程序需要同時進行多個套接字的操作的時候。
3
、如果一個 TCP 服務器程序同時處理正在偵聽網絡連接的套接字已經連接好的套接字
4
、如果一個服務器程序同時使用 TCP  UDP 協議
5
、如果一個服務器同時使用多種服務並且每種服務可能使用不同的協議(比如 inetd就是這樣的)。

 

 

 

異步IO模式有::

      1、信號驅動I/O模式

       2、異步I/O模式

信號驅動I/O模式                                                  //自己沒有用過。

       我們可以使用信號,讓內核在文件描述符就緒的時候使用 SIGIO 信號來通知我們。我們將這種模式稱爲信號驅動I/O 模式。

爲了在一個套接字上使用信號驅動 I/O 操作,下面這三步是所必須的。
1)一個和 SIGIO信號的處理函數必須設定。
2)套接字的擁有者必須被設定。一般來說是使用 fcntl 函數的 F_SETOWN 參數來
進行設定擁有者。
3)套接字必須被允許使用異步 I/O。一般是通過調用 fcntl 函數的 F_SETFL 命令,O_ASYNC爲參數來實現。

       雖然設定套接字爲異步 I/O 非常簡單,但是使用起來困難的部分是怎樣在程序中斷定產生 SIGIO信號發送給套接字屬主的時候,程序處在什麼狀態。

1UDP 套接字的 SIGIO 信號                   (比較簡單)
 UDP 協議上使用異步 I/O 非常簡單.這個信號將會在這個時候產生:

1、套接字收到了一個數據報的數據包。
2
、套接字發生了異步錯誤。
        
當我們在使用 UDP 套接字異步 I/O 的時候,我們使用 recvfrom()函數來讀取數據報數據或是異步 I/O 錯誤信息。
2TCP 套接字的 SIGIO 信號                  (不會使用)
          
不幸的是,異步 I/O 幾乎對 TCP 套接字而言沒有什麼作用。因爲對於一個 TCP 套接字來說,SIGIO 信號發生的機率太高了,所以 SIGIO 信號並不能告訴我們究竟發生了什麼事情。

 TCP 連接中, SIGIO 信號將會在這個時候產生:
l  
在一個監聽某個端口的套接字上成功的建立了一個新連接。
l  
一個斷線的請求被成功的初始化。
l  
一個斷線的請求成功的結束。
l  
套接字的某一個通道(發送通道或是接收通道)被關閉。
l  
套接字接收到新數據。
l  
套接字將數據發送出去。

l  發生了一個異步 I/O 的錯誤。

一個對信號驅動 I/O 比較實用的方面是NTP(網絡時間協議 Network TimeProtocol)服務器,它使用 UDP。這個服務器的主循環用來接收從客戶端發送過來的數據報數據包,然後再發送請求。對於這個服務器來說,記錄下收到每一個數據包的具體時間是很重要的。

因爲那將是返回給客戶端的值,客戶端要使用這個數據來計算數據報在網絡上來回所花費的時間。圖 6-8 表示了怎樣建立這樣的一個 UDP 服務器。

 

 

異步I/O模式             //比如寫操作,只需用寫,不一定寫入磁盤(這就是異步I/O)的好處。異步IO的好處效率高。
      
當我們運行在異步 I/O 模式下時,我們如果想進行 I/O 操作,只需要告訴內核我們要進行 I/O 操作,然後內核會馬上返回。具體的 I/O 和數據的拷貝全部由內核來完成,我們的程序可以繼續向下執行。當內核完成所有的 I/O 操作和數據拷貝後,內核將通知我們的程序。
異步 I/O   信號驅動I/O的區別是:
        1
、信號驅動 I/O 模式下,內核在操作可以被操作的時候通知給我們的應用程序發送SIGIO 消息。

        2、異步 I/O 模式下,內核在所有的操作都已經被內核操作結束之後纔會通知我們的應用程序。

selectpollepoll

. Epoll 是何方神聖?

Epoll 可是當前在 Linux 下開發大規模併發網絡程序的熱門人選, Epoll  Linux2.6 內核中正式引入,和 select 相似,其實都 I/O 多路複用技術而已,並沒有什麼神祕的。

其實在Linux 下設計併發網絡程序,向來不缺少方法,比如典型的 Apache 模型( Process Per Connection ,簡稱PPC), TPC  ThreadPer Connection )模型,以及 select 模型和 poll 模型,那爲何還要再引入 Epoll 這個東東呢?那還是有得說說的 …

2. 常用模型的缺點

如果不擺出來其他模型的缺點,怎麼能對比出 Epoll 的優點呢。

2.1 PPC/TPC 模型

這兩種模型思想類似,就是讓每一個到來的連接一邊自己做事去,別再來煩我。只是 PPC 是爲它開了一個進程,而TPC 開了一個線程。可是別煩我是有代價的,它要時間和空間啊,連接多了之後,那麼多的進程 / 線程切換,這開銷就上來了;因此這類模型能接受的最大連接數都不會高,一般在幾百個左右。

2.2 select 模型

1. 最大併發數限制,因爲一個進程所打開的 FD (文件描述符)是有限制的,www.linuxidc.com FD_SETSIZE 設置,默認值是 1024/2048 ,因此 Select 模型的最大併發數就被相應限制了。自己改改這個 FD_SETSIZE ?想法雖好,可是先看看下面吧 …

2. 效率問題, select 每次調用都會線性掃描全部的 FD 集合,這樣效率就會呈現線性下降,把 FD_SETSIZE 改大的後果就是,大家都慢慢來,什麼?都超時了??!!

3. 內核 / 用戶空間內存拷貝問題,如何讓內核把 FD 消息通知給用戶空間呢?在這個問題上 select 採取了內存拷貝方法。

2.3 poll 模型

基本上效率和select 是相同的,select 缺點的 2  3 它都沒有改掉。

3. Epoll 的提升

把其他模型逐個批判了一下,再來看看 Epoll 的改進之處吧,其實把 select 的缺點反過來那就是 Epoll 的優點了。

3.1. Epoll 沒有最大併發連接的限制,上限是最大可以打開文件的數目,這個數字一般遠大於 2048, 一般來說這個數目和系統內存關係很大,具體數目可以 cat /proc/sys/fs/file-max 察看。

3.2. 效率提升, Epoll 最大的優點就在於它只管你活躍的連接,而跟連接總數無關,因此在實際的網絡環境中, Epoll的效率就會遠遠高於 select  poll 

3.3. 內存拷貝, Epoll 在這點上使用了共享內存 ”,這個內存拷貝也省略了。


4. Epoll 
爲什麼高效

Epoll 的高效和其數據結構的設計是密不可分的,這個下面就會提到。

首先回憶一下select 模型,當有I/O 事件到來時,select 通知應用程序有事件到了快去處理,而應用程序必須輪詢所有的FD 集合,測試每個 FD 是否有事件發生,並處理事件;代碼像下面這樣:

int res = select(maxfd+1, &readfds,NULL, NULL, 120);

if (res > 0)

{

    for (int i = 0; i <MAX_CONNECTION; i++)

    {

       if (FD_ISSET(allConnection[i], &readfds))

       {

           handleEvent(allConnection[i]);

       }

    }

}

// if(res == 0) handle timeout, res < 0handle error


Epoll 
不僅會告訴應用程序有I/0事件到來,還會告訴應用程序相關的信息,這些信息是應用程序填充的,因此根據這些信息應用程序就能直接定位到事件,而不必遍歷整個FD 集合。

int res = epoll_wait(epfd, events, 20,120);

for (int i = 0; i < res;i++)

{

    handleEvent(events[n]);

}

5. Epoll 關鍵數據結構

前面提到Epoll 速度快和其數據結構密不可分,其關鍵數據結構就是:

struct epoll_event {

    __uint32_tevents;      // Epoll events

    epoll_data_tdata;      // User data variable

};

typedef union epoll_data {

    void *ptr;

    int fd;

    __uint32_t u32;

    __uint64_t u64;

} epoll_data_t;

可見epoll_data 是一個 union 結構體 , 藉助於它應用程序可以保存很多類型的信息 :fd 、指針等等。有了它,應用程序就可以直接定位目標了。

 

標準庫函數和系統調用的區別?

1、系統調用和庫函數的關係

系統調用通過軟中斷int 0x80從用戶態進入內核態。

函數庫中的某些函數調用了系統調用。

函數庫中的函數可以沒有調用系統調用,也可以調用多個系統調用。

編程人員可以通過函數庫調用系統調用。

高級編程也可以直接採用int 0x80進入系統調用,而不必通過函數庫作爲中介。

如果是在覈心編程,也可以通過int 0x80進入系統調用,此時不能使用函數庫。因爲函數庫中的函數是內核訪問不到的。

2、從用戶調用庫函數到系統調用執行的流程。

1) 假設用戶調用ssize_t write (int fields, cont void *buff, size_tnbytes);庫函數。

2) 庫函數會執行int 0x80中斷。因爲中斷使得進程從用戶態進入內核態,所以參數通過寄存器傳送。

3) 0x80中斷對應的中斷例程被稱爲system call handler。其工作是:

 i.  存儲大多數寄存器到內核堆棧中。這是彙編代碼寫的。

 ii.  執行真正的系統調用函數――system callservice routine。這是C代碼。

 iii. 通過ret_from_sys_call()返回,回到用戶態的庫函數。這是彙編代碼。

1、系統調用

系統調用提供的函數如open, close, read, write, ioctl等,需包含頭文件unistd.h。以write爲例:其函數原型爲 size_t write(int fd, const void *buf, size_t nbytes),其操作對象爲文件描述符或文件句柄fd(file descriptor),要想寫一個文件,必須先以可寫權限用open系統調用打開一個文件,獲得所打開文件的fd,例如fd=open(/"/dev/video/", O_RDWR)。fd是一個整型值,每新打開一個文件,所獲得的fd爲當前最大fd加1。Linux系統默認分配了3個文件描述符值:0-standard input,1-standard output,2-standard error。

系統調用通常用於底層文件訪問(low-level file access),例如在驅動程序中對設備文件的直接訪問。

系統調用是操作系統相關的,因此一般沒有跨操作系統的可移植性。

系統調用發生在內核空間,因此如果在用戶空間的一般應用程序中使用系統調用來進行文件操作,會有用戶空間到內核空間切換的開銷。事實上,即使在用戶空間使用庫函數來對文件進行操作,因爲文件總是存在於存儲介質上,因此不管是讀寫操作,都是對硬件(存儲器)的操作,都必然會引起系統調用。也就是說,庫函數對文件的操作實際上是通過系統調用來實現的。例如C庫函數fwrite()就是通過write()系統調用來實現的。

這樣的話,使用庫函數也有系統調用的開銷,爲什麼不直接使用系統調用呢?這是因爲,讀寫文件通常是大量的數據(這種大量是相對於底層驅動的系統調用所實現的數據操作單位而言),這時,使用庫函數就可以大大減少系統調用的次數。這一結果又緣於緩衝區技術。在用戶空間和內核空間,對文件操作都使用了緩衝區,例如用fwrite寫文件,都是先將內容寫到用戶空間緩衝區,當用戶空間緩衝區滿或者寫操作結束時,纔將用戶緩衝區的內容寫到內核緩衝區,同樣的道理,當內核緩衝區滿或寫結束時纔將內核緩衝區內容寫到文件對應的硬件媒介。

2、庫函數調用

標準C庫函數提供的文件操作函數如fopen, fread, fwrite, fclose,fflush, fseek等,需包含頭文件stdio.h。以fwrite爲例,其函數原型爲size_t fwrite(const void *buffer,size_t size, size_t item_num, FILE *pf),其操作對象爲文件指針FILE *pf,要想寫一個文件,必須先以可寫權限用fopen函數打開一個文件,獲得所打開文件的FILE結構指針pf,例如pf=fopen(/"~/proj/filename/",/"w/")。實際上,由於庫函數對文件的操作最終是通過系統調用實現的,因此,每打開一個文件所獲得的FILE結構指針都有一個內核空間的文件描述符fd與之對應。同樣有相應的預定義的FILE指針:stdin-standard input,stdout-standard output,stderr-standard error。

庫函數調用通常用於應用程序中對一般文件的訪問。

庫函數調用是系統無關的,因此可移植性好。

由於庫函數調用是基於C庫的,因此也就不可能用於內核空間的驅動程序中對設備的操作

ping命令所利用的原理是這樣的:網絡上的機器都有唯一確定的IP地址,我們給目標IP地址發送一個數據包,對方就要返回一個同樣大小的數據包,根據返回的數據包我們可以確定目標主機的存在,可以初步判斷目標主機的操作系統等。

補充一個坑爹坑爹坑爹坑爹的問題:系統如何將一個信號通知到進程?

信號機制是異步的;當一個進程接收到一個信號時,它會立刻處理這個信號,而不會等待當前函數甚至當前一行代碼結束運行。信號有幾十種,分別代表着不同的意義。信號之間依靠它們的值來區分,但是通常在程序中使用信號的名字來表示一個信號。在Linux系統中,這些信號和以它們的名稱命名的常量均定義在/usr/include/bits/signum.h文件中。(通常程序中不需要直接包含這個頭文件,而應該包含<signal.h>。)

信號事件的發生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發送信號的系統函數是kill, raise, alarmsetitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。

發送信號的主要函數有:kill()raise() sigqueue()alarm()setitimer()以及abort()

 

進程可以通過三種方式來響應一個信號:(1)忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILLSIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操作,

 c語言:

宏定義和展開(必須精通)

#defineC語言中提供的宏定義命令,其主要目的是爲程序員在編程時提供一定的方便,並能在一定程度上提高程序的運行效率,但學生在學習時往往不能理解該命令的本質,總是在此處產生一些困惑,在編程時誤用該命令,使得程序的運行與預期的目的不一致,或者在讀別人寫的程序時,把運行結果理解錯誤,這對C語言的學習很不利。
1
#define命令剖析 
1.1   #define
的概念
#define
命令是C語言中的一個宏定義命令,它用來將一個標識符定義爲一個字符串,該標識符被稱爲宏名,被定義的字符串稱爲替換文本。
該命令有兩種格式:一種是簡單的宏定義,另一種是帶參數的宏定義。
(1)   
簡單的宏定義:
#define   <
宏名><字符串
   
例:  #define PI 3.1415926 
(2) 
帶參數的宏定義
   #define   <
宏名> (<參數表>)  <宏體>
   
例:#define   A(x) x 
一個標識符被宏定義後,該標識符便是一個宏名。這時,在程序中出現的是宏名,在該程序被編譯前,先將宏名用被定義的字符串替換,這稱爲宏替換,替換後才進行編譯,宏替換是簡單的替換。
1.2 
宏替換髮生的時機
爲了能夠真正理解#define的作用,讓我們來了解一下對C語言源程序的處理過程。當我們在一個集成的開發環境如TurboC中將編寫好的源程序進行編譯時,實際經過了預處理、編譯、彙編和連接幾個過程

 

 

這裏要說一下宏的展開次序,如果宏有參數,如TO_STRING_MACRO(x)中的x,我們稱之爲形參,而宏實際的參數我們稱之爲實參,如TO_STRING_MACRO(a)中的a

宏的展開是個很複雜的過程,但可以用以下三步來簡單描述,

首先用實參替換形參,將實參代入宏文本中;

然後如果實參也是宏,則展開實參;

最後再繼續處理宏替換後的宏文本,若宏文本也包含宏則繼續展開,否則完成展開。

但是有個例外,那就是第一步後,將實參代入宏文本後,實參之前如果遇到字符“#”“##”,即使實參是宏,也不再展開實參,而是當作文本處理。

 

位操作(必須精通)

在計算機程序中,數據的位是可以操作的最小數據單位,理論上可以用位運算來完成所有的運算和操作。一般的位操作是用來控制硬件的,或者做數據變換使用,但是,靈活的位操作可以有效地提高程序運行的效率。C語言提供了位運算的功能,這使得C語言也能像彙編語言一樣用來編寫系統程序。

  位運算符C語言提供了六種位運算符:

按位與

按位或

按位異或

取反

<< 左移

>> 右移

 

指針操作和計算(必須精通)

&是地址操作符,用來引用一個內存地址。通過在變量名字前使用&操作符,我們可以得到該變量的內存地址。

1

2

3

4

5

6

7

8

9

// 聲明一個int指針

int*ptr;

// 聲明一個int

intval = 1;

// 爲指針分配一個int值的引用

ptr = &val;

// 對指針進行取值,打印存儲在指針地址中的內容

intderef = *ptr;

printf("%d\n", deref);

指針是一個存儲計算機內存地址的變量。在這份教程裏“引用”表示計算機內存地址。從指針指向的內存讀取數據稱作指針的取值。指針可以指向某些具體類型的變量地址,例如 int、long 和 double。指針也可以是 void 類型、NULL 指針和未初始化指針。本文會對上述所有指針類型進行探討。

  根據出現的位置不同,操作符 * 既可以用來聲明一個指針變量,也可以用作指針的取值。當用在聲明一個變量時,*表示這裏聲明瞭一個指針。其它情況用到*表示指針的取值。

  &是地址操作符,用來引用一個內存地址。通過在變量名字前使用&操作符,我們可以得到該變量的內存地址。

語言中指針的15個問題 
aqiaoboy 

1 指針的四要素 
  1指針變量,表示一個內存地址,通常爲邏輯地址,與實際的物理地址還有一個映射關係. 
2指針變量的長度,在WIN32下爲四個字節, 
  3指針指向的變量 
   該內存地址空間下存放的變量,具體內容可能是各種類型的變量. 
  4 指針指向的變量的長度,以該內存地址空間開始的內存空間大小. 
2 Const,volatile修飾指針的含義 
const    char *cpch=”hello’; 
表示指針指向的變量不可改變,但指針本身是可以改變的 
char * const    pchc; 
指針指向的變量可以改變,但指針本身不可改變. 

Const  char * const pchc; 
兩者都不可變. 
3 堆和棧上的指針 
指針所指向的這塊內存是在哪裏分配的,在堆上稱爲堆上的指針,在棧上爲棧上的指針. 
在堆上的指針,可以保存在全局數據結構中,供不同函數使用訪問同一塊內存. 
在棧上的指針,在函數退出後,該內存即不可訪問. 

4 什麼是指針的釋放? 
具體來說包括兩個概念. 
1 釋放該指針指向的內存,只有堆上的內存才需要我們手工釋放,棧上不需要. 
2 將該指針重定向爲NULL. 

5 near,far型指針的區別? 
老式的IBM PC兼容機纔有這種區別,因爲老式機不能完全支持32位指針, 
所以才分爲16位指針,(near),和32位指針(far) 
從386開始沒有這種區別,都是32位指針. 

6 數據結構中的指針? 
其實就是指向一塊內存的地址,通過指針傳遞,可實現複雜的內存訪問. 
7 函數指針? 
指向一塊函數的入口地址. 

8 指針作爲函數的參數? 
比如指向一個複雜數據結構的指針作爲函數變量 
這種方法避免整個複雜數據類型內存的壓棧出棧操作,提高效率. 
注意:指針本身不可變,但指針指向的數據結構可以改變. 

9 指向指針的指針? 
指針指向的變量是一個指針,即具體內容爲一個指針的值,是一個地址. 
此時指針指向的變量長度也是4位. 

10 指針與地址的區別? 
區別: 
1指針意味着已經有一個指針變量存在,他的值是一個地址,指針變量本身也存放在一個長度爲四個字節的地址當中,而地址概念本身並不代表有任何變量存在. 
2 指針的值,如果沒有限制,通常是可以變化的,也可以指向另外一個地址. 
   地址表示內存空間的一個位置點,他是用來賦給指針的,地址本身是沒有大小概念,指針指向變量的大小,取決於地址後面存放的變量類型. 

11 指針與數組名的關係? 
  其值都是一個地址,但前者是可以移動的,後者是不可變的. 

12 怎樣防止指針的越界使用問題? 
  必須讓指針指向一個有效的內存地址, 
1 防止數組越界 
2 防止向一塊內存中拷貝過多的內容 
3 防止使用空指針 
4 防止改變const修改的指針 
5 防止改變指向靜態存儲區的內容 
6 防止兩次釋放一個指針 
7 防止使用野指針. 


13 指針的類型轉換? 
指針轉換通常是指針類型和void * 類型之前進行強制轉換,從而與期望或返回void指針的函數進行正確的交接. 


14 什麼是指針退化? 
如果用一個數組作爲函數入參 
比如 
void fun(char a[100]) 

cout<<SIZEOF(A)<


15 指針的移動問題? 

指針P ++具體移動的字節數等於指針指向的變量類型大小. 



寫得還比較全的 
函數參數我都用指針沒有用數組 

14 什麼是指針退化? 
如果用一個數組作爲函數入參 
比如 
void fun(char a[100]) 

cout<<SIZEOF(A)<


像這樣定義也不能保證檢查傳進來的參數對不對 
[code] 
int a(char s[100]) 

    s[100]=1; 
    return 0; 


main() 

   char s[20]="hello"; 
   a(s); 

[/code]

內存分配(必須精通)

sizeof必考

內存分配方式有三種:

  (1)從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。

  (2)在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。

  (3)從堆上分配,亦稱動態內存分配。程序在運行的時候用mallocnew申請任意多少的內存,程序員自己負責在何時用freedelete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。

各類庫函數必須非常熟練的實現

 

哪些庫函數屬於高危函數,爲什麼?strcpy等等)

c++

一個String類的完整實現必須很快速寫出來(注意:賦值構造,operator=是關鍵)

虛函數的作用和實現原理(必問必考,實現原理必須很熟)

有虛函數的類內部有一個稱爲“虛表”的指針(有多少個虛函數就有多少個指針),這個就是用來指向這個類虛函數。也就是用它來確定調用該那個函數。

實際上在編譯的時候,編譯器會自動加入“虛表”。虛表的使用方法是這樣的:如果派生類在自己的定義中沒有修改基類的虛函數,就指向基類的虛函數;如果派生類改寫了基類的虛函數(就是自己重新定義),這時虛表則將原來指向基類的虛函數的地址替換爲指向自身虛函數的指針。那些被virtual關鍵字修飾的成員函數,就是虛函數。虛函數的作用,用專業術語來解釋就是實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異而採用不同的策略。

每個類都有自己的vtbl,vtbl的作用就是保存自己類中虛函數的地址,我們可以把vtbl形象地看成一個數組,這個數組的每個元素存放的就是虛函數的地址,

虛函數的效率低,其原因就是,在調用虛函數之前,還調用了獲得虛函數地址的代碼。

 

sizeof一個類求大小(注意成員變量,函數,虛函數,繼承等等對大小的影響)

指針和引用的區別(一般都會問到)

相同點1. 都是地址的概念;
指針指向一塊內存,它的內容是所指內存的地址;引用是某塊內存的別名。

區別:1. 指針是一個實體,而引用僅是個別名;

2. 引用使用時無需解引用(*),指針需要解引用;

3. 引用只能在定義時被初始化一次,之後不可變;指針可變;

4. 引用沒有 const,指針有 const

5. 引用不能爲空,指針可以爲空;

6. sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的地址)的大小;

7. 指針和引用的自增(++)運算意義不一樣;

8.從內存分配上看:程序爲指針變量分配內存區域,而引用不需要分配內存區域。

多重類構造和析構的順序

先調用基類的構造函數,在調用派生類的構造函數

先構造的後析構,後構造的先析構

stl各容器的實現原理(必考)

STL = Standard Template Library,標準模板庫

STL共有六大組件
 1
、容器。2、算法。3、迭代器。4、仿函數。6、適配器。

序列式容器:
vector-
數組,元素不夠時再重新分配內存,拷貝原來數組的元素到新分配的數組中。
list
-單鏈表。
deque-
分配中央控制器map(並非map容器)map記錄着一系列的固定長度的數組的地址.記住這個map僅僅保存的是數組的地址,真正的數據在數組中存放着.deque先從map中央的位置(因爲雙向隊列,前後都可以插入元素)找到一個數組地址,向該數組中放入數據,數組不夠時繼續在map中找空閒的數組來存數據。當map也不夠時重新分配內存當作新的map,把原來map中的內容copy的新map中。所以使用deque的複雜度要大於vector,儘量使用vector

stack-基於deque
queue-
基於deque
heap-
完全二叉樹,使用最大堆排序,以數組(vector)的形式存放。
priority_queue-
基於heap
slist-
雙向鏈表。

關聯式容器:
set,map,multiset,multimap-
基於紅黑樹(RB-tree),一種加上了額外平衡條件的二叉搜索樹。

hash table-散列表。將待存數據的key經過映射函數變成一個數組(一般是vector)的索引,例如:數據的key%數組的大小=數組的索引(一般文本通過算法也可以轉換爲數字),然後將數據當作此索引的數組元素。有些數據的key經過算法的轉換可能是同一個數組的索引值(碰撞問題,可以用線性探測,二次探測來解決)STL是用開鏈的方法來解決的,每一個數組的元素維護一個list,他把相同索引值的數據存入一個list,這樣當list比較短時執行刪除,插入,搜索等算法比較快。

hash_map,hash_set,hash_multiset,hash_multimap-基於hashtable

extern c 是幹啥的,(必須將編譯器的函數名修飾的機制解答的很透徹)

volatile是幹啥用的,(必須將cpu的寄存器緩存機制回答的很透徹)

volatile的本意是“易變的” 因爲訪問寄存器要比訪問內存單元快的多,所以編譯器一般都會作減少存取內存的優化,但有可能會讀髒數據。當要求使用volatile聲明變量值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。精確地說就是,遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問;如果不使用volatile,則編譯器將對所聲明的語句進行優化。(簡潔的說就是:volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以免出錯)

5.volatile的本質:

1> 編譯器的優化

在本次線程內, 當讀取一個變量時,爲提高存取速度,編譯器優化時有時會先把變量讀取到一個寄存器中;以後,再取變量值時,就直接從寄存器中取值;當變量值在本線程裏改變時,會同時把變量的新值copy到該寄存器中,以便保持一致。

當變量在因別的線程等而改變了值,該寄存器的值不會相應改變,從而造成應用程序讀取的值和實際的變量值不一致。

當該寄存器在因別的線程等而改變了值,原變量的值不會改變,從而造成應用程序讀取的值和實際的變量值不一致。

2>volatile應該解釋爲“直接存取原始內存地址”比較合適,“易變的”這種解釋簡直有點誤導人。

 

static const等等的用法,(能說出越多越好)

數據結構或者算法:

《離散數學》範圍內的一切問題皆由可能被深入問到(這個最坑爹,最重要,最體現功底,最能加分,特別是各類樹結構的實現和應用)

各類排序:大根堆的實現,快排(如何避免最糟糕的狀態?),bitmap的運用等等

hash任何一個技術面試官必問(例如爲什麼一般hashtable的桶數會取一個素數?如何有效避免hash結果值的碰撞)

網絡編程:

tcpudp的區別(必問)

1.基於連接與無連接 
2
.對系統資源的要求(TCP較多,UDP少) 
3
UDP程序結構較簡單 
4
.流模式與數據報模式
5
TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證

TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快

 

udp調用connect有什麼作用?

1:UDP中可以使用connect系統調用2:UDP中connect操作與TCP中connect操作有着本質區別.TCP中調用connect會引起三次握手,client與server建立連結.UDP中調用connect內核僅僅把對端ip&port記錄下來.3:UDP中可以多次調用connect,TCP只能調用一次connect.UDP多次調用connect有兩種用途:1,指定一個新的ip&port連結.2,斷開和之前的ip&port的連結.指定新連結,直接設置connect第二個參數即可.斷開連結,需要將connect第二個參數中的sin_family設置成 AF_UNSPEC即可. 4:UDP中使用connect可以提高效率.原因如下:普通的UDP發送兩個報文內核做了如下:#1:建立連結#2:發送報文#3:斷開連結#4:建立連結#5:發送報文#6:斷開連結采用connect方式的UDP發送兩個報文內核如下處理:#1:建立連結#2:發送報文#3:發送報文另外一點,每次發送報文內核都由可能要做路由查詢.5:採用connect的UDP發送接受報文可以調用send,write和recv,read操作.當然也可以調用sendto,recvfrom.調用sendto的時候第五個參數必須是NULL,第六個參數是0.調用recvfrom,recv,read系統調用只能獲取到先前connect的ip&port發送的報文. 
UDP中使用connect的好處:1:會提升效率.前面已經描述了.2:高併發服務中會增加系統穩定性.原因:假設client A 通過非connect的UDP與serverB,C通信.B,C提供相同服務.爲了負載均衡,我們讓A與B,C交替通信.A 與 B通信IPa:PORTa<----> IPb:PORTbA 與 C通信IPa:PORTa'<---->IPc:PORTc 
假設PORTa 與 PORTa'相同了(在大併發情況下會發生這種情況),那麼就有可能出現A等待B的報文,卻收到了C的報文.導致收報錯誤.解決方法內就是採用connect的UDP通信方式.在A中創建兩個udp,然後分別connect到B,C.

tcp連接中時序圖,狀態圖,必須非常非常熟練

 

socket服務端的實現,selectepoll的區別(必問)

select的本質是採用32個整數的32位,即32*32= 1024來標識,fd值爲1-1024。當fd的值超過1024限制時,就必須修改FD_SETSIZE的大小。這個時候就可以標識32*max值範圍的fd。

對於單進程多線程,每個線程處理多個fd的情況,select是不適合的。

1.所有的線程均是從1-32*max進行掃描,每個線程處理的均是一段fd值,這樣做有點浪費

2.1024上限問題,一個處理多個用戶的進程,fd值遠遠大於1024

所以這個時候應該採用poll,

poll傳遞的是數組頭指針和該數組的長度,只要數組的長度不是很長,性能還是很不錯的,因爲poll一次在內核中申請4K(一個頁的大小來存放fd),儘量控制在4K以內

epoll還是poll的一種優化,返回後不需要對所有的fd進行遍歷,在內核中維持了fd的列表。select和poll是將這個內核列表維持在用戶態,然後傳遞到內核中。但是只有在2.6的內核才支持。

epoll更適合於處理大量的fd ,且活躍fd不是很多的情況,畢竟fd較多還是一個串行的操作

 

epoll哪些觸發模式,有啥區別?(必須非常詳盡的解釋水平觸發和邊緣觸發的區別,以及邊緣觸發在編程中要做哪些更多的確認)

epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,如果我們沒有採取行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當複雜。

epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裏也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時複製的開銷。

另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。

 

大規模連接上來,併發模型怎麼設計

tcp結束連接怎麼握手,time_wait狀態是什麼,爲什麼會有time_wait狀態?哪一方會有time_wait狀態,如何避免time_wait狀態佔用資源(必須回答的詳細)

tcp頭多少字節?哪些字段?(必問)

20字節,選項12字節

什麼是滑動窗口(必問)

滑動窗口(Sliding window )是一種流量控制技術。滑動窗口協議是用來改善吞吐量的一種技術,即容許發送方在接收任何應答之前傳送附加的包。接收方告訴發送方在某一時刻能送多少包(稱窗口尺寸)。TCP中採用滑動窗口來進行傳輸控制,滑動窗口的大小意味着接收方還有多大的緩衝區可以用於接收數據。發送方可以通過滑動窗口的大小來確定應該發送多少字節的數據。當滑動窗口爲0時,發送方一般不能再發送數據報,但有兩種情況除外,一種情況是可以發送緊急數據,例如,允許用戶終止在遠端機上的運行進程。另一種情況是發送方可以發送一個1字節的數據報來通知接收方重新聲明它希望接收的下一字節及發送方的滑動窗口大小。滑動窗口協議的基本原理就是在任意時刻,發送方都維持了一個連續的允許發送的幀的序號,稱爲發送窗口;同時,接收方也維持了一個連續的允許接收的幀的序號,稱爲接收窗口。發送窗口和接收窗口的序號的上下界不一定要一樣,甚至大小也可以不同。不同的滑動窗口協議窗口大小一般不同。發送方窗口內的序列號代表了那些已經被髮送,但是還沒有被確認的幀,或者是那些可以被髮送的幀。

connect會阻塞,怎麼解決?(必考必問)

最通常的方法最有效的是加定時器;也可以採用非阻塞模式。

設置非阻塞,返回之後用select檢測狀態)

如果select返回可讀,結果只讀到0字節,什麼情況?

某個套接字集合中沒有準備好,可能會select內存用FD_CLR清該位爲0;

keepalive是什麼東東?如何使用?

設置Keepalive參數,檢測已中斷的客戶連接

·        Determine how long to wait beforeprobing the connection. On most platforms the default is 2 hours.

·       Determine how long to wait beforeretrying the probe.

·       Determine how many times to probe theconnection.

 

列舉你所知道的tcp選項,並說明其作用。

1.窗口擴大因子TCP Window Scale Option (WSopt)

TCP窗口縮放選項是用來增加TCP接收窗口的大小而超過65536字節。

2.SACK選擇確認選項

最大報文段長度(M S S)表示T C P傳往另一端的最大塊數據的長度。當建立一個連接時,每一方都有用於通告它期望接收的 M S S選項(M S S選項只能出現在S Y N報文段中)。通過MSS,應用數據被分割成TCP認爲最適合發送的數據塊,由TCP傳遞給IP的信息單位稱爲報文段或段(segment)。

TCP通信時,如果發送序列中間某個數據包丟失,TCP會通過重傳最後確認的包開始的後續包,這樣原先已經正確傳輸的包也可能重複發送,急劇降低了TCP性能。爲改善這種情況,發展出SACK(SelectiveAcknowledgment, 選擇性確認)技術,使TCP只重新發送丟失的包,不用發送後續所有的包,而且提供相應機制使接收方能告訴發送方哪些數據丟失,哪些數據重發了,哪些數據已經提前收到等。

3.MSS:Maxitum Segment Size 最大分段大小

socket什麼情況下可讀?

a.The number of bytes of data in the socket receive buffer is greater than or 
     equal to the current size of the low-water mark forthe socket receive buffer.
     A read operation on the socket will not block and willreturn a value greater than 0
b.  The read half of the connections is closed (i.e., A TCPconnection that has received a FIN).
     A read operation on the socket will not block and willreturn 0 (i.e., EOF)
c. The socket is a listening socket and the number of completed connection isnonzero.
    An accept on the listening socket will normally not block,although we will describe a   
d. A socket error is pending. A read operation on the socket will not block andwill return
    an error (-1) with errno set to the specific error condition

db:
mysql
,會考sql語言,服務器數據庫大規模數據怎麼設計,db各種性能指標

最後:補充一個最最重要,最最坑爹,最最有難度的一個題目:一個每秒百萬級訪問量的互聯網服務器,每個訪問都有數據計算和I/O操作,如果讓你設計,你怎麼設計?

1)tcp三次握手的過程,accept發生在三次握手哪個階段?

三次握手之後
2
Tcp流, udp的數據報,之間有什麼區別,爲什麼TCP要叫做數據流?
流無邊界,數據報有邊界.TCP是先進先出的,並且可靠.

3)const
的含義及實現機制,比如:const int i,是怎麼做到i只可讀的?
編譯器相關,優化可能讓其直接轉爲一常量代入. const用來說明所定義的變量是隻讀的。

  這些在編譯期間完成,編譯器可能使用常數直接替換掉對此變量的引用。



4) valitale
的含義。

volatile,告訴編譯器此處必須得從地址去取,不得作相關優化。千萬注意,這裏與硬件cache可不是一回事。
5
OFFSETOF(s, m)的宏定義,s是結構類型,ms的成員,求ms中的偏移量。

#define OFFSETOF(s, m) ({s s1;(void*)(&s1)-(void*)(&s1->m);})/*gcc*/


6)100
億個數,求最大的1萬個數,並說出算法的時間複雜度。
建一個堆,先把最開始的1萬個數放進去。以後每進一個,都把最小的趕出來。

7
)設計一個洗牌的算法,並說出算法的時間複雜度。

產生2*54+rand()%2次交換,所有序列已經很接近平均分佈(只要rand()滿足均分),並且比較快。否則會是複雜度比較高的算法。我統計過。
 socket在什麼情況下可讀?
擁塞控制是把整體看成一個處理對象的,流量控制是對單個的。感知的手段應該不少,比如在TCP協議裏,TCP報文的重傳本身就可以作爲擁塞的依據。依據這樣的原理,應該可以設計出很多手段。
--------------
1)
三次握手之後
2)
流無邊界,數據報有邊界.TCP是先進先出的,並且可靠.
3)
編譯器相關,優化可能讓其直接轉爲一常量代入.
4)volatile
,告訴編譯器此處必須得從地址去取,不得作相關優化。千萬注意,這裏與硬件cache可不是一回事。
5)#define OFFSETOF(s, m) ({s s1;(void*)(&s1)-(void*)(&s1->m);})/*gcc*/
6)
建一個堆,先把最開始的1萬個數放進去。以後每進一個,都把最小的趕出來。
7)
產生2*54+rand()%2次交換,所有序列已經很接近平均分佈(只要rand()滿足均分),並且比較快。否則會是複雜度比較高的算法。我統計過。
不知道想問什麼。
9)
擁塞控制是把整體看成一個處理對象的,流量控制是對單個的。感知的手段應該不少,比如在TCP協議裏,TCP報文的重傳本身就可以作爲擁塞的依據。依據這樣的原理,應該可以設計出很多手段。

9
)流量控制與擁塞控制的區別,節點計算機怎樣感知網絡擁塞了?

 

 

前段時間專心面過騰訊,經過了N輪的技術面,結果還是掛了,但沒掛在技術面,比較欣慰,回來之後寫一點總結,以供有夢想進入騰訊做後臺服務器開發的同學參考,本文章爲胡成精心總結,胡成原創,copy和轉載請通知。ps:()之內的文字由作者點評,非面試題文字。

linuxos:

netstat tcpdump ipcs ipcrm (如果這四個命令沒聽說過或者不能熟練使用,基本上可以回家,通過的概率較小^_^ ,這四個命令的熟練掌握程度基本上能體現面試者實際開發和調試程序的經驗)

cpu 內存硬盤等等與系統性能調試相關的命令必須熟練掌握,設置修改權限 tcp網絡狀態查看各進程狀態抓包相關等相關命令必須熟練掌握

awk sed需掌握

共享內存的使用實現原理(必考必問,然後共享內存段被映射進進程空間之後,存在於進程空間的什麼位置?共享內存段最大限制是多少?)

c++進程內存空間分佈(注意各部分的內存地址誰高誰低,注意棧從高道低分配,堆從低到高分配)

ELF是什麼?其大小與程序中全局變量的是否初始化有什麼關係(注意.bss段)

使用過哪些進程間通訊機制,並詳細說明(重點)

makefile編寫,雖然比較基礎,但是會被問到

gdb調試相關的經驗,會被問到

如何定位內存泄露?

動態鏈接和靜態鏈接的區別

32位系統一個進程最多多少堆內存

多線程和多進程的區別(重點面試官最最關心的一個問題,必須從cpu調度,上下文切換,數據共享,多核cup利用率,資源佔用,等等各方面回答,然後有一個問題必須會被問到:哪些東西是一個線程私有的?答案中必須包含寄存器,否則悲催)

寫一個c程序辨別系統是64 or32

寫一個c程序辨別系統是大端or小端字節序

信號:列出常見的信號,信號怎麼處理?

i++是否原子操作?並解釋爲什麼???????

說出你所知道的各類linux系統的各類同步機制(重點),什麼是死鎖?如何避免死鎖(每個技術面試官必問)

列舉說明linux系統的各類異步機制

exit() _exit()的區別?

如何實現守護進程?

linux的內存管理機制是什麼?

linux的任務調度機制是什麼?

標準庫函數和系統調用的區別?

補充一個坑爹坑爹坑爹坑爹的問題:系統如何將一個信號通知到進程?(這一題哥沒有答出來)

c語言:

宏定義和展開(必須精通)

位操作(必須精通)

指針操作和計算(必須精通)

內存分配(必須精通)

sizeof必考

各類庫函數必須非常熟練的實現

哪些庫函數屬於高危函數,爲什麼?(strcpy等等)

c++

一個String類的完整實現必須很快速寫出來(注意:賦值構造,operator=是關鍵)

虛函數的作用和實現原理(必問必考,實現原理必須很熟)

sizeof一個類求大小(注意成員變量,函數,虛函數,繼承等等對大小的影響)

指針和引用的區別(一般都會問到)

多重類構造和析構的順序

stl各容器的實現原理(必考)

extern c 是幹啥的,(必須將編譯器的函數名修飾的機制解答的很透徹)

volatile是幹啥用的,(必須將cpu的寄存器緩存機制回答的很透徹)

static const等等的用法,(能說出越多越好)

數據結構或者算法:

《離散數學》範圍內的一切問題皆由可能被深入問到(這個最坑爹,最重要,最體現功底,最能加分,特別是各類樹結構的實現和應用)

各類排序:大根堆的實現,快排(如何避免最糟糕的狀態?),bitmap的運用等等

hash任何一個技術面試官必問(例如爲什麼一般hashtable的桶數會取一個素數?如何有效避免hash結果值的碰撞)

網絡編程:

tcpudp的區別(必問)

udp調用connect有什麼作用?

tcp連接中時序圖,狀態圖,必須非常非常熟練

socket服務端的實現,selectepoll的區別(必問)

epoll哪些觸發模式,有啥區別?(必須非常詳盡的解釋水平觸發和邊緣觸發的區別,以及邊緣觸發在編程中要做哪些更多的確認)

大規模連接上來,併發模型怎麼設計

 

tcp結束連接怎麼握手,time_wait狀態是什麼,爲什麼會有time_wait狀態?哪一方會有time_wait狀態,如何避免time_wait狀態佔用資源(必須回答的詳細)

·       TIME_WAIT:表示收到了對方的FIN報文,併發送出了ACK報文。 TIME_WAIT狀態下的TCP連接會等待2*MSLMax Segment Lifetime,最大分段生存期,指一個TCP報文在Internet上的最長生存時間。每個具體的TCP協議實現都必須選擇一個確定的MSL值,RFC 1122建議是2分鐘,但BSD傳統實現採用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本機的這個值),然後即可回到CLOSED 可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。

  • 如果使用了nginx代理,那麼系統TIME_WAIT的數量會變得比較多,這是由於nginx代理使用了短鏈接的方式和後端交互的原因,使得nginx和後端的ESTABLISHED變得很少而TIME_WAIT很多。這不但發生在安裝nginx的代理服務器上,而且也會使後端的app服務器上有大量的TIME_WAIT。查閱TIME_WAIT資料,發現這個狀態很多也沒什麼大問題,但可能因爲它佔用了系統過多的端口,導致後續的請求無法獲取端口而造成障礙。

    雖然TIME_WAIT會造成一些問題,但是要完全槍斃掉它也是不正當的,雖然看起來這麼做沒什麼錯。具體可看這篇文檔:

    http://hi.baidu.com/tim_bi/blog/item/35b005d784ca91d5a044df1d.html

    所以目前看來最好的辦法是讓每個TIME_WAIT早點過期。

    在linux上可以這麼配置:

    #讓TIME_WAIT狀態可以重用,這樣即使TIME_WAIT佔滿了所有端口,也不會拒絕新的請求造成障礙
    echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse
    #讓TIME_WAIT儘快回收,我也不知是多久,觀察大概是一秒鐘
    echo "1" > /proc/sys/net/ipv4/tcp_tw_recycle

    很多文檔都會建議兩個參數都配置上,但是我發現只用修改tcp_tw_recycle就可以解決問題的了,TIME_WAIT重用TCP協議本身就是不建議打開的。

    不能重用端口可能會造成系統的某些服務無法啓動,比如要重啓一個系統監控的軟件,它用了40000端口,而這個端口在軟件重啓過程中剛好被使用了,就可能會重啓失敗的。linux默認考慮到了這個問題,有這麼個設定:

    #查看系統本地可用端口極限值
    cat /proc/sys/net/ipv4/ip_local_port_range

    用這條命令會返回兩個數字,默認是:32768 61000,說明這臺機器本地能向外連接61000-32768=28232個連接,注意是本地向外連接,不是這臺機器的所有連接,不會影響這臺機器的80端口的對外連接數。但這個數字會影響到代理服務器(nginx)對app服務器的最大連接數,因爲nginx對app是用的異步傳輸,所以這個環節的連接速度很快,所以堆積的連接就很少。假如nginx對app服務器之間的帶寬出了問題或是app服務器有問題,那麼可能使連接堆積起來,這時可以通過設定nginx的代理超時時間,來使連接儘快釋放掉,一般來說極少能用到28232個連接。

    因爲有軟件使用了40000端口監聽,常常出錯的話,可以通過設定ip_local_port_range的最小值來解決:

    echo "40001 61000" > /proc/sys/net/ipv4/ip_local_port_range

    但是這麼做很顯然把系統可用端口數減少了,這時可以把ip_local_port_range的最大值往上調,但是好習慣是使用不超過32768的端口來偵聽服務,另外也不必要去修改ip_local_port_range數值成1024 65535之類的,意義不大。

    因爲使用了nginx代理,在windows下也會造成大量TIME_WAIT,當然windows也可以調整:

    在註冊表(regedit)的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,值就是秒數,即可。

    windows默認是重用TIME_WAIT,我現在還不知道怎麼改成不重用的,本地端口也沒查到是什麼值,但這些都關係不大,都可以按系統默認運作。
  • ------------------------------------------------------------------------------------------------------------------------
  • TIME_WAIT狀態
  • 根據TCP協議,主動發起關閉的一方,會進入TIME_WAIT狀態,持續2*MSL(Max Segment Lifetime),缺省爲240秒,在這個post中簡潔的介紹了爲什麼需要這個狀態。
  • 值得一說的是,對於基於TCP的HTTP協議,關閉TCP連接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓240*1000=240,000個TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。當然現代操作系統都會用快速的查找算法來管理這些TIME_WAIT,所以對於新的TCP連接請求,判斷是否hit中一個TIME_WAIT不會太費時間,但是有這麼多狀態要維護總是不好。
  • HTTP協議1.1版規定default行爲是Keep-Alive,也就是會重用TCP連接傳輸多個request/response,一個主要原因就是發現了這個問題。還有一個方法減緩TIME_WAIT壓力就是把系統的2*MSL時間減少,因爲240秒的時間實在是忒長了點,對於Windows,修改註冊表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,一般認爲不要少於60,不然可能會有麻煩。
  • 對於大型的服務,一臺server搞不定,需要一個LB(Load Balancer)把流量分配到若干後端服務器上,如果這個LB是以NAT方式工作的話,可能會帶來問題。假如所有從LB到後端Server的IP包的source address都是一樣的(LB的對內地址),那麼LB到後端Server的TCP連接會受限制,因爲頻繁的TCP連接建立和關閉,會在server上留下TIME_WAIT狀態,而且這些狀態對應的remote address都是LB的,LB的source port撐死也就60000多個(2^16=65536,1~1023是保留端口,還有一些其他端口缺省也不會用),每個LB上的端口一旦進入Server的TIME_WAIT黑名單,就有240秒不能再用來建立和Server的連接,這樣LB和Server最多也就能支持300個左右的連接。如果沒有LB,不會有這個問題,因爲這樣server看到的remote address是internet上廣闊無垠的集合,對每個address,60000多個port實在是夠用了。
  • 一開始我覺得用上LB會很大程度上限制TCP的連接數,但是實驗表明沒這回事,LB後面的一臺Windows Server 2003每秒處理請求數照樣達到了600個,難道TIME_WAIT狀態沒起作用?用Net Monitor和netstat觀察後發現,Server和LB的XXXX端口之間的連接進入TIME_WAIT狀態後,再來一個LB的XXXX端口的SYN包,Server照樣接收處理了,而是想像的那樣被drop掉了。翻書,從書堆裏面找出覆滿塵土的大學時代買的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中間提到一句,對於BSD-derived實現,只要SYN的sequence number比上一次關閉時的最大sequence number還要大,那麼TIME_WAIT狀態一樣接受這個SYN,難不成Windows也算BSD-derived?有了這點線索和關鍵字(BSD),找到這個post,在NT4.0的時候,還是和BSD-derived不一樣的,不過Windows Server 2003已經是NT5.2了,也許有點差別了。
  • 做個試驗,用Socket API編一個Client端,每次都Bind到本地一個端口比如2345,重複的建立TCP連接往一個Server發送Keep-Alive=false的HTTP請求,Windows的實現讓sequence number不斷的增長,所以雖然Server對於Client的2345端口連接保持TIME_WAIT狀態,但是總是能夠接受新的請求,不會拒絕。那如果SYN的Sequence Number變小會怎麼樣呢?同樣用Socket API,不過這次用Raw IP,發送一個小sequence number的SYN包過去,Net Monitor裏面看到,這個SYN被Server接收後如泥牛如海,一點反應沒有,被drop掉了。
  • 按照書上的說法,BSD-derived和Windows Server 2003的做法有安全隱患,不過至少這樣至少不會出現TIME_WAIT阻止TCP請求的問題,當然,客戶端要配合,保證不同TCP連接的sequence number要上漲不要下降。
  • ----------------------------------------------------------------------------------------------------------------------------
  • Socket中的TIME_WAIT狀態
  • 在高併發短連接的server端,當server處理完client的請求後立刻closesocket此時會出現time_wait狀態然後如果client再併發2000個連接,此時部分連接就連接不上了,用linger強制關閉可以解決此問題,但是linger會導致數據丟失,linger值爲0時是強制關閉,無論併發多少多能正常連接上,如果非0會發生部分連接不上的情況!(可調用setsockopt設置套接字的linger延時標誌,同時將延時時間設置爲0。
  • TCP/IP的RFC文檔。TIME_WAIT是TCP連接斷開時必定會出現的狀態。
    是無法避免掉的,這是TCP協議實現的一部分。
    在WINDOWS下,可以修改註冊表讓這個時間變短一些

  • time_wait的時間爲2msl,默認爲4min.
    你可以通過改變這個變量:
    TcpTimedWaitDelay 
    把它縮短到30s
  • TCP要保證在所有可能的情況下使得所有的數據都能夠被投遞。當你關閉一個socket時,主動關閉一端的socket將進入TIME_WAIT狀態,而被動關閉一方則轉入CLOSED狀態,這的確能夠保證所有的數據都被傳輸。當一個socket關閉的時候,是通過兩端互發信息的四次握手過程完成的,當一端調用close()時,就說明本端沒有數據再要發送了。這好似看來在握手完成以後,socket就都應該處於關閉CLOSED狀態了。但這有兩個問題,首先,我們沒有任何機制保證最後的一個ACK能夠正常傳輸,第二,網絡上仍然有可能有殘餘的數據包(wandering duplicates),我們也必須能夠正常處理。
    通過正確的狀態機,我們知道雙方的關閉過程如下

  • 假設最後一個ACK丟失了,服務器會重發它發送的最後一個FIN,所以客戶端必須維持一個狀態信息,以便能夠重發ACK;如果不維持這種狀態,客戶端在接收到FIN後將會響應一個RST,服務器端接收到RST後會認爲這是一個錯誤。如果TCP協議能夠正常完成必要的操作而終止雙方的數據流傳輸,就必須完全正確的傳輸四次握手的四個節,不能有任何的丟失。這就是爲什麼socket在關閉後,仍然處於 TIME_WAIT狀態,因爲他要等待以便重發ACK。
  • 如果目前連接的通信雙方都已經調用了close(),假定雙方都到達CLOSED狀態,而沒有TIME_WAIT狀態時,就會出現如下的情況。現在有一個新的連接被建立起來,使用的IP地址與端口與先前的完全相同,後建立的連接又稱作是原先連接的一個化身。還假定原先的連接中有數據報殘存於網絡之中,這樣新的連接收到的數據報中有可能是先前連接的數據報。爲了防止這一點,TCP不允許從處於TIME_WAIT狀態的socket建立一個連接。處於TIME_WAIT狀態的socket在等待兩倍的MSL時間以後(之所以是兩倍的MSL,是由於MSL是一個數據報在網絡中單向發出到認定丟失的時間,一個數據報有可能在發送圖中或是其響應過程中成爲殘餘數據報,確認一個數據報及其響應的丟棄的需要兩倍的MSL),將會轉變爲CLOSED狀態。這就意味着,一個成功建立的連接,必然使得先前網絡中殘餘的數據報都丟失了。
  • 由於TIME_WAIT狀態所帶來的相關問題,我們可以通過設置SO_LINGER標誌來避免socket進入TIME_WAIT狀態,這可以通過發送RST而取代正常的TCP四次握手的終止方式。但這並不是一個很好的主意,TIME_WAIT對於我們來說往往是有利的。
  • 客戶端與服務器端建立TCP/IP連接後關閉SOCKET後,服務器端連接的端口
    狀態爲TIME_WAIT
  • 是不是所有執行主動關閉的socket都會進入TIME_WAIT狀態呢?
    有沒有什麼情況使主動關閉的socket直接進入CLOSED狀態呢?
  • 主動關閉的一方在發送最後一個 ack 後
    就會進入 TIME_WAIT 狀態 停留2MSL(max segment lifetime)時間
    這個是TCP/IP必不可少的,也就是“解決”不了的。

    也就是TCP/IP設計者本來是這麼設計的
    主要有兩個原因
    1。防止上一次連接中的包,迷路後重新出現,影響新連接
       (經過2MSL,上一次連接中所有的重複包都會消失)
    2。可靠的關閉TCP連接
       在主動關閉方發送的最後一個 ack(fin) ,有可能丟失,這時被動方會重新發
       fin, 如果這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以
       主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。

    TIME_WAIT 並不會佔用很大資源的,除非受到攻擊。

    還有,如果一方 send 或 recv 超時,就會直接進入 CLOSED 狀態
  • socket-faq中的這一段講的也很好,摘錄如下:
    2.7. Please explain the TIME_WAIT state.

 

tcp頭多少字節?哪些字段?(必問)

什麼是滑動窗口(必問)

connect會阻塞,怎麼解決?(必考必問,提示:設置非阻塞,返回之後用select檢測狀態)

如果select返回可讀,結果只讀到0字節,什麼情況?

keepalive 是什麼東東?如何使用?

 

keepalive

在TCP中有一個Keep-alive的機制可以檢測死連接,原理很簡單,TCP會在空閒了一定時間後發送數據給對方:

1.如果主機可達,對方就會響應ACK應答,就認爲是存活的。

2.如果可達,但應用程序退出,對方就發RST應答,發送TCP撤消連接。

3.如果可達,但應用程序崩潰,對方就發FIN消息。

4.如果對方主機不響應ack, rst,繼續發送直到超時,就撤消連接。這個時間就是默認

的二個小時。

 

列舉你所知道的tcp選項,並說明其作用。

 

socket什麼情況下可讀?

 

每次讀操作返回前都要檢查是否還有剩餘數據沒讀完,如果是的話保持數據有效標誌,不這樣設計的話會出現明顯的不一致,那就是數據在讀緩衝但沒有讀有效標誌。

當然也可以設計成讓程序必須一次性取走所有數據,但這樣設計的接口不友好,win32中也確實存在這種類型的API

db:
mysql
,會考sql語言,服務器數據庫大規模數據怎麼設計,db各種性能指標

最後:補充一個最最重要,最最坑爹,最最有難度的一個題目:一個每秒百萬級訪問量的互聯網服務器,每個訪問都有數據計算和I/O操作,如果讓你設計,你怎麼設計?

 

 

shell下輸入“man 2read ” 你先看看。
ssize_t read(int fd, void *buf, size_t count);
意義:從文件描述符fd所指向的文件中讀取count個字節的數據到buf所指向的緩存中。
文件描述符是由無符號整數表示的句柄,進程使用它來標識打開的文件。
文件描述符0代表標準文件。
fd是這麼來的。
fd=(open或creat成功調用時的返回值)

 

 

於一個完整的程序,在內存中分佈情況如下圖:


原文鏈接:http://blog.csdn.net/ibmfahsion/article/details/11992403?utm_source=tuicool

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章