Linux 核心修飾字隨筆
===
###### tags: `volatile` `asmlinkage` `static` `inline` `__visible` `extern` `__noreturn`
## `volatile`
`volatile` 用於告訴編譯器每一次存取該變數都需要從記憶體讀取,而非暫存器,也就是說在使用前一定會有 load memory (`mov` in x86, `lw` in RISC-V) 的指令發生,考慮以下程式碼:
```c
#include <stdio.h>
int main(void)
{
const int local = 10;
int *ptr = (int *)&local;
printf("%d \n", local);
*ptr = 100;
printf("%d \n", local);
return 0;
}
```
當我們使用 `gcc -O0` 編譯時,輸出結果是
```
10
100
```
此時沒有開啟編譯器最佳化,故每次存取都是從記憶體中取得而非暫存器,當使用 `gcc -O3` 時,輸出會是
```
10
10
```
雖然值被改變了,但由於其是 `const`,編譯器認為該值不會被變動,故不再從記憶體中取得,直接由暫存器取得,再考慮其 asm code:
#### `gcc -O0`
```c=11
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movl $10, -20(%rbp)
leaq -20(%rbp), %rax
movq %rax, -16(%rbp)
movl -20(%rbp), %eax
movl %eax, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movq -16(%rbp), %rax
movl $100, (%rax)
movl -20(%rbp), %eax
movl %eax, %esi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
movq -8(%rbp), %rdx
xorq %fs:40, %rdx
je .L3
call __stack_chk_fail@PLT
```
#### `gcc -O3`
```c
main:
.LFB23:
.cfi_startproc
endbr64
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $10, %edx
movl $1, %edi
xorl %eax, %eax
leaq .LC0(%rip), %rsi
call __printf_chk@PLT
movl $10, %edx
leaq .LC1(%rip), %rsi
xorl %eax, %eax
movl $1, %edi
call __printf_chk@PLT
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
```
可以見到在沒有開啟編譯器最佳化時,在輸出之前:
```c=33
movl $100, (%rax)
movl -20(%rbp), %eax
movl %eax, %esi
```
第 34 行從記憶體再讀取了一次,但開啟編譯器最佳化後僅有 `movl $10, %edx`。
我們使用 `volatile` 稍微改寫該 C 語言:
```c
#include <stdio.h>
int main(void)
{
volatile const int local = 10;
int *ptr = (int *)&local;
printf("Initial value of local : %d \n", local);
*ptr = 100;
printf("Modified value of local: %d \n", local);
return 0;
}
```
依然開啟 `-O3`,其 asm code
```c
main:
.LFB23:
.cfi_startproc
endbr64
subq $24, %rsp
.cfi_def_cfa_offset 32
leaq .LC0(%rip), %rsi
movl $1, %edi
movq %fs:40, %rax
movq %rax, 8(%rsp)
xorl %eax, %eax
movl $10, 4(%rsp)
movl 4(%rsp), %edx
call __printf_chk@PLT
movl $100, 4(%rsp)
movl 4(%rsp), %edx
xorl %eax, %eax
leaq .LC1(%rip), %rsi
movl $1, %edi
call __printf_chk@PLT
movq 8(%rsp), %rax
xorq %fs:40, %rax
jne .L5
xorl %eax, %eax
addq $24, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 8
ret
```
可以見到再輸出之前皆有 `movl 4(%rsp), %edx` 。
---
#### leaq
一個有趣的地方是,在沒有開啟編譯器最佳化時,type casting 是
```c=25
leaq -20(%rbp), %rax
movq %rax, -16(%rbp)
```
此處的 `leaq` 和 `movq` 都是 64 位元指令,因其操作的 `&local` 是 64 位元之地址。
## `asmlinkage`
## `__visible`
即 `__attribute__((__externally_visible__))`,用於告知編譯器不要移除該函式或變數,即便當它們沒有被顯式 (explicitly) 呼叫或使用;因為事實上它們可能被隱式 (implicitly) 呼叫或使用,舉例來說:
```c
// defined at kernel/sched/core.c
asmlinkage __visible void schedule_tail(struct task_struct *prev) __releases(rq->lock) {...}
// declare at include/linux/sched/task.h
extern asmlinkage void schedule_tail(struct task_struct *prev);
// used at arch/um/kernel/process.c
#include <task.h>
...
void new_thread_handler(void)
{
...
if (current->thread.prev_sched != NULL)
schedule_tail(current->thread.prev_sched);
...
}
```
## `extern`
>Code Organization: By declaring functions and variables in header files with extern, and defining them in corresponding source files, you can keep your code organized and maintainable. Other source files that need to use these functions or variables can simply include the appropriate header files.
> Avoiding Multiple Definitions: In large projects with multiple source files, extern can be used to prevent multiple definitions of the same function or variable. If a function or variable is defined in a header file that's included in multiple source files, it will lead to multiple definition errors. By using extern, you can ensure that the function or variable is only defined once in a source file, while still allowing it to be used in other files that include the header.
> Sharing Variables Between Files: extern can be used to share global variables between different source files. You can define a global variable in one source file, and then use extern in other source files to use that same variable.
## Reference
[Day-27 C 語言, 變數範圍, volatile, inline](https://ithelp.ithome.com.tw/articles/10308388)