圖解學(xué)習(xí)網(wǎng)站:https://xiaolincoding.com
大家好,我是小林。
之前我在公眾號(hào)說(shuō)過(guò) 25 屆理想汽車的校招薪資,總包有 40w+,屬實(shí)是一線大廠梯隊(duì)的校招薪資了,跟騰訊、字節(jié)、阿里巴巴等互聯(lián)網(wǎng)大廠是一個(gè)梯隊(duì)的薪資了,這么看理想汽車的薪資還是蠻“理想的。”
25 屆理想校招年薪是 16 薪,這跟以前不一樣,以前都是 14 薪為主,所以 25 屆的月薪 base 會(huì)比以前之前的少一些,但是整體的總包是差不多的。
比如有 24 屆訓(xùn)練營(yíng)同學(xué)拿到了理想 offer,開(kāi)的是 31k x 14(總包43.4w),base 就是 31k。
當(dāng)時(shí)談到理想汽車今年校招給了 16 薪,不少讀者留言說(shuō),16 薪別想了,能拿滿 14 薪都偷笑了。
每個(gè)公司的年終獎(jiǎng)都是沒(méi)辦法說(shuō)一定能拿滿的,除了跟自己本身績(jī)效有關(guān)系之外,還跟公司當(dāng)年的市場(chǎng)環(huán)境有很大的關(guān)系,甚至也跟所在部門給公司創(chuàng)作的價(jià)值也有關(guān)系。
簡(jiǎn)單來(lái)說(shuō),公司超預(yù)期賺錢了,那么之前談的年終數(shù)很大概率能兌現(xiàn)成功,當(dāng)然公司賺錢不及預(yù)期,年終數(shù)自然有可能會(huì)減少的,甚至可能直接沒(méi)有了。
理想汽車在 23 年銷量暴漲,23 年全年共交付了 37w+ 輛車, 同比增長(zhǎng)182.2%,這一年理想成了交付量最高的中國(guó)新勢(shì)力車企。
既然 23 年市場(chǎng)反饋那么猛,年終獎(jiǎng)自然只會(huì)多不會(huì)少呀,去年就有理想汽車的員工爆料拿到了「超高的年終獎(jiǎng)」,能打達(dá)到?7-8 個(gè)月的水平,這比預(yù)期的 2 個(gè)月(14 薪)年終獎(jiǎng),翻了 3-4 倍啊,還是蠻給力的。
當(dāng)時(shí)理想汽車 CEO 還為這次超額年終獎(jiǎng)的事情,說(shuō)了自己的想法,企業(yè)要做到賞罰分明,而且企業(yè)不能只學(xué)習(xí)華為的流程,而不學(xué)華為的利益分配。
只可惜新能源車市場(chǎng)還是太殘酷了,后來(lái)雷總的小米汽車也殺入了新能源汽車的賽道,理想的 MEGA 上市又被瘋狂吐槽,MEGA 新車市場(chǎng)表現(xiàn)也遠(yuǎn)遠(yuǎn)不及預(yù)期。
原本 24 年的銷量目標(biāo)是 80 萬(wàn),結(jié)果最后調(diào)整為了 50 萬(wàn)。最后 24 年理想汽車銷量是達(dá)到了 50 萬(wàn)的目標(biāo),同比增長(zhǎng)了 33.1%,但是與之前年初定的 80 萬(wàn)目標(biāo)少了 30萬(wàn)。
因此,今年理想汽車的年終獎(jiǎng)肯定是沒(méi)有去年超額年終獎(jiǎng)的了。
根據(jù)脈脈網(wǎng)的爆料,今年理想汽車年終獎(jiǎng)基本是 14 薪左右,也就是 2 個(gè)月年終獎(jiǎng),還真給之前留言的讀者說(shuō)對(duì), 拿不到 16 薪資。
關(guān)鍵是有些員工加入理想汽車的時(shí)候,是以 16 薪進(jìn)來(lái)的,現(xiàn)在拿到是 14 薪,有一定的落差感,感覺(jué)之前談薪談了個(gè)寂寞。
如果之前是以 16 薪談的,那肯定是虧了的,因?yàn)橄啾?14 薪的,base 會(huì)低一些的,那最后既然是 14 薪,那還不如按 14 薪談總包。
所以啊,咱們談薪的時(shí)候,最好爭(zhēng)取 base 高點(diǎn)的方案,年終數(shù)得等到年底才能知道,而且能不能拿滿還另說(shuō),不可預(yù)知的事情太多,不如每個(gè)月拿到手的錢多一些更好。
好了,接下來(lái)聊聊理想汽車的面經(jīng),之前已經(jīng)分析過(guò)理想汽車的 Java 面經(jīng),這次我們看點(diǎn)不一樣的。
來(lái)看看理想汽車 C++ 崗的面經(jīng),是一面的面經(jīng),拷打了近 50 分鐘,從 C++11 新特性、智能指針、迭代器、STL原理、Linux 內(nèi)存、HTTPS、排序算法等底層原理進(jìn)行的盤問(wèn)。
你們覺(jué)得難度如何?
理想汽車(C++一面)
C++11 新特性了解哪些內(nèi)容?
類型推導(dǎo)與簡(jiǎn)化語(yǔ)法
特性名稱 | 描述 | 示例 |
---|---|---|
auto ?關(guān)鍵字 |
自動(dòng)推導(dǎo)變量類型,簡(jiǎn)化復(fù)雜類型聲明(如迭代器)。 | auto x = 42; ?→?int x |
decltype |
推導(dǎo)表達(dá)式類型,保留?const ?和引用屬性,適用于模板編程。 |
int i=1; decltype(i) j = i; ?→?int j |
右值引用與移動(dòng)語(yǔ)義
右值引用(&& ) |
區(qū)分左值/右值,支持資源高效轉(zhuǎn)移(如臨時(shí)對(duì)象)。 | std::vector<int> v2 = std::move(v1); |
---|---|---|
移動(dòng)構(gòu)造函數(shù)/賦值運(yùn)算符 | 減少深拷貝開(kāi)銷,通過(guò)資源轉(zhuǎn)移提升性能。 | 類中定義?ClassName(ClassName&& other) noexcept; |
完美轉(zhuǎn)發(fā)(std::forward ) |
保持參數(shù)原始類型,避免多次拷貝。 | 模板中使用?std::forward<T>(arg) |
智能指針
std::unique_ptr |
獨(dú)占所有權(quán),不可復(fù)制但可移動(dòng),替代?auto_ptr 。 |
std::unique_ptr<int> ptr = std::make_unique<int>(10); |
---|---|---|
std::shared_ptr |
共享所有權(quán),引用計(jì)數(shù)管理資源,線程安全。 | std::shared_ptr<int> ptr = std::make_shared<int>(10); |
std::weak_ptr |
解決?shared_ptr ?循環(huán)引用問(wèn)題。 |
std::weak_ptr<int> w_ptr = s_ptr; |
函數(shù)與模板增強(qiáng)
Lambda 表達(dá)式 | 匿名函數(shù),支持捕獲外部變量,簡(jiǎn)化回調(diào)和算法。 | std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; }); |
---|---|---|
變長(zhǎng)參數(shù)模板 | 支持任意數(shù)量/類型的模板參數(shù),用于元編程和容器設(shè)計(jì)。 | template<typename... Args> void func(Args... args); |
constexpr ?常量表達(dá)式 |
編譯時(shí)求值,優(yōu)化性能,允許函數(shù)在編譯期執(zhí)行。 | constexpr int factorial(int n) { return n <=1 ? 1 : n*factorial(n-1); } |
并發(fā)編程支持
std::thread |
原生多線程支持,結(jié)合互斥鎖和原子操作實(shí)現(xiàn)同步。 | std::thread t(func); t.join(); |
---|---|---|
std::async ?和?std::future |
簡(jiǎn)化異步任務(wù)管理,獲取異步操作結(jié)果。 | auto future = std::async(func); int result = future.get(); |
智能指針介紹一下?
std::shared_ptr:是一種引用計(jì)數(shù)智能指針,允許多個(gè)shared_ptr對(duì)象共享同一個(gè)對(duì)象,并通過(guò)引用計(jì)數(shù)來(lái)管理內(nèi)存的釋放,當(dāng)最后一個(gè)shared_ptr指向?qū)ο笪鰳?gòu)時(shí),會(huì)釋放對(duì)象所占用的內(nèi)存。不適合處理循環(huán)引用的情況,可能導(dǎo)致內(nèi)存泄漏。
std::unique_ptr:是一種獨(dú)占式智能指針,確保只有一個(gè)指針可以指向一個(gè)對(duì)象,不能進(jìn)行復(fù)制,但可以移動(dòng)它的所有權(quán)。
std::weak_ptr:是用于解決std::shared_ptr可能導(dǎo)致的循環(huán)引用問(wèn)題的智能指針。它允許引用shared_ptr所管理的對(duì)象,但不會(huì)增加引用計(jì)數(shù),不會(huì)影響對(duì)象的生命周期,避免循環(huán)引用導(dǎo)致的內(nèi)存泄漏。
shared_ptr的作用是什么?
在傳統(tǒng)的 C++ 編程中,使用?new
?操作符分配的內(nèi)存需要手動(dòng)使用?delete
?操作符釋放,若忘記釋放或者在異常情況下無(wú)法執(zhí)行?delete
?操作,就會(huì)造成內(nèi)存泄漏。
std::shared_ptr
?可以自動(dòng)處理內(nèi)存的釋放,當(dāng)不再有?std::shared_ptr
?指向該對(duì)象時(shí),對(duì)象的內(nèi)存會(huì)被自動(dòng)釋放。
std::shared_ptr
?支持多個(gè)?std::shared_ptr
?實(shí)例共享同一個(gè)對(duì)象的所有權(quán)。它通過(guò)引用計(jì)數(shù)機(jī)制來(lái)實(shí)現(xiàn)這一點(diǎn),每個(gè)?std::shared_ptr
?都會(huì)維護(hù)一個(gè)引用計(jì)數(shù),記錄有多少個(gè)?std::shared_ptr
?共享同一個(gè)對(duì)象。當(dāng)引用計(jì)數(shù)變?yōu)?0 時(shí),對(duì)象的內(nèi)存會(huì)被釋放。
#include <iostream>
#include <memory>
class?MyClass?{
public:
? ? MyClass() { std::cout <<?"MyClass constructor"?<< std::endl; }
? ? ~MyClass() { std::cout <<?"MyClass destructor"?<< std::endl; }
};
int?main()?{
? ? std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
? ? std::shared_ptr<MyClass> ptr2 = ptr1;?// 共享所有權(quán)
? ? std::cout <<?"Use count: "?<< ptr1.use_count() << std::endl;?// 輸出 2
? ? ptr2.reset();?// 釋放 ptr2 的所有權(quán)
? ? std::cout <<?"Use count: "?<< ptr1.use_count() << std::endl;?// 輸出 1
? ??// 當(dāng) ptr1 離開(kāi)作用域時(shí),MyClass 對(duì)象的內(nèi)存會(huì)被釋放
? ??return0;
}
在這個(gè)例子中,ptr1
?和?ptr2
?共享同一個(gè)?MyClass
?對(duì)象的所有權(quán),引用計(jì)數(shù)變?yōu)?2。當(dāng)調(diào)用?ptr2.reset()
?時(shí),ptr2
?釋放了對(duì)對(duì)象的所有權(quán),引用計(jì)數(shù)減為 1。當(dāng)?ptr1
?離開(kāi)作用域時(shí),引用計(jì)數(shù)變?yōu)?0,對(duì)象的內(nèi)存被釋放。
weak_ptr的作用是什么?如何和shared_ptr結(jié)合使用?
std::weak_ptr
?主要用于輔助?std::shared_ptr
?進(jìn)行內(nèi)存管理,解決?std::shared_ptr
?可能存在的循環(huán)引用問(wèn)題,同時(shí)還可以用于觀察?std::shared_ptr
?所管理對(duì)象的生命周期。
當(dāng)兩個(gè)或多個(gè)?std::shared_ptr
?相互引用形成循環(huán)時(shí),會(huì)導(dǎo)致引用計(jì)數(shù)永遠(yuǎn)不會(huì)降為 0,從而造成內(nèi)存泄漏。std::weak_ptr
?不會(huì)增加所指向?qū)ο蟮囊糜?jì)數(shù),因此可以打破這種循環(huán)引用。
#include <iostream>
#include <memory>
class?B;
class?A?{
public:
? ? std::shared_ptr<B> b_ptr;
? ? ~A() { std::cout <<?"A destructor"?<< std::endl; }
};
class?B?{
public:
? ? std::weak_ptr<A> a_ptr; ?// 使用 std::weak_ptr 打破循環(huán)引用
? ? ~B() { std::cout <<?"B destructor"?<< std::endl; }
};
int?main()?{
? ? std::shared_ptr<A> a = std::make_shared<A>();
? ? std::shared_ptr<B> b = std::make_shared<B>();
? ? a->b_ptr = b;
? ? b->a_ptr = a;
? ??return0;
}
在上述代碼中,如果?B
?類中的?a_ptr
?也使用?std::shared_ptr
,就會(huì)形成循環(huán)引用,導(dǎo)致?A
?和?B
?對(duì)象的內(nèi)存無(wú)法釋放。使用?std::weak_ptr
?后,b->a_ptr
?不會(huì)增加?A
?對(duì)象的引用計(jì)數(shù),當(dāng)?main
?函數(shù)結(jié)束時(shí),a
?和?b
?的引用計(jì)數(shù)降為 0,A
?和?B
?對(duì)象的內(nèi)存會(huì)被正確釋放。
另外,std::weak_ptr
?可以用于觀察?std::shared_ptr
?所管理對(duì)象的生命周期。通過(guò)?std::weak_ptr
?的?expired()
?方法可以檢查所指向的對(duì)象是否已經(jīng)被釋放。
#include <iostream>
#include <memory>
int?main()?{
? ? std::shared_ptr<int> shared = std::make_shared<int>(42);
? ? std::weak_ptr<int> weak = shared;
? ??if?(!weak.expired()) {
? ? ? ? std::cout <<?"Object is still alive."?<< std::endl;
? ? }
? ? shared.reset();
? ??if?(weak.expired()) {
? ? ? ? std::cout <<?"Object has been destroyed."?<< std::endl;
? ? }
? ??return0;
}
在這個(gè)例子中,weak
?觀察?shared
?所管理的對(duì)象。當(dāng)?shared
?釋放對(duì)象后,weak.expired()
?返回?true
,表示對(duì)象已經(jīng)被銷毀。
lambda表達(dá)式的原理是什么?
從本質(zhì)上講,Lambda 表達(dá)式是編譯器自動(dòng)生成的一個(gè)匿名的函數(shù)對(duì)象(也稱為仿函數(shù))。當(dāng)我們編寫一個(gè) Lambda 表達(dá)式時(shí),編譯器會(huì)創(chuàng)建一個(gè)未命名的類,這個(gè)類重載了函數(shù)調(diào)用運(yùn)算符?operator()
。
#include <iostream>
int?main()?{
? ? auto lambda = [](int?a,?int?b) {?return?a + b; };
? ??int?result = lambda(3,?4);
? ? std::cout <<?"Result: "?<< result << std::endl;
? ??return?0;
}
編譯器會(huì)將上述 Lambda 表達(dá)式轉(zhuǎn)換為類似下面的代碼:
#include <iostream>
// 編譯器生成的未命名類
class?__lambda_4_13?{
public:
? ? __lambda_4_13() =?default;
? ??inline?int?operator()(int?a,?int?b)?const?{
? ? ? ??return?a + b;
? ? }
};
int?main()?{
? ? __lambda_4_13 lambda;
? ??int?result = lambda(3,?4);
? ? std::cout <<?"Result: "?<< result << std::endl;
? ??return0;
}
stl迭代器的失效情況你知道哪些?
序列式容器(
vector
、deque
),迭代器失效的情況
當(dāng)在?vector
?中插入元素時(shí),如果插入操作導(dǎo)致容器的內(nèi)存重新分配(即插入后容器的容量不足,需要重新分配更大的內(nèi)存空間),那么所有指向?vector
?的迭代器、指針和引用都會(huì)失效。因?yàn)橹匦路峙鋬?nèi)存后,元素會(huì)被移動(dòng)到新的內(nèi)存位置,例子代碼:
#include <iostream>
#include <vector>
int?main()?{
? ? std::vector<int> vec = {1,?2,?3};
? ? auto it = vec.begin();
? ? vec.push_back(4);?// 可能導(dǎo)致內(nèi)存重新分配
? ??// 此時(shí) it 可能已經(jīng)失效
? ??// std::cout << *it << std::endl; // 未定義行為
? ??return?0;
}
當(dāng)在?vector
?中刪除元素時(shí),指向被刪除元素的迭代器、指針和引用會(huì)失效,并且指向刪除位置之后的元素的迭代器、指針和引用也會(huì)失效。代碼如下:
#include <iostream>
#include <vector>
int?main()?{
? ? std::vector<int> vec = {1,?2,?3};
? ? auto it = vec.begin() +?1;
? ? vec.erase(vec.begin());?// 刪除第一個(gè)元素
? ??// 此時(shí) it 失效
? ??// std::cout << *it << std::endl; // 未定義行為
? ??return?0;
}
在?deque
?的中間插入元素時(shí),所有迭代器、指針和引用都會(huì)失效,還有在?deque
?的頭部或尾部插入元素時(shí),指向元素的迭代器、指針和引用不會(huì)失效,但如果插入操作導(dǎo)致內(nèi)存重新分配,那么迭代器可能會(huì)失效。
#include <iostream>
#include <deque>
int?main()?{
? ? std::deque<int> deq = {1,?2,?3};
? ? auto it = deq.begin() +?1;
? ? deq.insert(deq.begin() +?1,?4);?// 在中間插入元素
? ??// 此時(shí) it 失效
? ??// std::cout << *it << std::endl; // 未定義行為
? ??return?0;
}
刪除?deque
?中間的元素時(shí),所有迭代器、指針和引用都會(huì)失效,還有刪除?deque
?頭部或尾部的元素時(shí),指向被刪除元素的迭代器、指針和引用會(huì)失效。
#include <iostream>
#include <deque>
int?main()?{
? ? std::deque<int> deq = {1,?2,?3};
? ? auto it = deq.begin() +?1;
? ? deq.erase(deq.begin() +?1);?// 刪除中間元素
? ??// 此時(shí) it 失效
? ??// std::cout << *it << std::endl; // 未定義行為
? ??return?0;
}
關(guān)聯(lián)式容器(
set
、map
),迭代器失效的情況
set
?在插入元素不會(huì)使任何迭代器、指針和引用失效,因?yàn)殛P(guān)聯(lián)式容器使用紅黑樹(shù)等平衡二叉搜索樹(shù)實(shí)現(xiàn),插入操作只是在樹(shù)中添加新節(jié)點(diǎn),不會(huì)影響其他節(jié)點(diǎn)的內(nèi)存位置。
不過(guò)指向被刪除元素的迭代器、指針和引用會(huì)失效,其他迭代器、指針和引用不會(huì)失效。
#include <iostream>
#include <set>
int?main()?{
? ? std::set<int> s = {1,?2,?3};
? ? auto it = s.begin();
? ? auto it_to_delete = s.find(2);
? ? s.erase(it_to_delete);
? ??// 此時(shí) it_to_delete 失效
? ??// std::cout << *it_to_delete << std::endl; // 未定義行為
? ? std::cout << *it << std::endl;?// 正常輸出
? ??return?0;
}
map
?在插入元素不會(huì)使任何迭代器、指針和引用失效,原因與?set
?。但是指向被刪除元素的迭代器、指針和引用會(huì)失效,其他迭代器、指針和引用不會(huì)失效。
#include <iostream>
#include <map>
int?main()?{
? ? std::map<int,?int> m = {{1,?10}, {2,?20}, {3,?30}};
? ? auto it = m.begin();
? ? auto it_to_delete = m.find(2);
? ? m.erase(it_to_delete);
? ??// 此時(shí) it_to_delete 失效
? ??// std::cout << it_to_delete->second << std::endl; // 未定義行為
? ? std::cout << it->second << std::endl;?// 正常輸出
? ??return?0;
}
鏈表式容器(
list
),迭代器失效
在?list
?插入元素不會(huì)使任何迭代器、指針和引用失效,因?yàn)殒湵淼牟迦氩僮髦皇切薷墓?jié)點(diǎn)的指針,不會(huì)影響其他節(jié)點(diǎn)的內(nèi)存位置。但是,指向被刪除元素的迭代器、指針和引用會(huì)失效,其他迭代器、指針和引用不會(huì)失效。
#include <iostream>
#include <list>
int?main()?{
? ? std::list<int> lst = {1,?2,?3};
? ? auto it = lst.begin();
? ? auto it_to_delete = ++lst.begin();
? ? lst.erase(it_to_delete);
? ??// 此時(shí) it_to_delete 失效
? ??// std::cout << *it_to_delete << std::endl; // 未定義行為
? ? std::cout << *it << std::endl;?// 正常輸出
? ??return?0;
}
unordered_map的底層結(jié)構(gòu)是什么?
底層結(jié)構(gòu)基于哈希表實(shí)現(xiàn)。
std::unordered_map
?內(nèi)部維護(hù)了一個(gè)哈希桶數(shù)組,數(shù)組中的每個(gè)元素稱為一個(gè)哈希桶。每個(gè)哈希桶可以存儲(chǔ)一個(gè)或多個(gè)鍵值對(duì)。當(dāng)多個(gè)鍵通過(guò)哈希函數(shù)計(jì)算得到相同的索引時(shí),就會(huì)發(fā)生哈希沖突。
std::unordered_map
?通常使用鏈地址法來(lái)解決哈希沖突。在鏈地址法中,每個(gè)哈希桶是一個(gè)鏈表,當(dāng)發(fā)生哈希沖突時(shí),新的鍵值對(duì)會(huì)被插入到對(duì)應(yīng)的鏈表或容器中。
linux程序崩潰怎么定位問(wèn)題?
如果開(kāi)啟了core dump文件的轉(zhuǎn)存,那么程序崩潰之后,就會(huì)生成 core dump 文件,這里面會(huì)有程序運(yùn)行時(shí)的堆棧信息,然后我們可以用 gdp 工具去分析 core dump 文件。
gdb myapp core
在 GDB 中,使用?bt
(backtrace)命令可以查看程序崩潰時(shí)的調(diào)用棧:
(gdb) bt
如果程序崩潰問(wèn)題可以重現(xiàn),頁(yè)可以使用 GDB 直接調(diào)試程序。在編譯程序時(shí),使用?-g
?選項(xiàng)添加調(diào)試信息。例如:
gcc -g -o myapp myapp.c
使用 GDB 啟動(dòng)程序并運(yùn)行,當(dāng)程序崩潰時(shí),GDB 會(huì)停在崩潰的位置。
gdb?myapp
(gdb)?run
程序崩潰后,使用?bt
?命令查看調(diào)用棧,找出問(wèn)題所在的函數(shù)和代碼行。
有些程序會(huì)在崩潰時(shí)輸出錯(cuò)誤信息到標(biāo)準(zhǔn)錯(cuò)誤輸出(stderr),那么這時(shí)候直接去看錯(cuò)誤日志就行了。
?cat error.log
linux的內(nèi)存是如何組織的?
操作系統(tǒng)設(shè)計(jì)了虛擬內(nèi)存,每個(gè)進(jìn)程都有自己的獨(dú)立的虛擬內(nèi)存,我們所寫的程序不會(huì)直接與物理內(nèi)打交道。
有了虛擬內(nèi)存之后,它帶來(lái)了這些好處:
- 第一,虛擬內(nèi)存可以使得進(jìn)程對(duì)運(yùn)行內(nèi)存超過(guò)物理內(nèi)存大小,因?yàn)槌绦蜻\(yùn)行符合局部性原理,CPU 訪問(wèn)內(nèi)存會(huì)有很明顯的重復(fù)訪問(wèn)的傾向性,對(duì)于那些沒(méi)有被經(jīng)常使用到的內(nèi)存,我們可以把它換出到物理內(nèi)存之外,比如硬盤上的 swap 區(qū)域。第二,由于每個(gè)進(jìn)程都有自己的頁(yè)表,所以每個(gè)進(jìn)程的虛擬內(nèi)存空間就是相互獨(dú)立的。進(jìn)程也沒(méi)有辦法訪問(wèn)其他進(jìn)程的頁(yè)表,所以這些頁(yè)表是私有的,這就解決了多進(jìn)程之間地址沖突的問(wèn)題。第三,頁(yè)表里的頁(yè)表項(xiàng)中除了物理地址之外,還有一些標(biāo)記屬性的比特,比如控制一個(gè)頁(yè)的讀寫權(quán)限,標(biāo)記該頁(yè)是否存在等。在內(nèi)存訪問(wèn)方面,操作系統(tǒng)提供了更好的安全性。
Linux 是通過(guò)對(duì)內(nèi)存分頁(yè)的方式來(lái)管理內(nèi)存,分頁(yè)是把整個(gè)虛擬和物理內(nèi)存空間切成一段段固定尺寸的大小。這樣一個(gè)連續(xù)并且尺寸固定的內(nèi)存空間,我們叫頁(yè)(Page)。在 Linux 下,每一頁(yè)的大小為 4KB。
虛擬地址與物理地址之間通過(guò)頁(yè)表來(lái)映射,如下圖:
頁(yè)表是存儲(chǔ)在內(nèi)存里的,內(nèi)存管理單元?(MMU)就做將虛擬內(nèi)存地址轉(zhuǎn)換成物理地址的工作。
而當(dāng)進(jìn)程訪問(wèn)的虛擬地址在頁(yè)表中查不到時(shí),系統(tǒng)會(huì)產(chǎn)生一個(gè)缺頁(yè)異常,進(jìn)入系統(tǒng)內(nèi)核空間分配物理內(nèi)存、更新進(jìn)程頁(yè)表,最后再返回用戶空間,恢復(fù)進(jìn)程的運(yùn)行。
你說(shuō)到缺頁(yè)中斷,能介紹一下缺頁(yè)中斷么?
缺頁(yè)中斷是指在虛擬內(nèi)存管理中,當(dāng)程序試圖訪問(wèn)的頁(yè)面(Page)不在物理內(nèi)存中,而在磁盤等外存上時(shí),操作系統(tǒng)會(huì)產(chǎn)生一個(gè)中斷,將該頁(yè)面從外存調(diào)入物理內(nèi)存,以滿足程序的訪問(wèn)需求。這種中斷就被稱為缺頁(yè)中斷。
對(duì)于缺頁(yè)中斷這種情況,linux操作系統(tǒng)是如何減少頁(yè)面的換入換出的呢,有哪些方式?
按需加載機(jī)制:僅在進(jìn)程實(shí)際訪問(wèn)內(nèi)存時(shí)才分配物理頁(yè)框,而非預(yù)先分配所有所需內(nèi)存。例如,mmap 和?malloc
僅建立虛擬地址映射,物理頁(yè)的分配延遲到首次訪問(wèn)時(shí)觸發(fā)缺頁(yè)中斷,這樣可以減少不必要的物理內(nèi)存占用,避免過(guò)早的頁(yè)面換入換出。寫時(shí)復(fù)制:在進(jìn)程?fork
時(shí),父子進(jìn)程共享只讀頁(yè)面;當(dāng)任一進(jìn)程嘗試寫入時(shí),觸發(fā)缺頁(yè)中斷并復(fù)制新物理頁(yè),避免不必要的內(nèi)存復(fù)制。內(nèi)存預(yù)?。何募成洌ㄈ?mmap
)時(shí),內(nèi)核在缺頁(yè)處理中預(yù)加載相鄰文件內(nèi)容到內(nèi)存,減少后續(xù)缺頁(yè)次數(shù)。內(nèi)存分配策略:優(yōu)先使用空閑頁(yè)框,避免頻繁觸發(fā)頁(yè)面回收。例如,通過(guò)伙伴系統(tǒng)和 Slab 分配器高效管理內(nèi)存碎片。
你了解數(shù)字證書么?
數(shù)字證書采用公鑰加密技術(shù),它將實(shí)體的身份信息與公鑰綁定在一起,并由權(quán)威的證書頒發(fā)機(jī)構(gòu)(CA)進(jìn)行數(shù)字簽名。
具體來(lái)說(shuō),當(dāng)一個(gè)實(shí)體申請(qǐng)數(shù)字證書時(shí),它會(huì)生成一對(duì)密鑰,即公鑰和私鑰,然后將公鑰以及自身的身份信息提交給 CA。CA 會(huì)對(duì)這些信息進(jìn)行驗(yàn)證,驗(yàn)證通過(guò)后,CA 使用自己的私鑰對(duì)這些信息進(jìn)行簽名,生成數(shù)字證書。在數(shù)字證書中,包含了證書持有者的身份信息、公鑰、證書頒發(fā)機(jī)構(gòu)的信息、有效期等內(nèi)容。
數(shù)字證書的作用是接收方可以通過(guò)驗(yàn)證數(shù)字證書來(lái)確認(rèn)發(fā)送方的身份是否真實(shí)可靠。例如,在訪問(wèn)銀行網(wǎng)站時(shí),瀏覽器會(huì)驗(yàn)證銀行網(wǎng)站的數(shù)字證書,以確保用戶連接的是真實(shí)的銀行網(wǎng)站,而不是假冒的釣魚(yú)網(wǎng)站。
https的握手過(guò)程說(shuō)一下?
傳統(tǒng)的 TLS 握手基本都是使用 RSA 算法來(lái)實(shí)現(xiàn)密鑰交換的,在將 TLS 證書部署服務(wù)端時(shí),證書文件其實(shí)就是服務(wù)端的公鑰,會(huì)在 TLS 握手階段傳遞給客戶端,而服務(wù)端的私鑰則一直留在服務(wù)端,一定要確保私鑰不能被竊取。
在 RSA 密鑰協(xié)商算法中,客戶端會(huì)生成隨機(jī)密鑰,并使用服務(wù)端的公鑰加密后再傳給服務(wù)端。根據(jù)非對(duì)稱加密算法,公鑰加密的消息僅能通過(guò)私鑰解密,這樣服務(wù)端解密后,雙方就得到了相同的密鑰,再用它加密應(yīng)用消息。
我用 Wireshark 工具抓了用 RSA 密鑰交換的 TLS 握手過(guò)程,你可以從下面看到,一共經(jīng)歷了四次握手:
TLS 第一次握手
首先,由客戶端向服務(wù)器發(fā)起加密通信請(qǐng)求,也就是 ClientHello 請(qǐng)求。在這一步,客戶端主要向服務(wù)器發(fā)送以下信息:
- (1)客戶端支持的 TLS 協(xié)議版本,如 TLS 1.2 版本。(2)客戶端生產(chǎn)的隨機(jī)數(shù)(Client Random),后面用于生成「會(huì)話秘鑰」條件之一。(3)客戶端支持的密碼套件列表,如 RSA 加密算法。
TLS 第二次握手
服務(wù)器收到客戶端請(qǐng)求后,向客戶端發(fā)出響應(yīng),也就是 SeverHello。服務(wù)器回應(yīng)的內(nèi)容有如下內(nèi)容:
- (1)確認(rèn) TLS 協(xié)議版本,如果瀏覽器不支持,則關(guān)閉加密通信。(2)服務(wù)器生產(chǎn)的隨機(jī)數(shù)(Server Random),也是后面用于生產(chǎn)「會(huì)話秘鑰」條件之一。(3)確認(rèn)的密碼套件列表,如 RSA 加密算法。(4)服務(wù)器的數(shù)字證書。
TLS 第三次握手
客戶端收到服務(wù)器的回應(yīng)之后,首先通過(guò)瀏覽器或者操作系統(tǒng)中的 CA 公鑰,確認(rèn)服務(wù)器的數(shù)字證書的真實(shí)性。
如果證書沒(méi)有問(wèn)題,客戶端會(huì)從數(shù)字證書中取出服務(wù)器的公鑰,然后使用它加密報(bào)文,向服務(wù)器發(fā)送如下信息:
- (1)一個(gè)隨機(jī)數(shù)(pre-master key)。該隨機(jī)數(shù)會(huì)被服務(wù)器公鑰加密。(2)加密通信算法改變通知,表示隨后的信息都將用「會(huì)話秘鑰」加密通信。(3)客戶端握手結(jié)束通知,表示客戶端的握手階段已經(jīng)結(jié)束。這一項(xiàng)同時(shí)把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個(gè)摘要,用來(lái)供服務(wù)端校驗(yàn)。
上面第一項(xiàng)的隨機(jī)數(shù)是整個(gè)握手階段的第三個(gè)隨機(jī)數(shù),會(huì)發(fā)給服務(wù)端,所以這個(gè)隨機(jī)數(shù)客戶端和服務(wù)端都是一樣的。
服務(wù)器和客戶端有了這三個(gè)隨機(jī)數(shù)(Client Random、Server Random、pre-master key),接著就用雙方協(xié)商的加密算法,各自生成本次通信的「會(huì)話秘鑰」。
TLS 第四次握手
服務(wù)器收到客戶端的第三個(gè)隨機(jī)數(shù)(pre-master key)之后,通過(guò)協(xié)商的加密算法,計(jì)算出本次通信的「會(huì)話秘鑰」。
然后,向客戶端發(fā)送最后的信息:
- (1)加密通信算法改變通知,表示隨后的信息都將用「會(huì)話秘鑰」加密通信。(2)服務(wù)器握手結(jié)束通知,表示服務(wù)器的握手階段已經(jīng)結(jié)束。這一項(xiàng)同時(shí)把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個(gè)摘要,用來(lái)供客戶端校驗(yàn)。
至此,整個(gè) TLS 的握手階段全部結(jié)束。接下來(lái),客戶端與服務(wù)器進(jìn)入加密通信,就完全是使用普通的 HTTP 協(xié)議,只不過(guò)用「會(huì)話秘鑰」加密內(nèi)容。
對(duì)于https中的加密算法,rsa和ecc橢圓加密,這兩者有什么區(qū)別么?
- RSA 加密算法:國(guó)際標(biāo)準(zhǔn)算法,應(yīng)用較早的算法之一,普遍性更強(qiáng),相比 ECC 算法的適用范圍更廣,兼容性更好,一般采用2048位的加密長(zhǎng)度,服務(wù)端性能消耗較高。ECC 加密算法:橢圓加密算法,新一代算法趨勢(shì)主流,一般采用256位加密長(zhǎng)度(相當(dāng)于 RSA 3072 位加密強(qiáng)度)更安全,抗攻擊性更強(qiáng),相比 RSA 算法加密速度快,效率更高,服務(wù)器資源消耗更低。
排序算法介紹一下?
- 冒泡排序:通過(guò)相鄰元素的比較和交換,每次將最大(或最?。┑脑刂鸩健懊芭荨钡阶詈螅ɑ蜃钋埃r(shí)間復(fù)雜度:最好情況下O(n),最壞情況下O(n^2),平均情況下O(n^2)。
- 空間復(fù)雜度:O(1)。插入排序:將待排序元素逐個(gè)插入到已排序序列的合適位置,形成有序序列。時(shí)間復(fù)雜度:最好情況下O(n),最壞情況下O(n^2),平均情況下O(n^2),空間復(fù)雜度:O(1)。選擇排序(Selection Sort):通過(guò)不斷選擇未排序部分的最?。ɑ蜃畲螅┰?,并將其放置在已排序部分的末尾(或開(kāi)頭)。
- 時(shí)間復(fù)雜度:最好情況下O(n^2),最壞情況下O(n^2),平均情況下O(n^2),空間復(fù)雜度:O(1)??焖倥判颍≦uick Sort):通過(guò)選擇一個(gè)基準(zhǔn)元素,將數(shù)組劃分為兩個(gè)子數(shù)組,使得左子數(shù)組的元素都小于(或等于)基準(zhǔn)元素,右子數(shù)組的元素都大于(或等于)基準(zhǔn)元素,然后對(duì)子數(shù)組進(jìn)行遞歸排序。時(shí)間復(fù)雜度:最好情況下O(nlogn),最壞情況下O(n^2),平均情況下O(nlogn),空間復(fù)雜度:最好情況下O(logn),最壞情況下O(n)。歸并排序(Merge Sort):將數(shù)組不斷分割為更小的子數(shù)組,然后將子數(shù)組進(jìn)行合并,合并過(guò)程中進(jìn)行排序。時(shí)間復(fù)雜度:最好情況下O(nlogn),最壞情況下O(nlogn),平均情況下O(nlogn)??臻g復(fù)雜度:O(n)。堆排序(Heap Sort):通過(guò)將待排序元素構(gòu)建成一個(gè)最大堆(或最小堆),然后將堆頂元素與末尾元素交換,再重新調(diào)整堆,重復(fù)該過(guò)程直到排序完成。時(shí)間復(fù)雜度:最好情況下O(nlogn),最壞情況下O(nlogn),平均情況下O(nlogn)。空間復(fù)雜度:O(1)。
大文件排序應(yīng)該怎么辦?
可以采用外部排序的方式。外部排序主要分為兩個(gè)階段:
1. 分段排序(生成初始?xì)w并段):將大文件分割成多個(gè)較小的片段,這些片段的大小要能夠適應(yīng)內(nèi)存的限制,接著把每個(gè)片段加載到內(nèi)存中,使用內(nèi)部排序算法(如快速排序、歸并排序等)對(duì)其進(jìn)行排序,最后將排序好的片段寫回到磁盤中,這些片段就被稱為初始?xì)w并段。2. 歸并操作:將多個(gè)初始?xì)w并段逐步合并成一個(gè)有序的大文件。在歸并過(guò)程中,每次從各個(gè)歸并段中讀取一部分?jǐn)?shù)據(jù)到內(nèi)存中,比較這些數(shù)據(jù)并將最小(或最大)的元素依次寫入輸出文件,同時(shí)不斷從歸并段中補(bǔ)充數(shù)據(jù),直到所有歸并段的數(shù)據(jù)都被處理完畢。