# Kcalc
## 問題探討
### Character device register kernel API( register_chrdev vs. register_chrdev_region vs. alloc_chrdev_region )
[引伸自 ` johnnylord `的共筆](https://hackmd.io/@johnnylord/SJyiQ13wV?type=view)
[Character device vs. Block device](http://haifux.org/lectures/86-sil/kernel-modules-drivers/node10.html)
#### register_chrdev_region()
```clike
int register_chrdev_region(dev_t from, unsigned count, const char *name)
```
Arguemets:
* from: the first in the desired range of device numbers; must include the major number.
* count: the number of consecutive device numbers required
* name: the name of the device or driver
* 搭配 ` MKDEV ` 這個 macro 使用。
行為:
* 先使用 major number( 12bits ) 與 minor number( 20bits ) 使用` MKDEV `取得`from` 這個參數。 也就是這個 device driver 獨一無二的識別碼。
* 放入 ` register_chrdev_region ` 中。
* 如果回傳 = 0,即成功。
* 如果成功,則還要使用 cdev_init() 將把 file operations 註冊到 remapping table 裡。
* 最後 cdev_add , 將 driver 到 kernel 裡面。
* 然後就能使用 driver 了!
#### register_chrdev()
```clike
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
```
Arguemets:
* major: major device number or 0 for dynamic allocation
* name: name of this range of devices
* fops: file operations associated with the devices
行為:
* 將 major number, name, file operations 放進這個 function
* return 的數值就是 major number
* 因此只要 = 0 或 < 0 都是失敗的情況。
* 然後,就能直接使用 driver 了!
#### alloc_chrdev_region()
```clike
int alloc_chrdev_region (dev_t *dev, unsigned baseminor, unsigned count, const char *name);
```
Arguemets:
* dev: output parameter for first assigned number
* baseminor: first of the requested range of minor numbers
* count: the number of minor numbers required
* name: the name of the associated device or driver
行為:
* 這邊的 `dev` 這個參數不用先準備好,宣告後不用給值。
* 給定 `baseminor` 讓這個 function ,幫你從它開始找可用的 minor number。 For instance, baseminor = 10, your minor number could be any number >= 10!
* 不論如何,在這邊的 major number 都是系統配置的!
* 回傳數值 = 0,即成功。
* 同樣,後面要做 cdev_init, cdev_add。
#### 比較
| API | Major number | Minor number |
| --- | --- | --- |
| `register_chrdev` | 可自動分配也可開發者指定 | 系統分配 |
| `alloc_chrdev_region` | 系統自動分配 | 開發者自行指定基底和範圍 |
| `register_chrdev_region` | 開發者指定 | 系統分配 |
總歸來說, register_chrdev 承包了大部分會作的事情,也可作自動分配等等功能,不過沒辦法指定 minor number , alloc_chrdev_region 與 register_chrdev_region 則作差不多的事,只分配 major, minor number,剩下的 file operations remapping 跟 kernel 的動作仍要自己額外去作。
不過, register_chrdev_region 可以指定使用哪個 major number!
### Change file mode
``` bash
chmod - change file mode bits
```
* file mode bits: 用來形容 file 權限的 bits 們
``` bash
crw------- 1 root root 10, 240 Jun 22 14:27 userio
```
例如這個例子, 前面的 crw------- 就是從 file mode bits 得到的資訊。
* 4 stands for "read",
* 2 stands for "write",
* 1 stands for "execute", and
* 0 stands for "no permission.1
> 可推廣為 4 + 2 = 6, 代表權限改為可 read + wite , 以此類推。
> [name=Julian Fang]
```
chmod 0666
```
> 第一個 6 代表 user,第二個 6 代表 group ,最後一個代表 others。
> [name=Julian Fang]
### 浮點運算在 Linux kernel 中的特別對待,以及 context switch 的過程中,涉及到 FPU/SIMD context,該注意什麼?
[引伸自 ` rebvivi ` 的共筆](https://hackmd.io/@1az9eaZgQwG38Qx2YiKXDw/BkW1Bn3U4?type=view#1-%E8%A7%A3%E9%87%8B%E6%B5%AE%E9%BB%9E%E9%81%8B%E7%AE%97%E5%9C%A8-Linux-%E6%A0%B8%E5%BF%83%E4%B8%AD%E7%82%BA%E4%BD%95%E9%9C%80%E8%A6%81%E7%89%B9%E5%88%A5%E5%B0%8D%E5%BE%85%EF%BC%8C%E4%BB%A5%E5%8F%8A-context-switch-%E7%9A%84%E9%81%8E%E7%A8%8B%E4%B8%AD%EF%BC%8C%E6%B6%89%E5%8F%8A%E5%88%B0-FPUSIMD-context%EF%BC%8C%E8%A9%B2%E6%B3%A8%E6%84%8F%E4%BB%80%E9%BA%BC%EF%BC%9F%E6%8F%90%E7%A4%BA-%E5%8F%83%E7%85%A7-Lazy-FP-state-restore-%E5%92%8C%E4%B8%8A%E6%96%B9%E5%8F%83%E8%80%83%E8%B3%87%E6%96%99%E3%80%82%E6%87%89%E8%A9%B2%E6%92%B0%E5%AF%AB%E5%B0%8D%E6%87%89%E5%8C%85%E5%90%AB%E6%B5%AE%E9%BB%9E%E9%81%8B%E7%AE%97%E7%9A%84-Linux-%E6%A0%B8%E5%BF%83%E6%A8%A1%E7%B5%84%EF%BC%8C%E5%AF%A6%E9%9A%9B%E7%B7%A8%E8%AD%AF%E5%92%8C%E6%B8%AC%E8%A9%A6)
- 進行浮點數運算的時候,往往會遇到許多問題
* 輸入與儲存的值不一定精確,可能原因:
* 輸入值本身就不精確
* 儲存浮點數用的記憶體有限
* 不同底數之間的轉換不精確
* 輸入與儲存產生 overflow
* 計算的結果會有誤差:
使用浮點數進行運算時會有上述產生的許多誤差,這些誤差經過反覆而且大量運算之後很有可能會蔓延到其它所在,從而產生不可收拾的後果
#### FPU, SIMD
[What is SIMD?](https://www.quora.com/What-is-SIMD)
[Computer Architecture:SIMD/Vector/GPU](https://www.archive.ece.cmu.edu/~ece740/f13/lib/exe/fetch.php?media=seth-740-fall13-module5.1-simd-vector-gpu.pdf)
- `Floating Point Unit(FPU)`:專用於浮點運算的處理器
- `Single Instruction Multiple Data (SIMD)` : 同時對多個數據執行同一條CPU指令。
#### Context switch and Lazy context switch
[reference](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.prd29-genc-009492c/ch05s03s01.html)
- `context switch`: 轉換CPU至另一項行程時必須將舊行程的狀態儲存起來,然後再載入新行程的儲存狀態
- `Lazy context switch`:應用於 FPU 的 context switch ,在沒有指令的時候會先被 disable , 一直等到 FP 指令被執行的時候才 enable FPU ,然後載入資料。
>每次 context switch 的時候如果 CPU 和 FPU 都要做 context switch 會使系統的 overloading 增大,因為存取 FPU 的 hardware context 需要更多的 CPU time
- 在 context switch 的過程中,如果涉及到 FPU context ,會執行`Lazy context switch`,也就是僅在需要時保存並還原 FPU ,但這可能會面臨一些安全性的問題:
* 可以利用 Lazy FP 狀態還原功能,潛在允許一個 process 從另一個 process 推斷數據
* 推斷到的數據也可能包括已加密操作的信息,這個安全漏洞會影響到CPU的預測執行推測執行機制
## 定點數 (fixed-point) vs. 浮點數 (floating-point)
除了浮點數以外的小數表達方法:定點數
### 三個定點數實例
## Kernel 與 userspace 之間的溝通
### Kernel API: copy_to_user()
```
unsigned long copy_to_user(void *to, const void *from, unsigned long n);
```
Arguements:
* to:destination address in user space
* from:source address in kernel space
* n: number of bytes to copy
Return:
* 不能被複制的 bytes,如果完全成功,返回值為 0 。
From [kcalc/calc.c](https://github.com/sysprog21/kcalc/blob/master/calc.c)
``` clike
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
{
int error_count = 0;
if (*msg_ptr == 0) {
return 0;
}
memset(message, 0, sizeof(char) * BUFF_SIZE);
snprintf(message, 64, "%d\n", result);
size_of_message = strlen(message);
error_count = copy_to_user(buffer, message, size_of_message);
if (error_count == 0) {
printk(KERN_INFO "CALC: size: %d result: %d\n", size_of_message,
result);
while (len && *msg_ptr) {
error_count = put_user(*(msg_ptr++), buffer++);
len--;
}
if (error_count == 0) {
return (size_of_message);
} else {
return -EFAULT;
}
} else {
printk(KERN_INFO "CALC: Failed to send %d characters to the user\n",
error_count);
return -EFAULT;
}
}
```
這個例子中,使用 copy_to_user 來實作 read from device 的功能。
可以用 return 的值來了解,是否有發生錯誤,或是傳送成功!
### 資料量增長,是否會有效能的嚴重衝擊呢?
[引伸自 ` rebvivi ` 的共筆](https://hackmd.io/@1az9eaZgQwG38Qx2YiKXDw/BkW1Bn3U4?type=view#4-%E5%9C%A8-calcc-%E6%AA%94%E6%A1%88%E4%B8%AD%EF%BC%8C%E7%94%A8%E5%88%B0-copy_to_user-%E9%80%99%E5%80%8B-kernel-API%EF%BC%8C%E5%85%B6%E4%BD%9C%E7%94%A8%E7%82%BA%E4%BD%95%EF%BC%9F%E6%9C%AC%E4%BE%8B%E4%BD%BF%E7%94%A8%E8%A9%B2-API-%E5%81%9A%E4%BA%86%E4%BB%80%E9%BA%BC%E4%BA%8B%EF%BC%9F%E8%8B%A5%E6%98%AF%E8%B3%87%E6%96%99%E9%87%8F%E5%A2%9E%E9%95%B7%EF%BC%8C%E6%98%AF%E5%90%A6%E6%9C%83%E6%9C%89%E6%95%88%E8%83%BD%E7%9A%84%E5%9A%B4%E9%87%8D%E8%A1%9D%E6%93%8A%E5%91%A2%EF%BC%9F)
* 如果使用copy_to_user時,要複製的資料量太大,就會讓 buffer 產生 overflow,導致效能降低
* 如果 user 讀取到錯誤的資料,可能導致程式崩潰
* 如果後面的記憶體存放著應用程式的資料,則會導致其他功能讀取到錯誤的資料,因此工作不正常,也可能導致程式崩潰。
提出實際案例 [HID: debug: check length before copy_to_user](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=717adfdaf14704fd3ec7fa2c04520c0723247eac)
## MathEX
### `MathEx` 如何解析給定字串,從而分離出變數和對應數學表示法呢?
## 程式與實驗
延續自 afcidk 同學的工作 [Github](https://github.com/afcidk/kcalc),並針對同學的程式碼做強化
shell script 如果是 `NAN_INT` `INF_INT` 不會正確顯示 會錯誤的顯示數字
```
Testing 0/0 ...
.20000000000000000000
Testing 1/0 ...
.20000000000000000000
```
主因是沒有針對 `NAN_INT` `INF_INT` 兩個特殊的定義做檢查造成的
```shell=
test_op() {
local expression=$1
echo "Testing " ${expression} "..."
echo -ne ${expression}'\0' > $CALC_DEV
ret=$(cat $CALC_DEV)
# Transfer the self-defined representation to real number
num=$(($ret >> 4))
frac=$(($ret&15))
neg=$((($frac & 8)>>3))
[[ $neg -eq 1 ]] && frac=$((-((~$frac&15)+1)))
echo "$num*(10^$frac)" | bc -l
}
```
修正後的結果
```shell=
test_op() {
local expression=$1
local NAN_INT=31
local INF_INT=47
echo "Testing " ${expression} "..."
echo -ne ${expression}'\0' > $CALC_DEV
ret=$(cat $CALC_DEV)
# Transfer the self-defined representation to real number
num=$(($ret >> 4))
frac=$(($ret&15))
neg=$((($frac & 8)>>3))
[[ $neg -eq 1 ]] && frac=$((-((~$frac&15)+1)))
if [ "$ret" -eq "$NAN_INT" ]
then
echo "NAN_INT"
elif [ "$ret" -eq "$INF_INT" ]
then
echo "INF_INT"
else
echo "$num*(10^$frac)" | bc -l
fi
}
```
```
Testing 0/0 ...
INF_INT
Testing 1/0 ...
INF_INT
```
這邊處理了一些例外狀況,但是還是發現一些數值上的運算錯誤,如上圖 `0/0` 應該是 `NAN_INT` 而非 `INF_INT`,還有其他數值錯誤像是對於負數的除法結果並不正確,乘法與加法的 overflow 沒有做處理,且也沒有處理 減法的 underflow。
```
// overflow when add
Testing 9999999999999999999999999999999999999999999999+1 ...
0
// wrong when 負數除法
Testing (-100)/5 ...
252850180000000
// wrong when edge case
Testing 0/0 ...
INF_INT
//wrong when mult overflow
Testing 999999999999999*999999999999999999999 ...
-57049087
```
先針對 `0/0` 這個例外的例子做處理
```cpp
int divid(int a, int b)
{
int frac1 = GET_FRAC(a);
int frac2 = GET_FRAC(b);
int n1 = GET_NUM(a);
int n2 = GET_NUM(b);
// 加入判斷條件
if (n1 == 0 && n2 == 0)
return NAN_INT;
if (n2 == 0)
return INF_INT;
while (n1 * 10 < ((1 << 25) - 1)) {
--frac1;
n1 *= 10;
}
int n3 = n1 / n2;
int frac3 = frac1 - frac2;
return FP2INT(n3, frac3);
}
```
處理加法的 overflow
```cpp
int plus(int a, int b)
{
int frac1 = GET_FRAC(a);
int frac2 = GET_FRAC(b);
int n1 = GET_NUM(a);
int n2 = GET_NUM(b);
while (frac1 != frac2) {
if (frac1 > frac2) {
--frac1;
n1 *= 10;
} else if (frac1 < frac2) {
--frac2;
n2 *= 10;
}
}
n1 += n2;
// 新增這行檢查,去看說加完的結果是否發生overflow
if((~n1) >> 31 && (n1 & (15) << 28)) return INF_INT;
return FP2INT(n1, frac1);
}
```
```
Testing 0/0 ...
NAN_INT
Testing 1/0 ...
INF_INT
```
解決負數除法錯誤的情況。
```cpp
int divid(int a, int b)
{
int frac1 = GET_FRAC(a);
int frac2 = GET_FRAC(b);
int n1 = GET_NUM(a);
int n2 = GET_NUM(b);
int sign = 1;
printk("a : %d b : %d",a,b);
printk("n1 : %d n2 : %d frac1 : %d frac2 : %d",n1,n2,frac1,frac2);
if (n1 == 0 && n2 == 0)
return NAN_INT;
if(n1 == NAN_INT || n2 == NAN_INT)
return NAN_INT;
if (n2 == 0)
return INF_INT;
// check if there is negative number
if(n1 < 0){
sign = -sign;
n1 = -n1;
}
if(n2 < 0){
sign = -sign;
n2 = -n2;
}
while (n1 * 10 < ((1 << 25) - 1)) {
--frac1;
n1 *= 10;
}
int n3 = (n1 / n2)*sign;
int frac3 = frac1 - frac2;
return FP2INT(n3, frac3);
}
```
成功解決除法結果錯誤的問題
```
Testing (-100)/(-100) ...
1
Testing (-100)/5 ...
-20
```