1. 程式人生 > >Linux核心排程分析(轉,侵刪)

Linux核心排程分析(轉,侵刪)

多工

併發和並行

Linux作為一個多工作業系統,必須支援程式的併發執行。

分類

  1. 非搶佔式多工
   除非任務自己結束,否則將會一直執行。
  1. 搶佔式多工(Linux)
這種情況下,由排程程式來決定什麼時候停止一個程序的執行,這個強制的掛起動作即為**“搶佔”**。採用搶佔式多工的基礎是使用**時間片輪轉**機制來為每個程序分配可以執行的時間單位。

Linux程序排程

發展歷史

Linux從2.5版本開始引入一種名為的排程器,後在2.6版本中將公平的的排程概念引入了排程程式,代替之前的排程器,稱為演算法(完全公平排程演算法)。

策略

I/O消耗型和處理器消耗型

  I/O消耗型程序是指那些大部分時間都在等待I/O操作的程序,處理器耗費型的程序則是指把大多數時間用於執行程式碼的程序,除非被搶佔,他們一般都一直在執行。

為了保證互動式應用和桌面系統的效能,一般Linux更傾向於優先排程I/O消耗型程序。

程序優先順序

Linux採用了兩種不同的優先順序範圍。

  1. 使用nice值:越大的nice值意味著更低的優先順序。 (-19 ~ 20之間)
  2. 實時優先順序:可配置,越高意味著程序優先順序越高。
任何實時的程序優先順序都高於普通的程序,因此上面的兩種優先順序範圍處於互不相交的範疇。
  1. 時間片:Linux中並不是以固定的時間值(如10ms)來分配時間片的,而是將處理器的使用比作為“時間片”劃分給程序。這樣,程序所獲得的實際CPU時間就和系統的負載密切相關。
Linux中的搶佔時機取決於新的可執行程序消耗了多少處理器使用比,
如果消耗的使用比當前程序小,則立刻投入執行,否則將推遲其執行。

舉例

現在我們來看一個簡單的例子,假設我們的系統只有兩個程序在執行,一個是文字編輯器(I/O消耗型),另一個是視訊解碼器(處理器消耗型)。

理想的情況下,文字編輯器應該得到更多的處理器時間,至少當它需要處理器時,處理器應該立刻被分配給它(這樣才能完成使用者的互動),這也就意味著當文字編輯器被喚醒的時候,它應該搶佔視訊解碼程式。

按照普通的情況,OS應該分配給文字編輯器更大的優先順序和更多的時間片,但在Linux中,這兩個程序都是普通程序,他們具有相同的nice值,因此它們將得到相同的處理器使用比(50%)。

但實際的執行過程中會發生什麼呢?CFS將能夠注意到,文字編輯器使用的處理器時間比分配給它的要少得多(因為大多時間在等待I/O),這種情況下,要實現所有程序“公平”地分享處理器,就會讓文字編輯器在需要執行時立刻搶佔視訊解碼器(每次都是如此)。

Linux排程演算法

排程器類

  Linux的排程器是以模組的方式提供的,這樣使得不同型別的程序按照自己的需要來選擇不同的排程演算法。

上面說講到的CFS演算法就是一個針對普通程序的排程器類,基礎的排程器會按照優先順序順序遍歷排程類,擁有一個可執行程序的最高優先順序的排程器類勝出,由它來選擇下一個要執行的程序。

Unix中的程序排程

存在的問題:

  1. nice值必須對映到處理器的絕對時間上去,這意味著同樣是瓜分100ms的兩個同樣優先順序的程序,發生上下文切換的次數並不相同,可能會差別很大。優先順序越低的程序分到的時間片單位越小,但是實際上他們往往是需要進行大量後臺計算的,這樣很不合理。
  2. 相對的nice值引發的問題:兩個nice值不同但差值相同的程序,分到的時間片的大小是受到其nice值大小影響的:比如nice值18和19的兩個程序分到的時間片是10ms和5ms,nice值為0和1的兩個程序分到的卻是100ms和95ms,這樣的對映並不合理。
  3. 如果要進行nice值到時間片的對映,我們必須能夠擁有一個可以測量的“絕對時間片”(這牽扯到定時器和節拍器的相關概念)。實際上,時間片是會隨著定時器的節拍而改變的,同樣的nice值最終對映到處理器時間時可能會存在差異。
  4. 為了能夠更快的喚醒程序,需要對新的要喚醒的程序提升優先順序,但是這可能會打破“公平性”。

為了解決上述的問題,CFS對時間片的分配方式進行了根本性的重新設計,摒棄了時間片,用處理器使用比重來代替它。

公平排程(CFS)

出發點:程序排程的效果應該如同系統具備一個理想的多工處理器——我們可以給任何程序排程無限小的時間週期,所以在任何可測量範圍內,可以給n個程序桐鄉多的執行時間。

舉個例子來區分Unix排程和CFS:有兩個執行的優先順序相同的程序,在Unix中可能是每個各執行5ms,執行期間完全佔用處理器,但在“理想情況”下,應該是,能夠在10ms內同時執行兩個程序,每個佔用處理器一半的能力。

CFS的做法是:在所有可執行程序的總數上計算出一個程序應該執行的時間,nice值不再作為時間片分配的標準,而是用於處理計算獲得的處理器使用權重。

接下來我們考慮排程週期,理論上,排程週期越小,就越接近“完美排程”,但實際上這必然會帶來嚴重的上下文切換消耗。在CFS中,為能夠實現的最小排程週期設定了一個近似值目標,稱為“目標延遲”,於此同時,為了避免不可接受的上下文切換消耗,為每個程序所能獲得的時間片大小設定了一個底線——最小粒度(通常為1ms)。

在每個程序的平均執行時間大於最小粒度的情況下,CFS無疑是公平的,nice值用於計算一個程序在當前這個最小排程週期中所應獲得的處理器時間佔比,這樣就算nice值不同,只要差值相同,總是能得到相同的時間片。我們假設一個最小排程週期為20ms,兩個程序的nice值差值為5:

  • 兩程序的nice值分別為0和5,後者獲得的時間片是前者的1/3,因此最終分別獲得15ms和5ms
  • 兩程序的nice值分別為10和15,後者獲得的時間片是前者的1/3,最終結果也是15ms和5ms

關於上面這個推論,可能有些難以理解,所以我們深入一下,看看在底層nice差值究竟是如何影響到處理區佔比的。

首先,在底層,在實際計算一個程序的處理器佔比之前,核心會先把nice值轉換為一個權重值weight,這個轉換的公式如下:

weight = 1024/(1.25^nice)

舉個例子,預設nice值的程序得到的權重就是1024/(1.25^0) = 1024/1 = 1024。

這個轉換公式保證了我們可以得到非負的權重值,並且nice對權重的影響是在指數上的。

好,現在假設我們的可執行程序佇列中有n個程序,他們的權重和記為,那麼任意一個程序i最終得到的處理器佔比將是

接著,我們不難推匯出,任意兩個程序i和j所分配的到的處理器佔比的比例應該是,經過簡單的數學推導就可以得到最後的結果:,這意味著只要兩個nice值的差值相同,兩個程序所獲得處理器佔比永遠是相同的比例,從而解決了上面的第3點問題。

上述的轉換公式參考自:https://oakbytes.wordpress.com/2012/06/06/linux-scheduler-cfs-and-nice

總結一下,任何程序所獲得的處理器時間是由它自己和所有其他可執行程序nice值的相對差值決定的,因此我們可以說,CFS至少保證了給每個程序公平的處理器佔用比,算是一種近乎完美的多工排程方式了。

Linux排程的實現

下面我們來看看CFS是如何實現的,一般我們把它分為4個主要的部分來分析。

時間記賬

所有的排程器都必須對程序的執行時間記賬,換句話說就是要知道當前排程週期內,程序還剩下多少個時間片可用(這將會是搶佔的一個重要標準)

1. 排程器實體結構

CFS中用於記錄程序執行時間的資料結構為“排程實體”,這個結構體被定義在中:

struct sched_entity {
	/* 用於進行排程均衡的相關變數,主要跟紅黑樹有關 */
	struct load_weight		load; // 權重,跟優先順序有關
	unsigned long			runnable_weight; // 在所有可執行程序中所佔的權重 struct rb_node run_node; // 紅黑樹的節點 struct list_head group_node; // 所在程序組 unsigned int on_rq; // 標記是否處於紅黑樹執行佇列中 u64 exec_start; // 程序開始執行的時間 u64 sum_exec_runtime; // 程序總執行時間 u64 vruntime; // 虛擬執行時間,下面會給出詳細解釋 u64 prev_sum_exec_runtime; // 程序在切換CPU時的sum_exec_runtime,簡單說就是上個排程週期中執行的總時間 u64 nr_migrations; struct sched_statistics statistics; // 以下省略了一些在特定巨集條件下才會啟用的變數 }

注:本文中所有用到的linux原始碼均來自linux在github上官方的git庫(2018.01)

2. 虛擬實時 (vruntime)

  現在我們來談談上面結構體中的vruntime變數所表示的意義。我們稱它為“虛擬執行時間”,該執行時間的計算是經過了所有可執行程序總數的標準化(簡單說就是加權的)。它以ns為單位,與定時器節拍不再相關。

可以認為這是CFS為了能夠實現理想多工處理而不得不虛擬的一個新的時鐘,具體地講,一個程序的vruntime會隨著執行時間的增加而增加,但這個增加的速度由它所佔的權重來決定。

結果就是權重越高,增長越慢:所得到的排程時間也就越小 —— CFS用它來記錄一個程式到底運行了多長時間以及還應該執行多久。

下面我們來看一下這個記賬功能的實現原始碼()

/*
 * Update the current task's runtime statistics.
 */
static void update_curr(struct cfs_rq *cfs_rq) { struct sched_entity *curr = cfs_rq->curr; u64 now = rq_clock_task(rq_of(cfs_rq)); u64 delta_exec; if (unlikely(!curr)) return; // 獲得從最後一次修改負載後當前任務所佔用的執行總時間 delta_exec = now - curr->exec_start; if (unlikely((s64)delta_exec <= 0)) return; // 更新執行開始時間 curr->exec_start = now; schedstat_set(curr->statistics.exec_max, max(delta_exec, curr->statistics.exec_max)); curr->sum_exec_runtime += delta_exec; schedstat_add(cfs_rq->exec_clock, delta_exec); // 計算虛擬時間,具體的轉換演算法寫在clac_delta_fair函式中 curr->vruntime += calc_delta_fair(delta_exec, curr); update_min_vruntime(cfs_rq); if (entity_is_task(curr)) { struct task_struct *curtask = task_of(curr); trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime); cgroup_account_cputime(curtask, delta_exec); account_group_exec_runtime(curtask, delta_exec); } account_cfs_rq_runtime(cfs_rq, delta_exec); }

該函式計算了當前程序的執行時間,將其存放在變數中,然後使用函式計算對應的虛擬執行時間,並更新值。

這個函式是由系統定時器週期性呼叫的(無論程序的狀態是什麼),因此vruntime可以準確地測量給定程序的執行時間,並以此為依據推斷出下一個要執行的程序是什麼。

程序選擇

這裡便是排程的核心部分,用一句話來梗概CFS演算法的核心就是選擇具有最小vruntime的程序作為下一個需要排程的程序。

為了實現選擇,當然要維護一個可執行的程序佇列(教科書上常說的ready佇列),CFS使用了紅黑樹來組織這個佇列。

紅黑樹是一種非常著名的資料結構,但這裡我們不討論它的實現和諸多特性(過於複雜),我們記住:紅黑樹是一種自平衡二叉樹,再簡單一點,它是一種以樹節點方式儲存資料的結構,每個節點對應了一個鍵值,利用這個鍵值可以快速索引樹上的資料,並且它可以按照一定的規則自動調整每個節點的位置,使得通過鍵值檢索到對應節點的速度和整個樹節點的規模呈指數比關係。

1. 找到下一個任務節點

先假設一個紅黑樹儲存了系統中所有的可執行程序,節點的鍵值就是它們的vruntime,CFS現在要找到下一個需要排程的程序,那麼就是要找到這棵紅黑樹上鍵值最小的那個節點:就是最左葉子節點。

實現此過程的原始碼如下():

static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr) { struct sched_entity *left = __pick_first_entity(cfs_rq); struct sched_entity *se; /* * If curr is set we have to see if its left of the leftmost entity * still in the tree, provided there was anything in the tree at all. */ if (!left || (curr && entity_before(curr, left))) left = curr; se = left; /* ideally we run the leftmost entity */ /* * 下面的過程主要針對一些特殊情況,我們在此不做討論 */ if (cfs_rq->skip == se) { struct sched_entity *second; if (se == curr) { second = __pick_first_entity(cfs_rq); } else { second = __pick_next_entity(se); if (!second || (curr && entity_before(curr, second))) second = curr; } if (second && wakeup_preempt_entity(second, left) < 1) se = second; } if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1) se = cfs_rq->last; if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1) se = cfs_rq->next; clear_buddies(cfs_rq, se); return se; }

2. 向佇列中加入新的程序

向可執行佇列中插入一個新的節點,意味著有一個新的程序狀態轉換為可執行,這會發生在兩種情況下:一是當程序由阻塞態被喚醒,二是fork產生新的程序時。

將其加入佇列的過程本質上來說就是紅黑樹插入新節點的過程:

static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) { bool renorm = !(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATED); bool curr = cfs_rq->curr == se; /* * 如果要加入的程序就是當前正在執行的程序,重新規範化vruntime * 然後更新當前任務的執行時統計資料 */ if (renorm && curr) se->vruntime += cfs_rq->min_vruntime; update_curr(cfs_rq); /* * Otherwise, renormalise after, such that we're placed at the current * moment in time, instead of some random moment in the past. Being * placed in the past could significantly boost this task to the * fairness detriment of existing tasks. */ if (renorm && !curr) se->vruntime += cfs_rq->min_vruntime; /* * 更新對應排程器實體的各種記錄值 */ update_load_avg(cfs_rq, se, UPDATE_TG | DO_ATTACH); update_cfs_group(se); enqueue_runnable_load_avg(cfs_rq, se); account_entity_enqueue(cfs_rq, se); if (flags & ENQUEUE_WAKEUP) place_entity(cfs_rq, se, 0); check_schedstat_required(); update_stats_enqueue(cfs_rq, se, flags); check_spread(cfs_rq, se); if (!curr) __enqueue_entity(cfs_rq, se); // 真正的插入過程 se->on_rq = 1; if (cfs_rq->nr_running == 1) { list_add_leaf_cfs_rq(cfs_rq); check_enqueue_throttle(cfs_rq); } }

上面的函式主要用來更新執行時間和各類統計資料,然後呼叫來把資料真正插入紅黑樹中:

static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) { struct rb_node **link = &cfs_rq->tasks_timeline.rb_root.rb_node; struct rb_node *parent = NULL; struct sched_entity *entry; bool leftmost = true; /* * 在紅黑樹中搜索合適的位置 */ while (*link) { parent = *link; entry = rb_entry(parent, struct sched_entity, run_node); /* * 具有相同鍵值的節點會被放在一起 */ if (entity_before(se, entry)) { link = &parent->rb_left; } else { link = &parent->rb_right; leftmost = false; } } rb_link_node(&se->run_node, parent, link); rb_insert_color_cached(&se->run_node, &cfs_rq->tasks_timeline, leftmost); }

while()迴圈是遍歷樹以尋找匹配鍵值的過程,也就是搜尋一顆平衡樹的過程。找到後我們對要插入位置的父節點執行來將節點插入其中,然後更新紅黑樹的自平衡相關屬性。

3. 從佇列中移除程序

從佇列中刪除一個節點有兩種可能:一是程序執行完畢退出,而是程序受到了阻塞。

static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) { /* * 更新“當前程序”的執行統計資料 */ update_curr(cfs_rq); /* * When dequeuing a sched_entity, we must: * - Update loads to have both entity and cfs_rq synced with now. * - Substract its load from the cfs_rq->runnable_avg. * - Substract its previous weight from cfs_rq->load.weight. * - For group entity, update its weight to reflect the new share * of its group cfs_rq. */ update_load_avg(cfs_rq, se, UPDATE_TG); dequeue_runnable_load_avg(cfs_rq, se); update_stats_dequeue(cfs_rq, se, flags); clear_buddies(cfs_rq, se); if (se != cfs_rq->curr) __dequeue_entity(cfs_rq, se); se->on_rq = 0; account_entity_dequeue(cfs_rq, se); /* * 重新規範化vruntime */ if (!(flags & DEQUEUE_SLEEP)) se->vruntime -= cfs_rq->min_vruntime; /* return excess runtime on last dequeue */ return_cfs_rq_runtime(cfs_rq); update_cfs_group(se); /* * Now advance min_vruntime if @se was the entity holding it back, * except when: DEQUEUE_SAVE && !DEQUEUE_MOVE, in this case we'll be * put back on, and if we advance min_vruntime, we'll be placed back * further than we started -- ie. we'll be penalized. */ if ((flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)) == DEQUEUE_SAVE) update_min_vruntime(cfs_rq); }

和插入一樣,實際對樹節點操作的工作由實現:

static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) { rb_erase_cached(&se->run_node, &cfs_rq->tasks_timeline); }

可以看到刪除一個節點要比插入簡單的多,這得益於紅黑樹本身實現的函式。

排程器入口

  正如上文所述,每當要發生程序的排程時,是有一個統一的入口,從該入口選擇真正需要呼叫的排程類。

這個入口是核心中一個名為的函式,它會找到一個最高優先順序的排程類,這個排程類擁有自己的可執行佇列,然後向其詢問下一個要執行的程序是誰。

這個函式中唯一重要的事情是執行了這個函式(定義在中),它以優先順序為順序,依次檢查每一個排程類,並且從最高優先順序的排程類中選擇最高優先順序的程序。

static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) { const struct sched_class *class; struct task_struct *p; /* * 優化:如果當前所有要排程的程序都是普通程序,那麼就直接採用普通程序的排程類(CFS) */ if (likely((prev->sched_class == &idle_sched_class || prev->sched_class == &fair_sched_class) && rq->nr_running == rq->cfs.h_nr_running)) { p = fair_sched_class.pick_next_task(rq, prev, rf); if (unlikely(p == RETRY_TASK)) goto again; /* Assumes fair_sched_class->next == idle_sched_class */ if (unlikely(!p)) p = idle_sched_class.pick_next_task(rq, prev, rf); return p; } // 遍歷排程類 again: for_each_class(class) { p = class->pick_next_task(rq, prev, rf); if (p) { if (unlikely(p == RETRY_TASK)) goto again; return p; } } /* The idle class should always have a runnable task: */ BUG(); }

每個排程類都實現了方法,它會返回下一個可執行程序的指標,沒有則返回NULL。排程器入口從第一個返回非NULL的類中選擇下一個可執行程序。

睡眠和喚醒

睡眠和喚醒的流程在linux中是這樣的:

  • 睡眠:程序將自己標記成休眠狀態,然後從可執行紅黑樹中移除,放入等待佇列,然後呼叫選擇和執行一個其他進程。
  • 喚醒:程序被設定為可執行狀態,然後從等待佇列移到可執行紅黑樹中去。

休眠在Linux中有兩種狀態,一種會忽略訊號,一種則會在收到訊號的時候被喚醒並響應。不過這兩種狀態的程序是處於同一個等待佇列上的。

1.等待佇列

和可執行佇列的複雜結構不同,等待佇列在linux中的實現只是一個簡單的連結串列。所有有關等待佇列的資料結構被定義在中,具體的實現程式碼則被定義在中。

核心使用結構來表示一個等待佇列,它其實就是一個連結串列的頭節點,但是加入了一個自旋鎖來保持一致性(等待佇列在中斷時可以被隨時修改)

struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
}; typedef struct wait_queue_head wait_queue_head_t;

而休眠的過程需要程序自己把自己加入到一個等待佇列中,這可以使用核心所提供的、推薦的函式來實現。

一個可能的流程如下:

  1. 呼叫巨集建立一個等待佇列的項(連結串列的節點)
  2. 呼叫把自己加到佇列中去。該佇列會在進程等待的條件滿足時喚醒它,當然喚醒的具體操作需要程序自己定義好(你可以理解為一個回撥)
  3. 呼叫方法把自己的狀態變更為上面說到的兩種休眠狀態中的其中一種。

下面是上述提到的方法的原始碼:

void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { unsigned long flags; wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&wq_head->lock, flags); __add_wait_queue(wq_head, wq_entry); spin_unlock_irqrestore(&wq_head->lock, flags); } static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) { list_add(&wq_entry->entry, &wq_head->head); }
void
prepare_to_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state) { unsigned long flags; wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&wq_head->lock, flags); if (list_empty(&wq_entry->entry)) __add_wait_queue(wq_head, wq_entry); // 標記自己的程序狀態 set_current_state(state); spin_unlock_irqrestore(&wq_head->lock, flags); }

2.喚醒

喚醒操作主要通過實現,它會喚醒指定等待佇列上的所有程序。內部由函式將對應的程序標記為狀態,接著呼叫將程序加入紅黑樹中。

系函式由巨集定義,一般具體內部由下面這個函式實現:

/*
 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
 */
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key, wait_queue_entry_t *bookmark) { wait_queue_entry_t *curr, *next; int cnt = 0; if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) { curr = list_next_entry(bookmark, entry); list_del(&bookmark->entry); bookmark->flags = 0; } else curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry); if (&curr->entry == &wq_head->head) return nr_exclusive; list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) { unsigned flags = curr->flags; int ret; if (flags & WQ_FLAG_BOOKMARK) continue; ret = curr->func(curr, mode, wake_flags, key); if (ret < 0) break; if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) && (&next->entry != &wq_head->head)) { bookmark->flags = WQ_FLAG_BOOKMARK; list_add_tail
            
           

相關推薦

Linux核心排程分析

多工 併發和並行 Linux作為一個多工作業系統,必須支援程式的併發執行。 分類 非搶佔式多工    除非任務自己結束,否則將會一直執行。 搶佔式多工(Linux) 這種情況下,由排程程式來決定什麼時候停止一個程序的執行,這個強制的掛起動作即為**“搶佔”**。採用搶佔式多工

pycharm快捷鍵及一些常用設定轉載

轉載自: https://www.cnblogs.com/shizhengwen/p/6631527.html pycharm快捷鍵及一些常用設定(個人備忘) 在PyCharm /opt/pycharm-3.4.1/help目錄下可以找到ReferenceCard.pdf快捷鍵英文

linux核心排程演算法3--多核系統的負載均衡

多核CPU現在很常見,那麼問題來了,一個程式在執行時,只在一個CPU核上執行?還是交替在多個CPU核上執行呢?LINUX核心是如何在多核間排程程序的呢?又是核心又是CPU核,兩個核有點繞,下面稱CPU處理器來代替CPU核。 實際上,如果你沒有對你的程序做過特殊處理的話,L

linux核心排程演算法2--CPU時間片如何分配

核心在微觀上,把CPU的執行時間分成許多分,然後安排給各個程序輪流執行,造成巨集觀上所有的程序彷彿同時在執行。雙核CPU,實際上最多隻能有兩個程序在同時執行,大家在top、vmstat命令裡看到的正在執行的程序,並不是真的在佔有著CPU哈。 所以,一些設計良好的高效能程序,比如nginx,都是實際上有幾顆C

淺析Linux核心同步機制

 很早之前就接觸過同步這個概念了,但是一直都很模糊,沒有深入地學習瞭解過,近期有時間了,就花時間研習了一下《linux核心標準教程》和《深入linux裝置驅動程式核心機制》這兩本書的相關章節。趁剛看完,就把相關的內容總結一下。為了弄清楚什麼事同步機制,必須要弄明白以下三個

linux核心部件分析——原子性操作atomic_t +自我分析總結

在任何處理器平臺下,都會有一些原子性操作,供作業系統使用,我們這裡只講x86下面的。 原子操作的概念來自物理學中微粒的概念原子不可再分性,說明原子操作是不會被執行緒排程機制打斷的操作,不會被編譯器自動優化掉,必定執行的操作; 在單處理器情況下,每條

Darknet 程式碼分析之一準備工作

用yolo v3檢測圖片中的某種植物,沒找到網路定義檔案中的說明,看一下darknet的程式碼,做個筆記。 程式碼來源: Alexey AB維護的Darknet 版本: https://github.com/AlexeyAB/darknet 關於訓練自己的資料,說明見 https:/

面向物件模型分析繼承多型

class是一種特殊的struct 在記憶體中 class 依舊可以看做變數的集合 class 中的成員函式和成員變數是分開存放的 每個物件有獨立的成員變數 所有物件共享類中的成員函式

關聯分析AprioriFP-growth

關聯分析是資料探勘中的重要組成部分,旨在挖掘資料中的頻繁模式。我們可以通過一個案例資料庫挖掘著名案例來大致瞭解挖掘頻繁項集併產生關聯規則。 關聯分析的基本概念 關聯分析:在大規模資料集中尋找有趣的關係 頻繁項集:經常出現在一起的物品集合,即包含0個或者多個項的集合 關

Arduino核心檔案分析以Stm32duino為例

       這篇部落格主要是分析stm32duino的底層檔案結構,來分析stm32duino 的實現原理和它的基本框架。 使用的工具是Source Insight ,新建工程,新增原始碼路徑之後可以進行分析。 開啟工程原始碼的資料夾後,有四個資料夾,我們主要分

Linux 啟動過程分析 SysV init啟動模式

      本篇主要分析傳統的Linux啟動方式  SysV init啟動模式。(注:當前Linux發行版大多采用Systemd 啟動模式來替代傳統的 SysV init啟動模式。)    &nbs

核心異常分析訪問了空指標

/* *除錯核心驅動的過程中雖然編譯成功了 但是 載入時難免有時會遇到核心異常的情況 *其中訪問了空指標的情況 又是常見的異常原因 以下只是一個簡單的例子 在實際的工程 *中 引數傳遞的方式更復雜 這時就 更要細心查詢最終的根源 才能 排除異常 ... */ #inclu

Linux用戶管理之使用/bin/false和/usr/sbin/nologin拒絕用戶登錄及其功能分析

其他 spa 狀態 roo 服務器 linux用戶 密碼 targe let /bin/nologin,/bin/false的意思是禁止某個用戶登錄。 比較常用的用法: #添加一個不能登錄的用戶 useradd -d /usr/local/apache -g ap

Linux核心原始碼分析--zImage出生實錄Linux-3.0 ARMv7

此文為兩年前為好友劉慶敏的書《嵌入式Linux開發詳解--基於AT91RM9200和Linux 2.6》中幫忙寫的章節的重新整理。如有雷同,純屬必然。經作者同意,將我寫的部分重新整理後放入blog中。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Master原理剖析與原始碼分析:資源排程機制原始碼分析schedule()兩種資源排程演算法

1、主備切換機制原理剖析與原始碼分析 2、註冊機制原理剖析與原始碼分析 3、狀態改變處理機制原始碼分析 4、資源排程機制原始碼分析(schedule(),兩種資源排程演算法) * Dri

Linux核心原始碼分析--記憶體管理一、分頁機制

        Linux系統中分為幾大模組:程序排程、記憶體管理、程序通訊、檔案系統、網路模組;各個模組之間都有一定的聯絡,就像蜘蛛網一樣,所以這也是為什麼Linux核心那麼難理解,因為不知道從哪裡開始著手去學習。很多人會跟著系統上電啟動 BIOS-->bootse

LINUX-核心-中斷分析-中斷向量表2-mips

mips中斷概念 在《MIPS體系結構透視》的第5章說到,在MIPS中,中斷、陷阱、系統呼叫和任何可以中斷程式正常執行流的情況全被都被稱為異常。 以上這種統一到“異常”的概念及其邏輯當然會體現在MIPS的異常入口點的設計中,特別如MIPS中斷入口點的引出。

大併發連線的oracle在Linux下記憶體不足的問題的分析

最近一臺裝有Rhel5.3的40G記憶體的機器上有一個oracle資料庫,資料庫的SGA設定為20G,當執行業務時,一個業務高峰期時,發現swap頻繁交換,CPU 100%,Load很高,基本體現為記憶體不足。此時的連線數在600個左右。按記憶體的計算:每個連線佔用記憶體基本

Linux核心原始碼分析--檔案系統五、Inode.c

_bmap()         1、_bmap()函式用於把一個檔案資料塊對映到盤塊的處理操作                  因為一個i節點對應一個檔案,所以上面的i節點對映的邏輯塊號就是檔案資料存放的邏輯塊號;i_zone[0]到i_zone[6]是直接邏輯塊號,i

塊IO層Linux核心原始碼分析

背景 本篇部落格重點分析塊IO層,試圖瞭解在linux原始碼中是如何實現塊IO的。 基本知識 塊裝置與字元裝置 塊裝置與字元裝置都是物理外設。簡單來說,塊裝置與字元裝置的最大區別在於塊裝置都隨機對資料片段進行讀寫的,而字元裝置都以順序對資料片段進