---
# System prepended metadata

title: C語言與作業系統觀念(持續更新中)
tags: [linux, c]

---

# 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分擔出去。

