Try   HackMD

Linux 核心修飾字隨筆

tags: volatile asmlinkage static inline __visible extern __noreturn

volatile

volatile 用於告訴編譯器每一次存取該變數都需要從記憶體讀取,而非暫存器,也就是說在使用前一定會有 load memory (mov in x86, lw in RISC-V) 的指令發生,考慮以下程式碼:

#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

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

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

可以見到在沒有開啟編譯器最佳化時,在輸出之前:

movl $100, (%rax) movl -20(%rbp), %eax movl %eax, %esi

第 34 行從記憶體再讀取了一次,但開啟編譯器最佳化後僅有 movl $10, %edx
我們使用 volatile 稍微改寫該 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

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 是

leaq -20(%rbp), %rax movq %rax, -16(%rbp)

此處的 leaqmovq 都是 64 位元指令,因其操作的 &local 是 64 位元之地址。

asmlinkage

__visible

__attribute__((__externally_visible__)),用於告知編譯器不要移除該函式或變數,即便當它們沒有被顯式 (explicitly) 呼叫或使用;因為事實上它們可能被隱式 (implicitly) 呼叫或使用,舉例來說:

// 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