深信服的面試總共有三輪,其中包括兩輪技術面和一輪HR面。兩輪技術面間隔時間不長。如果過了,會讓你等着;如果沒過,就直接讓你走了。
面試問題
技術一面
1.手寫strncpy函數的實現。
strncpy函數總是正好向dst寫入len個字符。如果strlen(src)的值小於len,dst數組就用額外的'\0'字符填充到len長度,如果strlen(src)的值大於或等於len,那么只有len個字符被復制到dst中。
注意:如果strlen(src)的值大於或等於len,那么復制到dst中的字符將不包括'\0'。
char * strncpy(char *dst, const char *src, int len)
{
assert(dst != NULL && src != NULL);
char *temp = dst;
int i = 0;
int src_len = strlen(src);
if(src_len >= len) //src length大於或者等於len的時候,會拷貝len個有效字符(不包括'\0')到dst中
{
while(i <= len)
{
*temp++ = *src++;
i++;
}
}
else //src length小於len的時候,先拷貝src中所有的字符,再填充len - strlen(src)個'\0'
{
while(i <= src_len)
{
*temp++ = *src++;
i++;
}
while(i <= len)
{
*temp++ = '\0';
i++;
}
}
return dst;
}
2.strncpy函數的返回值是啥?
strncpy函數的返回值是dst。
3.在一個字符串中找另一個字符串的子串。
(1)C語言的庫函數strstr,函數原型函數原型:extern char *strstr(char *str1, const char *str2);
判斷str2是否是str1的子串。
返回值:若str2是str1的子串,則返回str2在str1的首次出現的地址;如果str2不是str1的子串,則返回NULL。
(2)KMP算法
鏈接:KMP算法詳解 ps:KMP算法不是效率最高的算法,實際采用並不多。
(3)Boyer-Moore算法
鏈接:Boyer-Moore算法詳解
(4)Sunday算法
鏈接:Sunday算法詳解
4.Linux系統中的僵屍進程。
僵屍進程:在Linux系統中,一個進程結束了,但是它的父進程沒有回收它,釋放它占用的資源, 那么它將變成一個僵屍進程。
另外,如果該進程的父進程已經先結束了,那么該進程就不會變成僵屍進程。 因為每個進程結束的時候,系統都會掃描當前系統中所運行的所有進程, 看有沒有哪個進程是剛剛結束的這個進程的子進程,如果是的話,就由Init 來接管子進程,成為子進程的父進程。
怎樣來清除僵屍進程:
(1)父進程通過wait和waitpid等函數等待子進程結束,這會導致父進程掛起。
(2)如果父進程很忙,那么可以用signal函數為SIGCHLD安裝handler,因為子進程結束后, 父進程會收到該信號,可以在handler中調用wait回收。
(3)如果父進程不關心子進程什么時候結束,那么可以用signal(SIGCHLD,SIG_IGN) 通知內核,自己對子進程的結束不感興趣,那么子進程結束后,內核會回收, 並不再給父進程發送信號。
(4)還有一些技巧,就是fork兩次,父進程fork一個子進程,然后繼續工作,子進程fork一 個孫進程后退出,那么孫進程被init接管,孫進程結束后,init會回收。不過,子進程的回收還是要自己做。
5.DHCP協議的交互過程。單個和多個DHCP server的區別。存在DHCP relay的情況。
鏈接:DHCP協議報文格式
DHCP交互過程分為四個階段(最基本的情況):
(1)Discover: Client在以廣播的形式DHCP Discover報文(DstMAC和DstIP都是全1,SrcIP全0,SrcMAC是Client的MAC),廣播域內的所有Server都會收到該報文。另外,Client可以在選項字段中加入“request paramter list” 選項,表明自己想要獲得的各種參數。
(2)Offer:廣播域內的Server收到DHCP Discover報文之后,會發送DHCP Offer報文,報文中包含了Server准備分配給Client的IP地址,以及一些選項字段。
(3)Request:如果廣播域內有多個Server,那么Client會收到多個DHCP Offer報文。Client只能選擇處理其中一個,一般的做法是選擇最先收到的DHCP Offer報文。然后Client就以廣播方式回答一個DHCP request請求信息,該信息中包含Client選中的DHCP Server的IP地址和Client想要的IP地址。之所以要以廣播方式回答,是為了通知所有的Server,Client將選擇某台Server所提供的IP地址。
(4)ACK:Server收到DHCP Request報文后,判斷選項字段中的DHCP Server的IP地址是否與自己的地址相同。如果不相同就不回應ACK報文,並且判斷自己是否給該Request報文的Client發送過DHCP Offer報文。如果發送過DHCP Offer報文,則清除相應IP地址記錄。如果選項字段中的DHCP Server的IP地址與自己的IP地址相同,Server就會響應一個DHCP ACK報文,其內容同DHCP Offer類似並在選項字段中增加了IP地址使用租期選項。Client收到ACK報文后,如果Server分配的IP地址不跟其他設備的IP地址沖突,那么Client將使用它作為自己的IP。
復雜一些的情況:
(1)Client收到Server發送的ACK報文之后,會先發送一個免費ARP報文(SrcIP和DstIP都是Server分配的IP)。如果Client收到ARP應答了,就說明Server分配的IP已被占用,那么Client會給Server發送一個DHCP Decline報文,在Request IP Address(option 50)字段填入Server分配的發生沖突的IP地址。發送完成后,等待一段時間再開始重新申請IP地址,直至申請到一個可用的IP地址。
(2)如果Client之前請求過IP地址,然后重啟了,那么重啟之后Client首先發送單播的DHCP Request報文到指定的Server,在Request IP Address(option 50)字段填入之前使用過的IP地址,等待Server的響應。如果收到了Server的ACK報文,那么Client確認該IP不沖突之后,將直接使用它。如果Server回應的NAK報文,那么Client將發送廣播的DHCP Discover報文請求IP地址。
(3)當DHCP Server分配給Client的IP地址的使用租期到達50%時(T1),Client發送單播的DHCP Request報文到指定的Server,等待Server的響應。如果收到了Server的ACK報文,那么Client會刷新租期。如果沒有收到Server的ACK報文,那么Client會繼續使用該IP。
(4)當DHCP Server分配給Client的IP地址的使用租期到達87.5%時(T2),Client發送廣播的DHCP Request報文,等待Server的響。如果收到了Server的ACK報文,那么Client會刷新租期。如果沒有收到Server的ACK報文,那么Client會繼續使用該IP,知道租期結束。
(5)當DHCP Server分配給Client的IP地址的使用租期結束時,Client發送廣播的DHCP Discover報文,重新申請IP地址。
(6)已經動態獲取到了IP地址的DHCP Client,如果需要向Server請求一些參數,那么它可以向Server發送DHCP Inform報文。Server收到DHCP Inform報文后,回應DHCP ACK報文。該ACK報文不包含分配的Client的IP地址,也不包含租期信息,但是在選項字段中包含了Client需要的參數。
存在DHCP Relay的情況:
(1)如果DHCP Server和DHCP Client不在同一個網段,那么可以使用DHCP Relay中轉。Relay收到Client發送的廣播的DHCP Discover報文和DHCP Request報文后,在報文的giaddr字段填充自己的IP地址,並將報文轉換成單播報文,然后發送給指定的Server(DHCP Relay上需要配置Server的IP地址)。Relay收到Server回應的Offer和ACK報文后,將其轉發給Client。注意:DHCP Server給DHCP Client分配的IP地址與DHCP Relay添加的giaddr在同一網段。
6.linux系統常用的命令,比如查看文件大小,查看磁盤分區及使用情況。
查看文件或者文件夾的大小:du -sh file_or_folder_name
查看磁盤分區及使用情況:df -h 或者 fdisk -l
7.AVL樹的特點。
1.AVL樹本質上還是一棵二叉搜索樹,它的特點是:
(1)本身首先是一棵二叉搜索樹。
(2)帶有平衡條件:每個結點的左右子樹的高度之差的絕對值(平衡因子)最多為1。
也就是說,AVL樹,本質上是帶了平衡功能的二叉查找樹(二叉排序樹,二叉搜索樹)。
2.AVL樹為了達到平衡條件,必須進行旋轉:
(1)左單旋:當前節點的右子樹比左子樹高(平衡因子為1),現在在它的右子樹的右側插入一個新節點,右子樹高度增加1導致節點平衡因子從1變為變為2而不平衡。此時,將當前節點(不平衡的節點)逆時針旋轉(左旋),使得它原先的右子節點變成它的父節點,它本身則變成原先右子節點的左子節點,原先右子節點的左子節點變成它新的右子節點。
int left_rotate(avl_tree *tree, avl_node *node) /* 左單旋 */
{
avl_node *p;
p = node->right; /* 原先的右子節點 */
node->right = p->left; /* 原先右子節點的左子節點 -->它新的右子節點 */
if(p->left)
{
p->left->parent = node;
}
p->left = node; /* 它本身-->原先右子節點的左子節點 */
if(node == tree->root)
{
tree->root = p;
}
p->parent = node->parent;
node->parent = p; /* 它原先的右子節點-->它的父節點 */
return 0;
}
(2)右單旋:當前節點左子樹比右子樹高(平衡因子為-1),現在在左子樹的左側插入一個新節點,左子樹的高度增加1,導致該節點的平衡因子從-1變為-2而不平衡。此時,將當前節點(不平衡的節點)順時針旋轉(右旋),使得它原先的左子節點變成它的父節點,它本身則變成原先左子節點的右子節點,原先左子節點的右子節點變成它新的左子節點。
struct avl_node
{
struct avl_node *left;
struct avl_node *right;
struct avl_node *parent;
int bf; //balance factor
void *info;
};
struct avl_tree
{
int (*compare_func)(void *data1, void *data2); /* 用來比較avl樹的節點的值的大小 */
struct avl_node *root;
int count;
};
int right_rotate(avl_tree *tree, avl_node *node) /* 右單旋 */
{
avl_node *p;
p = node->left; /* 原先的左子節點 */
node->left = p->right; /* 原先的左子節點的右子節點-->它新的左子節點 */
if(p->right)
{
p->right->parent = node;
}
p->right = node;/* 它本身-->原先的左子節點的右子節點 */
if(node == tree->root)
{
tree->root = p;
}
p->parent = node->parent;
node->parent = p;/* 它原先的左子節點-->它的父節點 */
return 0;
}
(3)左右雙旋(先左旋,再右旋):當前節點的左子樹比右子樹高(平衡因子為-1),現在在它的左子樹的右子樹上插入一個新的節點,它的左子樹的高度增加1,導致該節點的平衡因子從-1變為-2而不平衡。此時,將先當前節點(不平衡節點)的左子節點逆時針旋轉(左旋),再將當前節點順時針旋轉(右旋),就可以使得AVL樹恢復平衡。
int left_right_rotate(avl_tree *tree, avl_node *node) /*先左旋,再右旋*/
{
left_rotate(tree, node->left);
right_rotate(tree,node);
return 0;
}
(4)右左雙旋(先右旋,再左旋):當前節點的右子樹比左子樹高(平衡因子為1),現在在它的右子樹的左子樹上插入一個新的節點,它的右子樹的高度增加1,導致該節點的平衡因子從1變為2而不平衡。此時,將先當前節點(不平衡節點)的右子節點順時針旋轉(右旋),再將當前節點逆時針旋轉(左旋),就可以使得AVL樹恢復平衡。
int right_left_rotate(avl_tree *tree, avl_node *node) /*先右旋,再左旋*/
{
right_rotate(tree, node->right);
left_rotate(tree,node);
return 0;
}
8.報文檢測系統(我簡歷中寫的一個項目)的具體實現。
9.socket調用時可能出現的錯誤,以及解決方法。
鏈接:socket常見錯誤
鏈接:socket常見異常
10.如何判斷鏈表是否有環?
判斷鏈表是否有環可以用快慢指針,fast指針一次走兩步,slow指針一次走一步。如果鏈表確實有環,那么fast和slow會相遇;否則不會相遇。
假設鏈表有環,環長度為r,fast指針走過的距離為s1,slow指針走過的距離為s2。
可以得到s1 = s2 + n*r,(n >= 1)
typedef struct listnode
{
struct listnode *next;
struct listnode *prev;
void *data;
}NODE;
typedef struct list
{
struct listnode *head;
struct listnode *tail;
unsigned int count;
int (*compare_func)(void *data1, void *data2); /* 用來比較鏈表元素的值的大小 */
}LIST;
NODE * have_loop_or_not(LIST *list) /* 判斷鏈表是否有環 */
{
NODE *fast, *slow;
if(!list)
return NULL;
slow = fast = list->head;
while(slow && fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return slow;
}
return NULL;
}
11.如何判斷兩個鏈表(沒有環)是否相交?
如果兩個沒有環的鏈表相交,那么相交節點之后的所有節點必定是完全相同的。兩個相交鏈表呈Y型。
假設這兩個鏈表分別是listA和listB。現在讓listA的最后一個節點指向它的head,如果listA和listB相交,那么listB會有環。判斷listB是否有環就可以判斷A,B是否相交。
12.snmp相關的內容。比如trap上報了哪些信息?比如網元側的AgentX如何處理snmp報文?
技術二面
1.雙主控板(我簡歷中寫的一個分布式路由器項目)的時候,主用和備用主控板之間如何保持通信?如何倒換(備用主控什么時候變成主用)?
2.假如某個時刻存在雙主用,那么如何處理,怎么決策出一個主用主控板?
3.linux內核的收包過程。
4.Linux中的守護進程。
守護進程的概念:
守護進程(Daemon)是一種運行在后台的一種特殊的進程,它獨立於控制終端並且周期性的執行某種任務或等待處理某些發生的事件。由於在linux中,每個系統與用戶進行交流的界面成為終端,每一個從此終端開始運行的進程都會依附於這個終端,這個終端被稱為這些進程的控制終端,當控制終端被關閉的時候,相應的進程都會自動關閉。但是守護進程卻能突破這種限制,它脫離於終端並且在后台運行,並且它脫離終端的目的是為了避免進程在運行的過程中的信息在任何終端中顯示並且進程也不會被任何終端所產生的終端信息所打斷。它從被執行的時候開始運轉,知道整個系統關閉才退出(當然可以認為的殺死相應的守護進程)。如果想讓某個進程不因為用戶或中斷或其他變化而影響,那么就必須把這個進程變成一個守護進程。
鏈接:深入理解Linux操作系統守護進程的意義
5.字符串的搜索,比如CLI命令行的快速匹配與搜索。
6.鏈表的插入操作。
NODE * listnode_add(LIST *list, void *val)
{/* 在鏈表尾部插入節點 */
NODE *node;
if((!list) || (!val))
return NULL;
node = (NODE *)calloc(1, sizeof(NODE));
if(!node)
return NULL;
node->prev = list->tail;
node->data = val;
if(list->head == NULL)
list->head = node;
else
list->tail->next = node;
list->tail = node;
list->count++;
return node;
}
NODE * listnode_add_sort(LIST *list, void *val)
{/* 往有序鏈表中插入元素
*(鏈表元素的值按照從小到大順序排序)
*/
NODE *n, *node;
if((!list) || (!val))
return NULL;
node = (NODE *)calloc(1, sizeof(NODE));
if(!node)
return NULL;
node->data = val;
if(list->compare_func)
{
for(n = list->tail; n; n = n->prev)
{
if((list->compare_func(val, n->data)) >= 0)
{/* 在中間或者尾部插入元素 */
node->prev = n;
node->next = n->next;
if(n->next)
n->next->prev = node;
else
list->tail = node;
n->next = node;
list->count++;
return node;
}
}
}
/* 在頭部插入元素 */
node->next = list->head;
if(list->head)
list->head->prev = node;
else
list->tail = node;
list->head = node;
list->count++;
return node;
}
7.如何處理廣播風暴?
(1)什么是廣播風暴:網絡上的廣播幀(由於被轉發)數量急劇增加而影響正常的網絡通訊的反常現象,廣播風暴會占用相當可觀的網絡帶寬,造成整個網絡無法正常工作。
(2)廣播風暴只會在二層出現,廣播報文跨越不了網段。
(3)廣播風暴的產生有幾個可能的原因:網卡損壞、網絡環路、網絡病毒。
(4)廣播風暴的避免/抑制/解決方法:
a)開啟交換機端口的廣播風暴控制,當端口收到的廣播幀累計到預定門限值時,端口將自動丟棄收到的廣播幀。
b)vlan隔離:按照端口/MAC/子網划分vlan,可以將廣播風暴的影響范圍限制在vlan內。
c)交換機開啟生成樹協議(STP/RSTP/MSTP),可以避免網絡環路造成的廣播風暴。
d)開啟安全防護軟件,可以減少網絡病毒造成的廣播風暴。
8.分布式的一致性問題。
鏈接:解決分布式一致性問題的方案
9.平時有看過那些技術書籍?
10.平時有看過哪些開源的代碼,或者參與過哪些開源的項目?
總的來說深信服的技術面會參考應聘者的簡歷,問一些跟簡歷相關的內容。
另外,他們還會讓應聘者手寫代碼,實現基本的算法,考察應聘者數據結構與算法的功底。