# C語言與作業系統觀念(持續更新中) ###### tags: `c` `linux` --- 此篇目的是自我釐清C語言與作業系統的觀念 ## 觀念釐清 `*ptr`的\*唸做『value of』 or 『dereference』。 `&ptr`的&唸『address of』。 `**ptr` 的\*\*不能唸『雙指標』,要唸成『pointer to pointer』。 lvalue 是『locator value』,不應該唸成『左值』。 `sizeof(type)` 是"operator"不是function,在**編譯時期計**算出資料型別的大小,常使用 sizeof(array) / sizeof(array[0])取得陣列個數。 C 永遠只有 call by value ,沒有~~call by address~~。 `parameter`: 用於函式宣告。 void swap(int par1,int par2); `argument` : 用於呼叫函式。 swap(arg1,arg2); ## C雜談 void **(*d) (int &, char **(*)(char *, char **)); [你所不知道的C語言:指標篇](https://hackmd.io/@sysprog/c-pointer?type=view) * d is a pointer to a function that takes two parameters: * a reference to an int and * a pointer to a function that takes two parameters: * a pointer to a char and * a pointer to a pointer to a char * and returns a pointer to a pointer to a char * and returns a pointer to a pointer to void --- void ( *signal(int sig, void (*handler)(int)) ) (int); [How to read this prototype?](https://stackoverflow.com/questions/15739500/how-to-read-this-prototype) The whole thing declares a function called signal: * signal takes an int and a function pointer * this function pointer takes an int and returns void * signal returns a function pointer * this function pointer takes an int and returns a void --- [What is the difference between const int*, const int * const, and int const *?](https://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const) Read it backwards (as driven by Clockwise/Spiral Rule): * `int*` - pointer to int * `int const *` - pointer to **const int** * `int * const` - **const pointer** to int * `int const * const` - const pointer to const int Now the first const can be on either side of the type so: * `const int *` == `int const *` * `const int * const` == `int const * const` If you want to go really crazy you can do things like this: * `int **` - pointer to pointer to int * `int ** const` - a const pointer to a pointer to an int * `int * const *` - a pointer to a const pointer to an int * `int const **` - a pointer to a pointer to a const int * `int * const * const` - a const pointer to a const pointer to an int 總結就是 1. const在*的左邊: 代表pointer指向的變數為常數,不能更改變數的值。 2. const在*的右邊: 代表pointer本身是常數,不能派給他新的記憶體位址。 ``` int a = 5, b = 10, c = 15; const int* foo; // pointer to constant int. foo = &a; // assignment to where foo points to. /* dummy statement*/ *foo = 6; // the value of a can´t get changed through the pointer. foo = &b; // the pointer foo can be changed. int *const bar = &c; // constant pointer to int // note, you actually need to set the pointer // here because you can't change it later ;) *bar = 16; // the value of c can be changed through the pointer. /* dummy statement*/ bar = &a; // not possible because bar is a constant pointer. ``` --- ``` int a; // 一個整型數 int *a; // 一個指向整數的指標 int **a; // 一個指向指標的指標,它指向的指標是指向一個整型數 int a[10]; // 一個有10個整數型的陣列 int *a[10]; // 一個有10個指標的陣列,該指標是指向一個整數型的 int (*a)[10]; // 一個指向有10個整數型陣列的指標 int (*a)(int); // 一個指向函數的指標,該函數有一個整數型參數並返回一個整數 int (*a[10])(int); // 一個有10個指標的陣列,該指標指向一個函數,該函數有一個整數型參數並返回一個整數 ``` --- * 記憶體配置 [C 語言程式的記憶體配置概念教學](https://blog.gtwang.org/programming/memory-layout-of-c-program/) 這篇文章已講得很清楚。 ![](https://i.imgur.com/IUCuMFO.png) --- * 三元運算子 * x ? a:b * 如果x為true時運算為a,x為false時運算為b。 --- ## Bitwise Operators [Bitwise Operators in C ](https://www.youtube.com/watch?v=8aFik6lPPaA&ab_channel=NesoAcademy) ![](https://i.imgur.com/t3znM8S.jpg) ![](https://i.imgur.com/fXReqJc.jpg) --- ## 靜態函式庫、共享函式庫與動態函式庫 1. 靜態函式庫(static library):由.o檔(object files)組成的封裝檔,通常以lib為開頭,副檔名為.a檔. - 在編譯時期,就把**所有**會用到的程式都一起把包進去 - 優點:在執行時,不會因為缺少檔案而不能執行 - 缺點:檔案較大,每次更新要重新編譯 2. 共享函式庫(shared library):程式在執行時,才會去載入library,執行檔與share library是分開的,開頭以lib為主,附檔名.so檔. - 優點:檔案大小較小,更新時執行檔也不需要重新編譯 - 缺點:執行時必須要有.so檔,如果是在機器內執行,機器內也必須有此.so檔 3. 動態載入函式庫(dynamically loaded library):DL函式庫搭配share library而成,也是.so檔 - 與share library在載入函示庫時有很大的不同,share library式程式**開始**時自動載入函示庫(不一定都會使用到),dynamically loaded library則是程式**真正**使用時才載入(有用到才載入) - 使用dlopen開啟library ## Linux 硬連結與軟連結:ln 1. 硬連結: 使用相同的inode建立檔案,ln預設使用硬連結 --- ## 作業系統(OS) ### Real-Time OPerating Systems - Well-defined fixed-time constraints - "Real-time" doesn't mean speed,but keeping deadlines. - Guaranteed reponse and reaction times. - Often used as a control device in a dedicated application: - Scientific experiments,medical imaging systems,industrial control systems,weapon systems,etc. ### Process 觸發任何一個事件時,系統都會定義成為一個程序,並且給這個程序一個ID,稱為PID,同時依據啟發這個程序的使用者與相關屬性關係,給予這個PID一組有效的權限設定。 * 程式(program): 通常為binary program,放置在儲存媒體中,為實體檔案的形態存在。 * 程序(process): 程式被觸發後,執行者的權限與屬性,程式碼與所需的資料會被載入記憶體中,作業系統並給予這個記憶體內的單元一個識別碼(PID),可以說程序就是正在運行的程式。 程序的呼叫(fork and exec) ![](https://i.imgur.com/qWiMzhl.jpg) process在linux相關指令,在另一篇[process](https://hackmd.io/gJsPVI89S5-ohNyABwtdLw?view#Process)中。 * 工作管理(job control):當我們登入系統取得bash shell之後,在單一終端機介面下同時進行多個工作的行為管理。 這些工作所觸發的程序必須來自自己shell的子程序 * 前景: 可以控制與下達指令的環境(foreground)。 * 背景: 可以自行運作的工作,無法使用[ctrl]+c來終止,可使用bg/fg呼叫該工作。 * 背景中「執行」的程序不能等待terminal/shell的input。 --- Process 5種狀態 ![](https://i.imgur.com/DHfc70V.jpg) * interrupt * external interrupt: hardware -> signal * internal interrupt: bug,overflow * software interrupt: software -> system call * context switching(內容轉換): CPU在執行時只能用一個process,要載入其他process,需先將process的內容存下來,在切換另一個process。 * short-term schedular: 從記憶體中挑一個從ready state到running state,合理安排每支程式執行time。 * long-term schedular: 選擇從new->ready決定當前有多少支程式可以執行,避免同時太多程式執行。 * medium-term schedular: 將IO,sleep等耗時的事情移出記憶體,挪出記憶體空間,達到好的空間使用。 --- * Layered OS ![](https://i.imgur.com/QSGbsOB.jpg) ``` str[]="Hello" => user space x = x+2 => user space file.write(str) => kernal space write就是 system call y= x+2 => 切回 user space ``` * API VS system call * API: 使用者介面可以使用的函式庫。 * system call: OS可以使用的函式庫。 --- ### Thread (#include<pthread.h>) * 這篇解得很清楚 [POSIX Thread 介紹](https://hackmd.io/kUAJn1p3SLKpWD9JsY8fMQ?fbclid=IwAR27dhEv-L5nZZv_xm6-4pA02-2CCG_6SzwAXUZk8kYV1b4sACle7HcM4So) ![](https://i.imgur.com/ncUcdV0.jpg) ![](https://i.imgur.com/ZzRa1Tj.jpg) --- **thread最重要的觀念是共享process內的資源(變數、記憶體等)。** 在多執行緒(Multithreading),如果多個thread同時讀取同一個全域變數,會產生同步(Synchronization)問題。若thread互相搶資源,會造成死結(deadlock)。 [OS - Ch6 同步問題 Synchronization](https://mropengate.blogspot.com/2015/01/operating-system-ch6-synchronization.html) [OS - Ch7 死結 Deadlock](https://mropengate.blogspot.com/2015/01/operating-system-ch7-deadlock.html) [Race Condition](https://zh.wikipedia.org/wiki/%E7%AB%B6%E7%88%AD%E5%8D%B1%E5%AE%B3) ## 作業系統名詞比較 * explain process and thread? * process(程序):已經load到記憶體裡的program,就是已經在運行的program。 * thread(執行續):process的單位,一個process可以有很多thread並共享資源。 --- * explain deadlock? * 一組process互相等待對方的資源,造成process無法繼續執行,使CPU使用率大幅下降。 --- * explain mutex amd semaphore? * mutex只能由上鎖的thread去解鎖,則semaphore沒有這種限制。 * mutex(互斥鎖):只能讓"一個"thread進入[critical section(臨界區段)](https://zh.wikipedia.org/wiki/%E8%87%A8%E7%95%8C%E5%8D%80%E6%AE%B5),可避免[priority inversion(優先權反轉)](https://zh.wikipedia.org/wiki/%E4%BC%98%E5%85%88%E8%BD%AC%E7%BD%AE)。 * semaphore(旗標):可以讓"多個"thread進入critical section,但會發生priority inversion。 --- * compare Stack amd Queue * Stack(堆疊):Frist in last out(FILO)。 * Queue(佇列):Frist in frist out(FIFO)。 --- * explain interrupt step * 暫停目前的process執行,並[context switch](https://tfing.blogspot.com/2019/10/context-switch.html)保存資料 -> 根據interrupt ID查詢interrupt vector,取出對應的[ISR](https://terms.naer.edu.tw/detail/1280775/)地址 -> 到ISR的inital assredd執行該ISR -> ISR complete -> OS 恢復原先中斷的process執行。 --- * explain DMA(Direct Memory Access) * DMA是一種傳輸方式,繞過CPU到IO device與Memory,CPU有更多時間可以執行process。 --- * explain kernal space amd user space * kernal space可以控制CPU管理的硬體,比user space更高的權限,user space受到限制,只能做簡單的事情,如果要調用系統資源(I/O作業),則需要通過system call到kernal space。 --- * explain Blocking/Non Blocking Synchronous/Asynchronous * Blocking(阻塞):啟動I/O後,APP只能等待,不能繼續往下執行。 * Non Blocking(非阻塞):啟動I/O後,APP可以繼續執行與該次I/O無關的其他指令。 * Synchronous(同步):APP啟動I/O後,此程序會在OS的核心完成I/O後才返回APP。 * Asynchronous(非同步):APP啟動I/O後,OS核心不等待I/O完成,直接返回APP。 [談談對不同I/O模型的理解 (阻塞/非阻塞IO,同步/非同步IO)](https://iter01.com/553431.html) --- * explain IPC (linux) * IPC(行程間通訊):至少兩個thread或process之間的通訊方式。 * 信號(signal) 管道(pipe) FIFO 信號量(semphore) Socket ...等等。 [Linux 常見的六大 IPC 通訊方式](https://www.gushiciku.cn/pl/2HVJ/zh-tw) --- * explain Concurrent(併發) Parallel(並行) * Concurrent(併發):把task在不同時間交給CPU處理,同一時間task"不同時"進行。 * Parallel(並行):把每一個task交給每個CPU獨力完成,同一時間task"同時"進行。 --- * explain Race condition and critical sections * Race condition:系統或process的輸出或出現時機不受控制。 * critial sections:程式的某區段在同一時間,只能有"一個"thread執行,否則會錯誤。 --- ## 硬體知識補充 ### 晶片間通訊協議 常用的通訊有3種,SPI、UART、I2C (**唸"I-squared-C",不是"I-two-C"**),詳細資訊點擊超連結。 [Serial Peripheral Interface Bus,SPI](https://zh.wikipedia.org/wiki/%E5%BA%8F%E5%88%97%E5%91%A8%E9%82%8A%E4%BB%8B%E9%9D%A2) : 是一種用於晶片通信的**同步**串行通信介面規範,主要應用於單晶片系統中。 ![](https://i.imgur.com/FMwu2nt.png) ![](https://i.imgur.com/9VIZUDE.png) * SCLK(Serial Clock):串列時脈,由主機發出 * MOSI(Master Output, Slave Input):主機輸出從機輸入訊號(資料由主機發出) * MISO(Master Input, Slave Output):主機輸入從機輸出訊號(資料由從機發出) * SS(Slave Select):片選訊號,由主機發出,一般是低電位有效 **優點** * SPI協定預設是全雙工通信。 * 與漏極開路輸出相反,SPI的推輓輸出可提供良好的訊號完整性和高速度 * 比I²C或SMBus更高的吞吐量 。 不限於任何最大時鐘速度,可實現高速執行 * 完整的傳輸位協定靈活性 * 不限於8位元字 * 任意選擇訊息大小,內容和目的地 * 非常簡單的硬體介面 * 由於電路較少(包括上拉電阻),因此通常比I²C或SMBus的功耗要低, * 沒有仲裁或相關的失敗模式 * 從站直接使用主時鐘,不需要精密振盪器 * 從站不需要唯一的位址 - 不像I²C或GPIB或SCSI * 不需要收發器 * IC封裝只使用四個引腳,而電路板布局或連接器則少於並列埠 * 每個器件至多有一個獨特的匯流排訊號(晶片選擇);其他訊號均可以共享 * 訊號是單向的,允許簡單的電氣隔離 * 簡單的軟體實現 **缺點** * 即使是三線式SPI,也需要比I²C更多的IC封裝引腳 * 沒有帶內定址; 共享匯流排上需要帶外片選訊號 * 從機不支援流控制 (但主機可以延遲下一個時鐘邊沿以降低傳輸速率) * 不支援動態添加節點(熱插拔)。 * 沒有從機檢測機制,主機無法檢測是否與從機斷開。 * 通常只支援一個主裝置(取決於裝置的硬體實現) * 沒有錯誤檢測機制 * 無法進行資料校驗,不定義額外的協定時無法保證一致性。 * 與RS-232 , RS-485或CAN匯流排相比,它只能處理短距離內的資料傳輸。(距離可以通過使用收發器如RS-422進行擴充) * 有許多現有的變體,使得很難找到支援這些變體的主機配接器等開發工具。 * 一些變體,如雙路SPI , 四路SPI和三線SPI是半雙工的。 * 必須通過帶外訊號來實現中斷,或者通過使用類似於USB 1.1和2.0的定期輪詢來類比中斷 (來源:維基百科) --- [UART](https://zh.wikipedia.org/wiki/UART]https://makerpro.cc/2016/04/understand-what-is-uart/) : ![](https://i.imgur.com/0ZjdbqM.jpg) --- [Inter-Integrated Circuit,I2C](http://wiki.csie.ncku.edu.tw/embedded/I2C) : 是一種串列通訊匯流排,使用多主從架構。 ![](https://i.imgur.com/QGvgL7m.png) * 傳輸資料的串列資料線(SDA) * 啟動或停止傳輸以及傳送時鐘序列的串列時脈線(SCL) (來源:維基百科) --- ### [邏輯閘](https://zh.wikipedia.org/wiki/%E9%82%8F%E8%BC%AF%E9%96%98) ![](https://i.imgur.com/knjP0qH.jpg) ![](https://i.imgur.com/cjKP5eV.jpg) ![](https://i.imgur.com/zIx8ujw.jpg) ![](https://i.imgur.com/ZkOBmjO.jpg) ![](https://i.imgur.com/92jlYHP.jpg) ## 軟韌/嵌入式工程師面試題型 大部分來源自:[嵌入式筆試面試題目系列(彙總)](https://tw511.com/a/01/26758.html#1_2)、 ### 程序與執行緒 * 多程序、多執行緒的優缺點 1. 一個程序死了不影響其他程序,一個執行緒崩潰很可能影響到它本身所處的整個程序。 2. 建立多程序的系統花銷大於建立多執行緒。 3. 多程序通訊因為需要跨越程序邊界,不適合大量資料的傳送,適合小資料或者密集資料的傳送。多執行緒無需跨越程序邊界,適合各執行緒間大量資料的傳送。並且多執行緒可以共用同一程序裡的共用記憶體和變數。 --- * 什麼時候用程序,什麼時候用執行緒 1. 建立和銷燬較頻繁使用執行緒,因為建立程序花銷大。 2. 需要大量資料傳送使用執行緒,因為多執行緒切換速度快,不需要跨越程序邊界。 3. 安全穩定選程序;快速頻繁選執行緒。 --- * Linux 多程序、多執行緒同步(通訊)的方法 1. 程序間通訊(IPC) * 有名管道/無名管道 * 訊號 * 共用記憶體 * 訊息佇列 * 號誌 * socket 2. 執行緒間通訊 * 號誌 * 讀寫鎖 * 條件變數 * 互斥鎖 * 自旋鎖 --- ### C * static的用法 1. 用static修飾區域性變數:使其變為靜態儲存方式(靜態資料區),那麼這個區域性變數在函數執行完成之後不會被釋放,而是繼續保留在記憶體中。 2. 用static修飾全域性變數:使其只在本檔案內部有效,而其他檔案不可連線或參照該變數。 3. 用static修飾函數:對函數的連線方式產生影響,使得函數只在本檔案內部有效,對其他檔案是不可見的(這一點在大工程中很重要很重要,避免很多麻煩,很常見)。這樣的函數又叫作靜態函數。使用靜態函數的好處是,不用擔心與其他檔案的同名函數產生干擾,另外也是對函數本身的一種保護機制。 --- * const的用法 1. 用const修飾常數:定義時就初始化,以後不能更改。 2. 用const修飾形參:func(const int a){};該形參在函數裡不能改變。 3. 用const修飾類成員函數:該函數對成員變數只能進行唯讀操作,就是const類成員函數是不能修改成員變數的數值的。 --- * volatile原理與作用 **最佳化**有時一個變數的值改變時,compiler不會直接寫進記憶體中,而是把值放入CPU暫存器,處理結束才放入記憶體,而有時這個變數是其他執行續的依據隨時會改變,而讀取時沒寫進記憶體中,會導致錯誤的結果。 在變數前面使用 volatile可以提醒compiler遇到此變數時不做優化,直接讀取"原始的記憶體位置"。 1. 中斷服務程式中修改的供其它程式檢測的變數。 2. 多工環境下各任務間共享的標誌。 3. 並行裝置的硬體暫存器(如:狀態暫存器)。 --- --- ### 作業系統 * 死鎖的原因、條件 產生死鎖的原因主要是: (1) 因為系統資源不足。 (2) 程序執行推進的順序不合適。 (3) 資源分配不當等。 如果系統資源充足,程序的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。其次,程序執行推進順序與速度不同,也可能產生死鎖。 這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。 1. 互斥條件:一個資源每次只能被一個程序使用。 2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。 3. 不剝奪條件:程序已獲得的資源,在末使用完之前,不能強行剝奪。 4. 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係。 --- * Semaphore / Mutex /Spinlock (都以Linux角度) * Semaphore: 處理資源分配,一到**多個**process/thread要進去Critical section,semaphore本身是一個計數器,紀錄有多少個process剩餘或等待,使用資源-1(sem_wait) 釋放資源+1(sem_post),當counter小於0,要讀取共享資源的process/thread會被block,直到counter為正,另外Semaphore可以由其他process進行釋放。 * Mutex: "主要"使用在multithread中,因為thread共享資源,該資源需要被保護,有了mutex就可以保護critical section,thread拿到mutex才能讀取此global變數並lock保護住,其他thread不能讀取需要等待有mutex的thread結束並釋放lock,把mutex交給下一個排隊的thread,但會有priority inversion的問題要解決。 * Spinlock:一直讀取指定的lock,當lock不能讀取時會polling不斷嘗試,使用spinlock不會產生context switch,process不會休眠,不法執行的process會把cpu分擔出去。