kernel相關知識

爲什麼需要內核鎖?
多核處理器下,會存在多個進程處於內核態的情況,而在內核態下,進程是可以訪問所有內核數據的,因此要對共享數據進行保護,即互斥處理
有哪些內核鎖機制?
(1)原子操作
atomic_t數據類型,atomic_inc(atomic_t *v)將v加1
原子操作比普通操作效率要低,因此必要時才使用,且不能與普通操作混合使用
如果是單核處理器,則原子操作與普通操作相同
(2)自旋鎖
spinlock_t數據類型,spin_lock(&lock)和spin_unlock(&lock)是加鎖和解鎖
等待解鎖的進程將反覆檢查鎖是否釋放,而不會進入睡眠狀態(忙等待),所以常用於短期保護某段代碼
同時,持有自旋鎖的進程也不允許睡眠,不然會造成死鎖——因爲睡眠可能造成持有鎖的進程被重新調度,而再次申請自己已持有的鎖
如果是單核處理器,則自旋鎖定義爲空操作,因爲簡單的關閉中斷即可實現互斥
///////////

互斥量與信號量的區別?(轉載但找不到原文出處)
(1)互斥量用於線程的互斥,信號線用於線程的同步
這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別
互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的
同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源
(2)互斥量值只能爲0/1,信號量值可以爲非負整數
也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量爲單值信號量是,也可以完成一個資源的互斥訪問
(3)互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到
/////
mutex和spin lock的區別和應用(sleep-waiting和busy-waiting的區別)
信號量mutex是sleep-waiting。 就是說當沒有獲得mutex時,會有上下文切換,將自己、加到忙等待隊列中,直到另外一個線程釋放mutex並喚醒它,而這時CPU是空閒的,可以調度別的任務處理。
而自旋鎖spin lock是busy-waiting。就是說當沒有可用的鎖時,就一直忙等待並不停的進行鎖請求,直到得到這個鎖爲止。這個過程中cpu始終處於忙狀態,不能做別的任務。

/////////////////////
例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0 和Core1上。 用spin-lock,coer0上的線程就會始終佔用CPU。
另外一個值得注意的細節是spin lock耗費了更多的user time。這就是因爲兩個線程分別運行在兩個核上,大部分時間只有一個線程能拿到鎖,所以另一個線程就一直在它運行的core上進行忙等待,CPU佔用率一直是100%;而mutex則不同,當對鎖的請求失敗後上下文切換就會發生,這樣就能空出一個核來進行別的運算任務了。(其實這種上下文切換對已經拿着鎖的那個線程性能也是有影響的,因爲當該線程釋放該鎖時它需要通知操作系統去喚醒那些被阻塞的線程,這也是額外的開銷)
///
總結
(1)Mutex適合對鎖操作非常頻繁的場景,並且具有更好的適應性。儘管相比spin lock它會花費更多的開銷(主要是上下文切換),但是它能適合實際開發中複雜的應用場景,在保證一定性能的前提下提供更大的靈活度。

(2)spin lock的lock/unlock性能更好(花費更少的cpu指令),但是它只適應用於臨界區運行時間很短的場景。而在實際軟件開發中,除非程序員對自己的程序的鎖操作行爲非常的瞭解,否則使用spin lock不是一個好主意(通常一個多線程程序中對鎖的操作有數以萬次,如果失敗的鎖操作(contended lock requests)過多的話就會浪費很多的時間進行空等待)。

(3)更保險的方法或許是先(保守的)使用 Mutex,然後如果對性能還有進一步的需求,可以嘗試使用spin lock進行調優。畢竟我們的程序不像Linux kernel那樣對性能需求那麼高(Linux Kernel最常用的鎖操作是spin lock和rw lock)。

task_state=R                                        
任務的狀態,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead

jiffies
全局變量jiffies取值爲自操作系統啓動以來的時鐘滴答的數目,在頭文件<linux/sched.h>中定義,數據類型爲unsigned long volatile (32位無符號長整型)。
系統啓動時,內核將該變量初始化爲0,此後,每次時鐘中斷處理程序就會增加該變量的值。因爲一秒內時鐘中斷的次數等於HZ, 所以jiffies一秒內增加的值也就爲HZ。
jiffies轉換爲秒可採用公式:(jiffies/HZ)計算,系統運行時間以秒爲單位,等於jiffies/Hz。
將秒轉換爲jiffies可採用公式:(seconds*HZ)計算。

"main" prio=5 tid=1 Native // 輸出了線程名,優先級,線程號,線程狀態,帶有『deamon』字樣的線程表示守護線程,即DDMS中『*』線程
  | group="main" sCount=1 dsCount=0 obj=0x7541b3c0 self=0xb4cf6500 // 輸出了線程組名,sCount被掛起次數,dsCount被調試器掛起次數,obj表示線程對象的地址,self表示線程本身的地址
  | sysTid=4280 nice=-1 cgrp=default sched=0/0 handle=0xb6f5cb34 // sysTid是Linux下的內核線程id,nice是線程的調度優先級,cgrp是調度屬組,sched分別標誌了線程的調度策略和優先級,handle是線程的處理函數地址。
  | state=S schedstat=( 52155108 81807757 159 ) utm=2 stm=3 core=0 HZ=100 // state是調度狀態;schedstat從 /proc/[pid]/task/[tid]/schedstat讀出,三個值分別表示線程在cpu上執行的時間、線程的等待時間和線程執行的時間片長度,有的android內核版本不支持這項信息,得到的三個值都是0;utm是線程用戶態下使用的時間值(單位是jiffies);stm是內核態下的調度時間值;core是最後執行這個線程的cpu核的序號。
  | stack=0xbe121000-0xbe123000 stackSize=8MB
  | held mutexes=
//調用棧信息
  native: #00 pc 00040984  /system/lib/libc.so (__epoll_pwait+20)
  native: #01 pc 00019f5b  /system/lib/libc.so (epoll_pwait+26)
  native: #02 pc 00019f69  /system/lib/libc.so (epoll_wait+6)
  native: #03 pc 00012c57  /system/lib/libutils.so (android::Looper::pollInner(int)+102)
  native: #04 pc 00012ed3  /system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+130)
  native: #05 pc 00082bed  /system/lib/libandroid_runtime.so (android::NativeMessageQueue::pollOnce(_JNIEnv*, _jobject*, int)+22)
  native: #06 pc 0000055d  /data/dalvik-cache/arm/system@[email protected] (Java_android_os_MessageQueue_nativePollOnce__JI+96)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:323)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5435)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:735)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)

cmdline:這個主要是當前這個進程被運行時的command line,裏面包括了運行時指定的一些參數,比如如果是mysqld的話就包括basedir==,datadir==,port=,socket=等等信息,你可以自己嘗試一下。

cwd:current working directory,當前的工作目錄

environ:這是個比較有用的文件,裏面記錄了當前進程的一些環境變量,比如一臺機器上對同一個系統起多個實例(當然是不同端口、不同數據目錄),而你想知道哪個進程對應的是在哪個數據目錄起來的(可能是因爲你想kill-9其中一個實例,因爲你怕弄錯,所以的確定哪個進程是對應哪個數據目錄),那麼此時你該怎麼辦呢?兩者方法:1.strings /proc/pid/environ | grep PWD 2.tr \\0 \\n < /proc/pid/envrion | grep PWD 即可。當然裏面還有很多的信息,你可以自己嘗試。

exe:這個就是氣這個進程的執行文件

fd:進程打開的文件描述符,我記得以前有人使用mysql遇到過two many openfiles的錯誤,這個就是打開太多的文件導致的,當然你如果只修改mysql裏面的參數可能不會起作用,因爲可能os上設置了一個比較小的數,所以要兩者都調大。

fdinfo:跟上面一個一樣,只不過只有文件描述符的值,沒有表示這個文件描述符是對應打開的哪個文件。在/proc很多時候都是這樣維護的:可能兩個文件裏面要表示的信息是一樣的,但是有一個一般是以人能容易讀懂的格式給出。

limits:這個跟fd有一點關聯,因爲這個裏面限制了進程對系統資源的使用額度,比如前面說的你可以打開多少文件,具體的設定你可以修改這個文件/etc/securiry/limits.conf,裏面也對每一項說得很清楚,只要會點E文,理解應該沒問題

oom_adj/oom_score:這兩個與linux的OOM機制有關的文件(關於OOM請看這裏),oom_adj相當於一個因子,它值越大,在OOM時更容易被系統kill掉,但最終決定是否被kill的是oom_score,其實計算這個oom_score時,就是根據oom_adj來的,oom_adj更大,計算出來的oom_score就更大,也能容易在OOM時被系統kill掉。當然如果是很重要的服務現場,爲了避免出現這種情況設置oom_adj=0就行,表示永遠不會因爲OOM被kill。

stat/status:這兩者要表示的信息都是一樣的,進程的基本狀態,但是後一個是以人容易讀懂的格式給出。

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