linux內核札記

1)內核的主要組成:進程調度,內存管理,虛擬文件系統,網絡接口,進程間通信.
進程調度(SCHED):控制進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程.

內存管理單元(MMU):允許多個進程安全的共享主內存區域.

虛擬文件系統(VFS):隱藏了各種硬件的具體細節,爲所有的設備提供了統一的接口

網絡接口(NET):提供了對各種網絡標準的存取和各種網絡硬件的支持

進程間通信(IPC):支持進程間各種通信機制

1.make xxxxx_defconfig:選取某一具體型號的IC的默認配置到.config文件
2.make menuconfig :啓動圖形化內核配置界面
3.make uImage:生成uboot專用的內核文件

uImage:是在zImage的基礎上,在原本文件的開頭,加上一個長64字節的頭,說明這個內核的版本,加載位置,生成時間,大小等信息得到。

4.make modules:用來生成獨立的模塊:*.ko

模塊操作相關函數:
modprobe:加載內核模塊時,如果有依賴的模塊,同時加載所依賴的模塊.也可以卸載模塊
lsmod:顯示系統中正在運行的模塊,
insmod:加載模塊到內核,不檢測是否有依賴模塊,如果有依賴模塊,則加載不成功
rmmod:刪除一個正在運行的模塊
modinfo: 查看內核模塊包含的模塊信息

模塊參數:加載模塊時,可傳入參數給模塊.
module_param(參數名,參數類型,讀寫權限);
在安裝模塊時這樣使用 : insmod xx.ko 模塊參數 =傳入值
模塊導出符號:導出函數或者變量
EXPORT_SYMBOL(xxxx)
EXPORT_SYMBOL_GPL(XXX)
當某個模塊想要導出自己本身的符號給其他模塊用時,使用上面兩個函數導出符號。
當某個模塊想要使用另一個模塊的導出符號時,使用 extern xxxxx 來聲明下.

5.linux中內存最小管理單位爲頁(page),一個page一般爲4k.初始化時,linux爲每個物理內存頁建立一個page結構,操作物理內存實際上就是操作page頁.

虛擬地址:32位cpu 2^32:0x0-0xFFFF FFFF(4G).

進程空間的劃分:
用戶空間:0G-3G(size:3G range:0x-0xBFFF FFFF)
內核空間:3G-4G(size:1G range:0xc000 0000 -0xFFFF FFFF)
通常用戶進程只能訪問進程空間中的用戶空間,不能訪問內核空間。例外:系統調用時可以訪問到內核空間
用戶空間的內存分配:
malloc /free
valloc/free

內核空間的內存分配:
kmalloc/kfree:分配的內存物理上連續
kzalloc/kfree: 自動初始化內存空間爲0,其他同上
get_zeroed_page/free_page:分配一個頁,同初始化爲0,
__get_free_pages/ free_pages:分配指定頁數的內存
alloc_pages / __free_pages:分配指定頁數的內存
vmalloc/vfree:分配一塊內存,物理上不需要連續
內核分配標誌:
GFP_KERNEL:正常申請內核內存,允許睡眠,
GFP_ATOMIC:不允許睡眠
__GFP_DMA:用於分配DMA內存
__GFP_HIGHMEM:分配的內存可位於高端內存
6.內核定時器

HZ常數:內核定時器中斷髮生的頻率,若HZ=200,代表每 1/200 s發生一次中斷,每次發生中斷的間隔爲5ms,這個時間間隔被稱作tick.

jiffies:被用來記錄自開機以來,經過了多少個tick.

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;
    unsigned long expires;
    struct tvec_base *base;

    void (*function)(unsigned long);
    unsigned long data;

    int slack;

#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

內核定時器的初始化:
1)靜態初始化: TIMER_INITIALIZER(_function,_expires,_data)
_function:定時器超時處理函數
_expires:定時器超時jiffies 時間
_data:傳遞給超時處理函數的參數
2)定義一個定時器:
DEFINE_TIMER(_name,_function,_expires,_data)
_name:待定義內核定時器變量名
3)動態初始化:
init_timer(struct timer_list *timer)
timer:帶初始化的內核定時器
4)添加定時器:
add_timer(struct timer_list * timer)
5)刪除定時器:
void del_timer(struct timer_list * timer)
6)修改定時器:
mod_timer(struct timer_list * timer,unsigned long expires)
該函數分3個步驟:1,del_timer(),timer->expires=expires,add_timer()
7.進程
1)內核線程:
進程描述符,PID,進程正文段,核心堆棧
2)用戶進程:
進程描述符,PID,進程正文段,核心堆棧,用戶空間的數據段和堆棧
3)用戶線程:
進程描述符,PID,進程正文段,核心堆棧,同父進程共享用戶空間的數據段和堆棧

4)進程四要素:
@1.有獨立的用戶空間
@2.進程有進程私有的內核空間堆棧
@3.進程在內核中有一個代表他的task_struct的結構,即通常所說的進程控制塊,內核通過這個數據結構來組織和調度進程.
@4.進程有一段代碼供其執行,這段代碼可以非當前進程私有,可以是與其他進程共享的代碼片段.

5)task_struct結構:
pid_t pid:進程號,最大爲32768
volatile long state:進程狀態
int exit_state:進程退出狀態
struct mm_struct *mm:進程用戶空間描述指針,對於內核線程來說,爲空
unsigned int policy:進程調度策略
int piro:進程優先級
int static_piro:靜態優先級
struct sched_rt_entity rt:進程調度實體
struct task_struct *real_parent:真實的父進程
char comm[TASK_COMMON_LEN]:不包含路徑的執行程序的名稱
6)進程狀態:
TASK_RUNNING:正在被執行,或者已經就緒隨時可執行,正在被創建的進程處於此階段
TASK_INTERRUPTIBLE:處於等待中的進程,當等待條件爲真時被喚醒,也可以被信號或者中斷喚醒
TASK_UNINTERRUPTIBLE:處於等待中的進程,待資源有效時被喚醒,但不可以由其他進程通過信號或者中斷喚醒
TASK_KIALLABLE:可以被致命信號SIGKILL喚醒
TASK_STOPED:進程暫時中止執行,當接收到SIGSTOP和SIGTSTP信號時,進程進入該狀態,接收到SIGCONT信號後,進程重新回到TASK_RUNNING狀態
TASK_TRACED:正處於被調試的進程
TASK_DEAD:進程用do_exit()退出後,進程處於該狀態.
7)進程退出狀態
EXIT_ZOMBIE:僵死狀態,表示進程的執行被終止,父進程還沒有發送wait_pid()系統調用來蒐集有關死亡進程的相關信息
EXIT_DEAD:僵死撤銷狀態,表示進程的最終狀態,父進程已經通過wait_pid()或者wait4()蒐集了信息,因此進程將被系統刪除.
8)進程優先級:
取值範圍:0-139(數字越小,優先級越高)
0-99:實時進程
MAX_RT_PRIO -MAX_PRIO-1:非實時進程
優先級常量:

#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO     MAX_USER_RT_PRIO

#define MAX_PRIO        (MAX_RT_PRIO + 40)
#define DEFAULT_PRIO        (MAX_RT_PRIO + 20)

8)進程靜態優先級:數字越小,優先級越高
作用:決定進程初始時時間片的大小
無論實時進程還是非實時進程都一樣,只不過實時進程的該值不參與優先級運算
9)時間片
rt_time_slice:表示進程運行佔用的時間片
進程缺省時間片與進程靜態優先級有關
內核將100-139優先級的進程映射到200-10ms的時間段上,數值越大,分配的時間片越小
10)current指針:
該指針總是指向當前正在運行的task_struct結構體
當進程被創建,爲進程申請task_struct空間時(大約1k),系統將連同系統的堆棧空間一起分配,分配該空間時以8KB爲單位分配
11)進程調度:
從準備好的進程中選擇一個合適的進程來執行
調度策略:
SCHED_NORMAL:傳統的調度策略,適用於交互式的分時應用
SCHED_FIFO:先來的先執行,適用於對時間性要求比較強,且執行時間比較短的進程,實時應用程序適合該策略
SCHED_RR(round robin):輪流的意思,適合程序比較大,每次運行都要花很長世間的進程
SCHED_BATCH:適用於具有batch風格(批處理),CPU密集型進程,非交互型,該進程不搶佔另外的進程
SHCED_IDLE:適用於優先級比較低的進程,該進程僅在系統空閒時纔會被調用
12)調度時機
主動式:
@1.在內核中,當進程需要等待資源而暫時停止運行時,通過調用schedule()或者schedule_timeout()自願放棄cpu,調度其他進程,調度前應該將進程狀態置爲掛起
@2.在用戶空間,通過系統調用pause(),nanosleep()而自願放棄cpu.
被動式:被搶佔
可以是內核搶佔,也可以是用戶搶佔
@1.內核搶佔:更高優先級的進程/線程可以搶佔當前正在運行時的低優先級的進程/線程
@2.用戶搶佔:當內核即將返回用戶空間的時候,如果 need_resched()測試TIF_NEED_RESCHED標誌被設置,會導致schedule()被調用,此時發生的搶佔成爲用戶搶佔
中斷處理,系統調用,異常處理返回用戶空間都算
13)不允許內核搶佔的特例:
@1.內核正進行中斷處理
@2:內核正在進行中斷上下文的底半部處理.
@3:進程正持有自旋鎖,讀寫鎖等情況.
@4: 內核正在執行調度程序.
14)內核搶佔計數器:
爲保證在以上情況不被搶佔,linux內核使用了一個變量preempt_count,稱爲內核搶佔計數,這個變量被設置在進程的thread_info結構中,每當進程進入到以上幾種狀態時,preempt_count便+1,表示內核不允許搶佔,每當內核出現以上幾種狀態的退出時,preempt_count便-1,表示允許重新調度與搶佔

15)調度步驟:
清理當前運行中的進程
選擇下一個要運行的進程
設置新進程的運行環境
進程上下文切換
系統調用:
由操作系統內核實現,運行在內核態
普通調用:由函數庫或者用戶自己提供,運行在用戶態
8併發與竟態
1)中斷屏蔽:local_irq_disable(), local_irq_enable()都只能禁止/使能本CPU內的中斷,對於SMP(多核),不可行
而且,linux系統的異步IO,任務調度等重要操作都依賴於中斷,所以採用中斷屏蔽來避免竟態並不可取
local_irq_save,不僅可以屏蔽中斷,同時可以保存cpu的中斷位信息。local_irq_restore與它相反
如果只是想禁止中斷的底半部,那麼則local_bh_disable(),locla_bh_enable()。
2)原子操作:指的是在執行過程中不會被別的代碼路徑所中斷的操作.分爲整形原子操作和位原子操作.
a.整形原子的操作
1.atomic_set(atomic_t *v,int i):設置原子變量的值爲i。
2.atomic_t v =ATOMINC_INIT(0):定義原子變量v,並初始化爲 0.
3.atomic_read(atomic_t *v):返回原子變量的值
4.void atomic_add(int i,atomic_t *v):原子變量+i
5.void atomic_sub(int i,atomic_t *v):原子變量-i
6.void atomic_inc(atomic_t *v):原子變量+1
7.void atomic_dec(atomic_t *v):原子變量-1
8.int atomic_inc_and_test(atomic_t *v):自增1,並測試是否爲0,爲0返回true
9.int atomic_dec_and_test(atomic_t *v):自減1,並測試是否爲0,爲0返回true
10.int atomic_sub_and_tets(int i,atomic_t *v):減去i,並測試是否爲0,爲0返回true
11.int atomic_add_and_return(int i,atomic_t *v):原子變量+i,並返回新的值
12.int atomic_sub_and_return(int i,atomic_t *v):原子變量-i,並返回新的值
13.int atomic_inc_return(atomic_t *v):自增1並返回新的值
14.int atomic_dec_return(atomic_t *v):自減1並返回新的值
b.位原子操作
1.void set_bit(nr,void *addr):設置addr地址的nr位,置1
2.void clear_bit(nr,void *addr):清除addr地址的nr位,置0
3.void changebit(nr,void *addr):對addr地址的nr位取反
4.test_bit(nr,void *addr):返回addr的nr位
5.int test_and_set_bit(nr,void *addr):測試並設置nr位
6.int test_and_clear_bit(nr,void *addr):測試並清除nr位
7.int test_and_change_bit(nr,void *addr):測試並對nr位取反

3)自旋鎖
1.spin lock:如果鎖未被佔用,則獲得這個鎖並執行程序。如果鎖被佔用,則原地自旋等待,直到其他程序釋放鎖後。
2.自旋鎖的操作:
a.spinlock_t lock:定義自旋鎖
b.spin_lock_init(lock):初始化自旋鎖
c.spin_lock(lock):獲得鎖,如果被佔用,原地自旋等待
d.spin_trylock(lock):獲得鎖,如果被佔用,返回假。不原地自旋等待
e.spin_unlock(lock):釋放鎖,和spin_lock()/spin_try_lock()配對使用
f.使用範例:

spin_lock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
//do 臨界區資源代碼
spin_unlocked(&lock);

自旋鎖持有期間,內核的搶佔將被禁止。自旋鎖能夠保證臨界區資源不受別的CPU和本CPU內的搶佔進程打擾。
但得到鎖的代碼路徑在執行臨界區的時候還可能受到中斷斷和底半部的影響.則會有自旋鎖的衍生鎖
4)讀寫鎖:是一種比自旋鎖粒度更小的鎖,保留了自旋的概念,在寫操作時最多隻允許一個寫進程。在讀操作方面,允許同時多個進程讀。
讀寫鎖的操作:
a.rwlock_t lock =RW_LOCK_LOCKED//靜態初始化
b.rwlock_t lock; rwlock_init(&lock);//動態初始化
讀鎖定:
c.read_ lock(rwlock_t *lock);
d.read_ lock_irq(rwlock_t *lock);
e.read_ lock_irqsave(rwlock_t *lock,unsigned long flags);
f.read_ lock_bh(rwlock_t *lock);
讀解鎖:
g.read_unlock(rwlock_t *lock);
h.read_unlock_irq(rwlock_t *lock);
i.read_unlock_irqrestore(rwlock_t *lock,unsigned long flags);
j.read_unlock_bh(rwlock_t *lock);
寫鎖定
k.write_lock(rwlock_t *lock);
l.write_lock_irq(rwlock_t *lock);
m.write_lock_irqsave(rwlock_t *lock,unsigned long flags);
n.write_lock_bh(rwlock_t *lock);
o.write_trylock(rwlock_t* lock);
寫解鎖:
p.write_unlock(rwlock_t *lock);
q.write_unlock_irq(rwlock_t *lock);
r.write_unlock_irqrestore(rwlock_t *lock,unsigned long flags);
s.write_unlock_bh(rwlock_t *lock);

5)順序鎖:是一種對讀寫鎖的優化,若使用順序鎖,讀操作絕對不會被寫執行單元阻塞,僅僅寫操作是互斥的.
限制:要求被保護的臨界資源不含指針
操作:
a. seqlock_t lock;
b.seqlock_init(&lock);
c.unsigned read_seqbegin(seqlock_t *lock);//讀順序鎖
d.int read_seqretry(seqlock_t *lock,unsigned start)//讀檢查
e.write_seqlock(seqlock_t *lock);
f.write_tryseqlock(seqlock_t *lock);
g.write_seqlock_irq(seqlock_t *lock);
h.write_seqlock_irqsave(seqlock_t *lock,unsigned long flags);
i.write_seqlock_bh(seqlock_t *lock)

j.write_sequnlock(seqlock_t *lock)
k.write_sequnlock_irq(seqlock_t *lock);
l.write_sequnlock_irqrestore(seqlock_t *lock,unsigned long flags);
m.write_sequnlock_bh(seqlock_t *lock)
6)RCU 讀拷貝更新…..

7)信號量(semaphore):當獲取不到信號量時,進程不會原地打轉自旋,而是進入休眠等待
信號量的操作:
a.struct semaphore sem;
b.sema_init(struct semaphore *sem,int val):初始化信號量,並賦值爲val
c.init_MUTEX(struct semaphore *sem):初始化爲1
d.init_MUTEX_LOCKED(struct semaphore *sem):初始化爲0,初始化後便處於鎖定狀態
e.DECLARE_MUTEX(name):定義一個名爲name的信號量,並初始化爲1
f.void down(struct semaphore *sem):獲取信號量,可能導致睡眠,不能在中斷上下文使用
g.int down_interruptible(struct semaphore *sem):獲取信號量,如果信號量不可用,進程被設爲TASK_INTERRUPTIABLE類型的睡眠狀態,返回0代表信號獲取正常返回,非0代表信號被中斷.
h.int down_killable(struct semaphore *sem):獲取信號量失敗會進入TASK_KILLABLE的睡眠狀態
i.int down_trylock(struct semaphore *sem):嘗試獲取,獲取成功,立即返回。否則返回非0,不會睡眠,可以在中斷上下文使用.
j.void up(struct semaphore *sem):釋放信號量,實際上是給信號量+1.

DECLARE_MUTEX(sem);
down(&sem);
//do 臨界資源
up(&sem);

信號量被初始化爲0,可用於同步,同步意味着一個執行單元繼續執行需等待另一個執行單元完成某事,保證執行的先後順序
8)完成量:
struct completion com;
init_completion(&com);
DECLARE_COMPLETION(com):定義並初始化完成量
void wait_for_completion(struct completion *com);等待完成量
void complete(struct completion *c)
void complete_all(struct completion *c )
前者釋放一個等待的執行單元,後者釋放所有等待同一完成量的執行單元
9)互斥體
10)等待隊列可用於阻塞進程的喚醒
定義和初始化隊列頭:
wait_queue_head_t wqh;
init_waitqueue_head(wait_queue_head_t *wqh);
DECLARE_WAITQUEUE(name.tsk):定義和初始化等待隊列
add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait):將等待隊列添加到等待隊列頭所在的列表中
remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait):將等待隊列從等待隊列頭所在的列表中刪除
等待事件:
wait_event(queue,condition):condition爲真時,立即返回,否則進入TASK_UNINTERRUPTIBLE 的睡眠狀態,並掛在queue指定的等待隊列頭上.
add_wait_queue(queue,condition):條件爲真,立即返回,否則進入TASK_INTERRUPTIBLE的睡眠狀態,並掛在queue指定的等待隊列頭上
wait_event_killable(wq,condition):同上,否則進入TASK_KILLABLE類型的睡眠狀態
wait_event_timeout(wq,condition,timeout):同上,否則進入TASK_UNINTERRUPTIBLE類型的睡眠狀態,並掛在指定wq的等待對獵頭上,當超時後,立即返回
wait_event_interruptible_timeout(wq,condition,timeout):…
喚醒:
wake_up(x)
wake_up_interruptible(x)
在等待隊列中睡眠:
sleep_on(wait_queue_head_t * q)
interruptible_sleep_on(wait_queue_head_t * q)

阻塞/非阻塞:如果設備沒有準備好數據給應用程序讀,或者沒有準備好接收應用程序的寫數據,那麼設備應當阻塞進程,使他進入睡眠,直到請求看可以得到相應.
用戶應用程序可以使用O_NONBLOCK標誌來人爲地設置讀寫方式爲非阻塞

這裏寫圖片描述
select系統調用:用於多路監控,當沒有一個文件滿足要求時,select調用將引起系統阻塞
unsigned int (poll) (struct file , struct poll_table_struct *);第一個參數:文件表指針,第二個參數:輪詢表指針
使用 poll_wait()函數將等待隊列添加到輪詢表中.

/*
 * Do not touch the structure directly, use the access functions
 * poll_does_not_wait() and poll_requested_events() instead.
 */
typedef struct poll_table_struct {
    poll_queue_proc _qproc;
    unsigned long _key;
} poll_table;

poll_wait(struct file * filp,wait_queue_head_t * wait_address,poll_table * p):poll_wait()的調用不會引起阻塞,它僅僅將當前進程添加到wait參數指定的等待隊列的數據鏈中
poll操作的返回值:
POLLIN:設備可無阻塞讀
POLLOUT:設備可無阻塞寫
POLLRDNORM:數據可讀
POLLWRNORM:數據可寫
11)異步通知
阻塞:設備未就緒則阻塞進程
非阻塞:設備未就緒則直接返回狀態
輪詢:由應用程序查詢狀態判斷設備是否就緒
異步:一旦設備就緒,就主動通知應用程序.
異步通知使用信號來實現,除SIGSTOP和SIGKILL兩個信號外,進程能夠忽略或捕獲其他所有信號
實現:
用戶應用程序:
1.完成F_SETOWN cmd:設備設備的擁有者爲本進程。這樣才能接收到設備驅動發出的信號
2.完成F_SETFL cmd:該命令設置文件支持FASYNC模式,幾異步通知模式
3.完成signal()系統調用:該系統調用連接SIGIO信號和對應的信號處理函數
驅動程序實現:
1.支持F_SETOWN 控制命令:在控制命令中需要將filp->f_owner設爲對應進程的ID,這部分由內核完成.
2.支持F_SETFL:每當FASYNC中的標誌位改變時,設備驅動中的fasync()得以執行,所以驅動中需要實現fasync()函數
3.釋放信號:在設備驅動中,當設備資源可以獲得時,設備驅動應該通過調用kill_fasync()函數釋放相應的信號.
異步事件通知隊列:

struct fasync_struct {
    spinlock_t      fa_lock;
    int         magic;
    int         fa_fd;
    struct fasync_struct    *fa_next; /* singly linked list */
    struct file     *fa_file;
    struct rcu_head     fa_rcu;
};
int (*fasync) (int fd, struct file *filp, int on);
//返回:+值表示成功,0表示未變,負值失敗

fasync_helper(int fd,struct file * filp,int on,struct fasync_struct * * fapp):
初始化fasync_struct異步事件通知隊列,包括分配內存和設置屬性
釋放初始化時爲fasync_struct分配的內存.
kill_fasync(struct fasync_struct * * fp,int sig,int band):
在設備資源可以獲得時,應調用該函數釋放SIGIO信號

異步事件的發起者問題:
當進程收到SIGIO信號時,沒辦法區分是那個文件有新數據提供,此時仍需要select()函數來找出是那個文件發生變化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章