linux軟中段和系統呼叫深入研究
arm軟中斷模式
arm7種模式
有中斷模式,但是並沒有軟中斷模式。那麼arm的軟中斷是什麼呢?
arm的軟中斷是arm從使用者模式切換到特權模式,也就是linux中從使用者態切換到核心態的過程。
swi命令觸發軟中斷
linux系統中,swi異常向量程式碼:
linux系統呼叫
x86 架構是硬中斷int 80,中斷號為80來實現系統呼叫的;
arm架構是使用swi命令,使arm切換為軟中斷模式,執行swi異常向量表中的異常向量。
軟中斷的異常向量
arm中異常象量表:
異常型別 | 偏移地址(低) | 偏移地址(高) |
---|---|---|
復 位 | 0x00000000 | 0xffff0000 |
未定義指令 | 0x00000004 | 0xffff0004 |
軟中斷 | 0x00000008 | 0xffff0008 |
預取指令終 | 0x0000000c | 0xffff000c |
資料終止 | 0x00000010 | 0xffff0010 |
保留 | 0x00000014 | 0xffff0014 |
中斷請求(IRQ) | 0x00000018 | 0xffff0018 |
快速中斷請求(FIQ) | 0x0000001c | 0xffff001c |
軟中斷中斷向量在arn異常向量表中偏移為0x08的地址。
來看linux程式碼的處理函式
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000 /*軟中斷異常的handle*/
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
軟中斷異常的handle,是異常向量表後0x1000的位置,即異常向量後剛好一個page(4K),我們知道linux記憶體分佈,在。
vector段是向量表段,vector在編譯連結時,連結在程式碼段之後,在linux核心初始化時後將vector段複製到核心空間的第一頁,之後機器異常入口就設定為0xffff0000(PAGE_OFFSET)。所以存在兩個vector段。程式碼段是從0xffff1000開始的。
記憶體管理中的零頁不在實體記憶體的第一塊(存在mmu的系統)
關於記憶體管理子系統中零頁的地址問題:
ZERO_PAGE,零頁。linux中,在分配記憶體時遵從一個原則,寫時分配。COW,寫時複製,在程式建立時有講到。linux分配一塊記憶體,一開始之後對映到零頁上,只有當往這塊記憶體寫內容時才會真正分配記憶體。
而這個零頁的位置,在沒有mmu的系統中才位於實體記憶體開始處,而在有mmu的系統中是當記憶體管理子系統初始化時會分配一個頁作為零頁。所以零頁不在實體記憶體開始的第一頁。
/*pgtable-nommu.h*/
#define ZERO_PAGE(vaddr) (virt_to_page(0))
/*pgtable.h*/
extern struct page *empty_zero_page;
#define ZERO_PAGE(vaddr) (empty_zero_page)
empty_zero_page即為ZERO_PAGE,在pagin_init中初始化
void __init paging_init(const struct machine_desc *mdesc)
{
...
zero_page = early_alloc(PAGE_SIZE);
bootmem_init();
empty_zero_page = virt_to_page(zero_page);
...
}
在說會核心空間記憶體分佈:
linux連結指令碼vmlinux.lds.s中有各記憶體區的說明
SECTIONS
{
. = PAGE_OFFSET + TEXT_OFFSET; /*PAGE_OFFSET 是核心空間的起始地址,TEXT_OFFSET預留一個頁的空間給向量表*/
/*刪掉一些空置的斷*/
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */
ARM_TEXT
}
_etext = .; /* End of text section */
RO_DATA(PAGE_SIZE) /*只讀段*/
. = ALIGN(4);
__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {
__start___ex_table = .;
ARM_MMU_KEEP(*(__ex_table))
__stop___ex_table = .;
}
#ifdef CONFIG_ARM_UNWIND
ARM_UNWIND_SECTIONS
#endif
NOTES
#ifdef CONFIG_STRICT_KERNEL_RWX
. = ALIGN(1<<SECTION_SHIFT);
#else
. = ALIGN(PAGE_SIZE);
#endif
__init_begin = .; /*init段開始標誌*/
ARM_VECTORS /*異常向量表*/
INIT_TEXT_SECTION(8)
.exit.text : {
ARM_EXIT_KEEP(EXIT_TEXT)
}
.init.proc.info : {
ARM_CPU_DISCARD(PROC_INFO)
}
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}
.......
vmlinux.lds.h中
#define ARM_VECTORS \
__vectors_start = .; \
.vectors 0xffff0000 : AT(__vectors_start) { \
*(.vectors) \ /*vertort斷*/
} \
. = __vectors_start + SIZEOF(.vectors); \
__vectors_end = .; \
\
__stubs_start = .; \
.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { \
*(.stubs) \ /*stubs段,vector_swi函式*/
} \
. = __stubs_start + SIZEOF(.stubs); \
__stubs_end = .; \
\
PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));
vector段後0x1000的為.stubs段開始。來看看stubs段放了什麼東西。
繼續回到entry-armv.S
.section .stubs, "ax", %progbits /*stubs段宣告*/
@ This must be the first word
.word vector_swi /*存放vector_swi的地址*/
找到了軟中斷的入口,vector_swi函式。
軟中斷的處理入口vector_swi
進入vector_swi函式:
/*=============================================================================
* SWI handler
*-----------------------------------------------------------------------------
*/
.align 5
ENTRY(vector_swi)
/*現場保護*/
.....
/*
* Get the system call number.
*/
/*OABI 和EABI獲取系統呼叫號會有區別,EABI,系統呼叫號在R7暫存器中,OABI則儲存在R10中*/
/* Pure EABI user space always put syscall number into scno (r7).
*/
/* saved_psr and saved_pc are now dead */
uaccess_disable tbl
/*獲取系統呼叫表*/
adr tbl, sys_call_table @ load syscall table pointer
....
/*獲取當前執行的程式*/
get_thread_info tsk
/*
* Reload the registers that may have been corrupted on entry to
* the syscall assembly (by tracing or context tracking.)
*/
TRACE( ldmia sp, {r0 - r3} )
local_restart:
/*檢查程式flag,是否開啟系統呼叫追蹤,儲存到r10中*/
ldr r10, [tsk, #TI_FLAGS] @ check for syscall tracing
stmdb sp!, {r4, r5} @ push fifth and sixth args
tst r10, #_TIF_SYSCALL_WORK @ are we tracing syscalls?
bne __sys_trace
/*執行系統呼叫的handle,彙編巨集,在entry-header.S中*/
/*tbl: 系統呼叫表
* scno:系統呼叫號
* r10:是否進行系統呼叫追蹤標誌
* __ret_fast_syscall:系統呼叫返回使用者態,恢復現場,檢查搶佔,排程等
*/
invoke_syscall tbl, scno, r10, __ret_fast_syscall
...
/*快速系統呼叫的出口*/
b ret_fast_syscall
#endif
ENDPROC(vector_swi)
entry-header.S中給暫存器起了別名
scno .req r7 @ syscall number
tbl .req r8 @ syscall table pointer
why .req r8 @ Linux syscall (!= 0)
tsk .req r9 @ current thread_info
關於系統呼叫號的獲取,OABI和EABI區別有所不同,見
https://www.cnblogs.com/DF11G/p/10172520.html
OABI方式系統呼叫
SWI{cond} immed_24
immed_24: 24位立即數,指定了系統呼叫號,引數用通用暫存器傳遞
MOV R0,#34
SWI 12
EABI方式系統呼叫
MOV R7,#34
SWI 0X0
系統呼叫號由R7暫存器決定。
系統呼叫號和入口
sys_call_table的定義
entry-common.S中
#define COMPAT(nr, native, compat) syscall nr, native
#ifdef CONFIG_AEABI
#include <calls-eabi.S>
#else
#include <calls-oabi.S>
#endif
#undef COMPAT
syscall_table_end sys_call_table
以eabi為例
NATIVE(0, sys_restart_syscall)
NATIVE(1, sys_exit)
NATIVE(2, sys_fork)
NATIVE(3, sys_read)
NATIVE(4, sys_write)
NATIVE(5, sys_open)
NATIVE(6, sys_close)
NATIVE(8, sys_creat)
NATIVE(9, sys_link)
NATIVE(10, sys_unlink)
NATIVE(11, sys_execve)
NATIVE(12, sys_chdir)
NATIVE(14, sys_mknod)
NATIVE(15, sys_chmod)
NATIVE(16, sys_lchown16)
NATIVE(19, sys_lseek)
NATIVE(20, sys_getpid)
系統呼叫函式定義
include/linux/syscalls.h中定義
SYSCALL_DEFINE* 巨集
系統呼叫影響效能影響在哪?
系統呼叫是通過軟中斷陷入核心的,然後執行註冊的軟中斷處理函式。
其過程中存在如下消耗:
- arm模式切換,陷入核心,需要儲存上下文,所以頻繁的系統呼叫,會放大這一開銷,降低程式碼執行效率。
- 系統呼叫退出時可能產生系統排程,可能會很容易發生排程,得不到足夠的執行時間。
最主要的消耗還是儲存/恢復現場的消耗。
linux陷入核心的幾種方式
在linux核心(SVC模式)中只有硬體中斷和軟中斷
硬體中斷陷入核心的arm處理器模式的切換過程
usr -> 硬體中斷(irq模式) -> svc模式; 短暫的盡力irq模式,主要的處理還是在svc模式下。
軟中斷的切換過程,就是系統呼叫:
usr->系統呼叫(SVC)
linux軟中斷
此軟中斷非彼軟中斷!!!
linux核心的軟中斷是純軟體的實現,和arm的軟中斷區分開來。和系統呼叫的軟中斷不是同一個軟中斷。
此軟中斷和tasklet類比,是一個一個的任務,是由定時執行的,由tick_timer或者其他方式喚醒執行的,是由核心執行緒[ksoftirqd%d]核心執行緒執行的。
原理
linux軟體中斷,借用硬體中斷思想,一箇中斷號對應一個handle的思路。
kernel/softirq.c中
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
每個cpu都有自己的ksoftirqd%d核心執行緒執行這些,軟中斷。
softirq和smp,可以同時在smp的多個cpu上用執行同樣的softirq處理函式。也就是可以並行處理,所以,irq執行緒和處理函式只能訪問perCPU的變數,否則存在同步的問題。
程式碼走讀
open_softirq註冊軟中斷處理函式
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
raise_softirq觸發softirq
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
if (!in_interrupt())
wakeup_softirqd();
}
軟中斷處理執行緒ksoftirqd%d
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};
核心執行緒函式run_ksoftirqd
呼叫__do_softirq函式實際處理。
static void run_ksoftirqd(unsigned int cpu)
{
local_irq_disable();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq();
local_irq_enable();
cond_resched();
return;
}
local_irq_enable();
}
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
/*軟中斷處理時長在2ms一下,如果處理完軟中斷有發生則繼續執行*/
unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
bool in_hardirq;
__u32 pending;
int softirq_bit;
/*
* Mask out PF_MEMALLOC s current task context is borrowed for the
* softirq. A softirq handled such as network RX might set PF_MEMALLOC
* again if the socket is related to swap
*/
current->flags &= ~PF_MEMALLOC;
pending = local_softirq_pending();
account_irq_enter_time(current);
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
in_hardirq = lockdep_softirq_start();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec; /*軟中斷處理函式陣列*/
while ((softirq_bit = ffs(pending))) {
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1;
vec_nr = h - softirq_vec; /*軟中斷號*/
prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h); /*執行軟中斷處理函式*/
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
vec_nr, softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count_set(prev_count);
}
h++;
pending >>= softirq_bit;
}
rcu_bh_qs();
local_irq_disable();
pending = local_softirq_pending();
/*如果又來軟中斷,時間還有則繼續處理,如果不需要排程的話*/
if (pending) {
if (time_before(jiffies, end) && !need_resched() &&
--max_restart)
goto restart;
wakeup_softirqd();
}
lockdep_softirq_end(in_hardirq);
account_irq_exit_time(current);
__local_bh_enable(SOFTIRQ_OFFSET);
WARN_ON_ONCE(in_interrupt());
current_restore_flags(old_flags, PF_MEMALLOC);
}
核心執行緒的執行時機和優先順序
喚醒:
- raise_softirq
- irq_exit()
- 如果執行一輪軟中斷後,時間超過2ms但是又有軟中斷來,主動調出後等待下次排程執行;如果軟中斷過多會導致處理不過來。
優先順序?
瀏覽程式碼,並沒有設定該核心執行緒的優先順序的地方,即使用的預設優先順序,即nice值0,和我們應用執行緒沒有什麼區別。
軟中斷的使用場景
- 定時器時間到處理handle;
- 網路收發包
- RCU
inlucde/linux/interrupt.h中定義
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
numbering. Sigh! */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
中斷處理下半段的軟中斷和系統呼叫的軟中斷的區別
上文已經闡述。
結論
作者注
2020/12/27 凌晨4.01
知其然知其所以然,愛linux,愛生活,碎覺。
相關文章
- Linux系統呼叫原理Linux
- linux系統呼叫getoptLinux
- Linux系統呼叫列表Linux
- 使用 Ptrace 去攔截和模擬 Linux 系統呼叫Linux
- 如何利用Ptrace攔截和模擬Linux系統呼叫Linux
- Linux系統呼叫過程分析Linux
- Linux系統呼叫講義(轉)Linux
- 對JavaScript呼叫堆疊和setTimeout用法的深入研究JavaScript
- 函式庫呼叫和系統呼叫的區別函式
- Linux作業系統分析 | 深入理解系統呼叫Linux作業系統
- Linux系統呼叫機制淺析Linux
- linux系統程式設計之檔案與IO(二):系統呼叫read和writeLinux程式設計
- jfinal系統啟動時呼叫的方法和系統停止時呼叫的方法
- 線上環境 Linux 系統呼叫追蹤Linux
- Linux 下系統呼叫的三種方法Linux
- linux系統呼叫第一篇Linux
- 作業系統實驗2 程式控制和系統呼叫作業系統
- 如何區分CRM系統和呼叫中心?
- 在 Linux 上用 strace 來理解系統呼叫Linux
- 為Linux-3.10.1核心新增系統呼叫Linux
- Linux核心模組程式設計--系統呼叫(轉)Linux程式設計
- 在Linux中新增新的系統呼叫(轉)Linux
- Linux系統程式設計(七)檔案許可權系統呼叫Linux程式設計
- dup()系統呼叫
- Windows 系統呼叫Windows
- linux之系統命令command和系統呼叫system calls及函式function之間的關係Linux函式Function
- Android呼叫系統相簿和相機拍照Android
- 探索作業系統:核心、啟動和系統呼叫的奧秘作業系統
- 簡述linux系統中軟體包管理系統Linux
- Linux系統呼叫詳解(實現機制分析)Linux
- C程式函式呼叫&系統呼叫C程式函式
- linux系統常用的中介軟體Linux
- 系統呼叫篇——SSDT
- Lec 04 系統呼叫
- 學習筆記 作業系統Linux-Ubuntu 之初次新增系統呼叫筆記作業系統LinuxUbuntu
- x64架構下Linux系統函式呼叫架構Linux函式
- 【linux】系統呼叫版串列埠分析&原始碼實戰Linux串列埠原始碼
- Linux核心分析--系統呼叫實現程式碼分析(轉)Linux