第一次作業:深入Linux源碼分析進程模型

?

一.進程的概念

? ? ?第一,進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲著活動過程調用的指令和本地變量。

? ? ?第二,進程是一個“執行中的程序”。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操作系統執行之),它才能成為一個活動的實體,我們稱其為進程。

其中在Linux內核中賦予它更通用的名稱----任務(task)

  進程在整個內核中的功能位置:

  我們還可以分兩個層次對操作系統進程進行討論。?

  在較高的層次上,“進程”是一個重要的組織概念,用其說明一個計算機系統作為一個整體的活動。將計算機系統視作若干進程的組合活動是適合的,每一個進程與一道特定的程序相結合。例如“shell”或者“vi”編輯程序。在這一層次上,進程本身被視作系統中的活動實體,而真正的活動部件本體,即處理機和外部設備則被消隱,不引起人們的注意。進程誕生、生長,然后死亡;它們存在的數量在不斷變化;它們可以獲得并釋放資源;它們可以交互作用、合作、沖突、共享資源等等。?

  在較低的層次上,進程是不活動的實體,它們依靠活動的實體,例如處理機才起作用。借助于頻繁地使用處理機從一個進程映像的執行切換到另一個,就可以產生一種印象:每一個進程映像都連續發生變化,這就導致較高層次上的解釋。

? ? ? ?Linux進程的四個要素:

  1.有一段程序供其執行,這段程序不一定是某個進程所專有的,可以與其他進程共用。

  2.有進程專用的內核空間堆棧。

  3.在內核中有一個task_struct數據結構,即通常所說的“進程控制塊”。有了這個數據結構,進程才能成為內核調度的一個基本單位接受內核的調度。同時,這個結構還記錄著進程所占用的各項資源。

  4.有獨立的存儲空間,這意味著擁有專有的用戶空間;進一步,還意味著除前述的內核空間堆棧外還有其專用的用戶空間堆棧。有一點必須指出,內核空間是不能獨立的,任何進程都不可能直接(不通過系統調用)改變內核空間的內容(除其本身的內核空間堆棧以外)。

二.進程的組織:

進程控制塊

進程創建時,操作系統就新建一個PCB結構,它之后就常駐內存,任一時刻可以存取,?在進程結束時刪除。PCB是進程實體的一部分,是進程存在的唯一標志。

當創建一個進程時,系統為該進程建立一個PCB;當進程執行時,系統通過其PCB?了 解進程的現行狀態信息,以便對其進行控制和管理;當進程結束時,系統收回其PCB,該進 程隨之消亡。操作系統通過PCB表來管理和控制進程。

表2-1 PCB通常包含的內容
進程描述信息進程控制和管理信息資源分配清單處理機相關信息
進程標識符(PID)進程當前狀態代碼段指針通用寄存器值
用戶標識符(UID)進程優先級數據段指針地址寄存器值
?代碼運行入口地址堆棧段指針控制寄存器值
?程序的外存地址文件描述符標志寄存器值
?進入內存時間鍵盤狀態字
?處理機占用時間鼠標?
?信號量使用??

表2-1是一個PCB的實例,PCB主要包括進程描述信息、進程控制和管理信息、資源 分配清單和處理機相關信息等。各部分的主要說明如下:

1) 進程描述信息
進程標識符:標志各個進程,每個進程都有一個并且是唯一的標識號。
用戶標識符:進程歸屬的用戶,用戶標識符主要為共享和保護服務。

2) 進程控制和管理信息
進程當前狀態:描述進程的狀態信息,作為處理機分配調度的依據。
進程優先級:描述進程搶占處理機的優先級,優先級高的進程可以優先獲得處理機。

3) 資源分配清單,用于說明有關內存地址空間或虛擬地址空間的狀況;所打開文件的 列表和所使用的輸入/輸出設備信息。

4) 處理機相關信息,主要指處理機中各寄存器值,當進程被切換時,處理機狀態信息 都必須保存在相應的PCB中,以便在該進程重新執行時,能再從斷點繼續執行。

在一個系統中,通常存在著許多進程,有的處于就緒狀態,有的處于阻塞狀態,而且阻塞的原因各不相同。為了方便進程的調度和管理,需要將各進程的PCB用適當的方法組織起來。目前,常用的組織方式有鏈接方式和索引方式兩種。鏈接方式將同一狀態的PCB鏈接成一個隊列,不同狀態對應不同的隊列,也可以把處于阻塞狀態的進程的PCB,根據其阻塞原因的不同,排成多個阻塞隊列。索引方式是將同一狀態的進程組織在一個索引表中,索引表的表項指向相應的PCB,不同狀態對應不同的索引表,如就緒索引表和阻塞索引表等。
大量的進程是如何組織的:
/* wq為某個等待隊列的隊列頭 */
void sleep_on (wait_queue_head_t *wq)
{/* 聲明一個等待隊列結點 */wait_queue_t wait;/* 用當前進程初始化這個等待隊列結點 */init_waitqueue_entry (&wait, current);/* 設置當前進程狀態為TASK_UNINTERRUPTIBLE */current->state = TASK_UNINTERRUPTIBLE;/* 將這個代表著當前進程的等待隊列結點加入到wq這個等待隊列 */add_wait_queue (wq, &wait);/* 請求調度器進行調度,執行完schedule后進程會被移除CPU運行隊列,只有等待隊列喚醒后才會重新回到CPU運行隊列 */schedule ();/* 這里進程已經被等待隊列喚醒,重新移到CPU運行隊列,也就是等待的條件已經為真,喚醒后第一件事就是將自己從等待隊列wq中移除 */remove_wait_queue (wq, &wait);  
}
在Linux操作系統中,用戶創建一個新進程的一個方法是調用系統調用fork。調用fork的進程是父進程(parent process),而新創建的進程是子進程(child? process)。在系統中調用fork返回時,子進程是父進程的一個拷貝,兩個進程除了返回PID(Process ID)不同外,具有完全一樣的變量值,它們打開的文件都相同。而在系統初啟時由內核內部地創建的#0進程(idle進程)是唯一不通過fork而創建的進程;#1進程是系統創建的init進而在系統初啟時由內核內部地創建的#0進程(idle進程)是唯一不通過fork而創建的進程;#1進程是系統創建的init進程,是系統其他每個進程的父進程。在Linux中,fork和_clone函數的具體實現是通過do_fork函數來實現的。
do_fork的算法如下:
int do_fork(unsigned long clone_flags,unsigned long usp,struct pt_regs *regs)
{取一個空閑的task數組表項和唯一的PID號;根據clone_flags參數的值將父進程的進程表現拷貝到子進程表項中或設置為共享;把進程加入進程圖表設置跟蹤進程的數據結構調用hash_pid把新進程置入pidhash表中;調用wake_up_process設進程為TASK_RUNNING并置入運行隊列;return(p->pid)
}

在創建新進程后,我們需要它來處理其他實際的工作,通過調用exec來執行別的實際程序,就能夠變成獨立于其他進程的進程了,因此,創建一個真正的進程--與其祖先不同的程序鏡像,要分為兩步,一步是fork,另一步是exec.下面是C代碼描述:

/*實驗fork和exec*/
if((result=fork()==0)
{
/*child code*/if(exec(  "new_program")<0)perror("exec failed");exit(1);
}
else if(result<0)
{
perror ("fork failde");
}

三.進程狀態轉換

在一個給定時刻內,進程處于下面六種狀態之一,進程的當前狀態被記錄在struct task_struct結構中的state成員中

struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */    …… }; 

/include/linux/sched.h定義的進程狀態:

#define TASK_RUNNING 0 /* 進程準備好運行 */
#define TASK_INTERRUPTIBLE   1  /* 等待特定事件,可以被信號中斷  */ 
#define TASK_UNINTERRUPTIBLE  2  /* 等待特定硬件條件,不可以被信號中斷*/ 
#define TASK_ZOMBIE        4  /* 進程已經退出  */ 
#define TASK_STOPPED        8  /* 進程已經停止運行  */ 
#define TASK_SWAPPING       16  /* 進程正在執行磁盤交換工作  */ 

何時刻一個處理機僅能執行一個進程,而可能不止一個進程處于TASK_RUNNING狀態。TASK_RUNNING并不意味著該進程可以立即獲得CPU(雖然有時候是這樣),而是僅僅說明只要CPU一旦可用,進程就可以立即準備執行了。進程處于TASK_ZOMBIE狀態,意味進程已經退出了(或已經被殺掉了),但是其相關的struct task_struct結構并沒有被刪除。這樣即使子進程已經退出,也允許父進程對已經死去的子孫進程進行查詢。父進程通過wait來獲取TASK_ZOMBIE進程的信息,同時釋放它占用的struct task_struct結構。

?

四.task_struct數據結構

?

?

struct task_struct
{volatile long state;                                                    /*state成員的可能取值如下
                                         #define TASK_RUNNING 0
                                         
#define TASK_INTERRUPTIBLE 1
                                       #define TASK_UNINTERRUPTIBLE 2
                                       #define TASK_STOPPED 4
                                       
#define TASK_TRACED 8
                                       #define EXIT_DEAD 16#define EXIT_ZOMBIE 32#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
                             
#define TASK_DEAD 64#define TASK_WAKEKILL 128 #define TASK_WAKING 256#define TASK_PARKED 512#define TASK_NOLOAD 1024#define TASK_STATE_MAX 2048#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)*/
struct list_head run_list;struct task_struct * next_task,*prev_task;pid_t pid;struct task_struct*p_opptr,*P_pptr;/*1.p_opptr指向進程的原始祖先2.p_pptr指向進程的當前祖先3.p_cptr指向進程的最年輕子孫4.p_ysptr指向進程的下一個最年輕兄弟5.p_osptr指向進程的下一個最古老兄弟*/*p_cptr,*p_ysptr,*p_osptr;struct task_struct*pidhash_next;struct task_struct**pidhash_pprev; }

進程標識符:

 pid_t pid; //進程的標識符

pid_t tgid; //線程組標識符

進程標記符:

unsigned int flags; /* per process flags, defined below */ 

?五.Linux的調度

調度器介紹

隨著時代的發展,linux也從其初始版本穩步發展到今天,從2.4的非搶占內核發展到今天的可搶占內核,調度器無論從代碼結構還是設計思想上也都發生了翻天覆地的變化,其普通進程的調度算法也從O(1)到現在的CFS,一個好的調度算法應當考慮以下幾個方面:
  • 公平:保證每個進程得到合理的CPU時間。
  • 高效:使CPU保持忙碌狀態,即總是有進程在CPU上運行。
  • 響應時間:使交互用戶的響應時間盡可能短。
  • 周轉時間:使批處理用戶等待輸出的時間盡可能短。
  • 吞吐量:使單位時間內處理的進程數量盡可能多。
  • 負載均衡:在多核多處理器系統中提供更高的性能
而整個調度系統至少包含兩種調度算法,是分別針對實時進程普通進程,所以在整個linux內核中,實時進程和普通進程是并存的,但它們使用的調度算法并不相同,普通進程使用的是CFS調度算法(紅黑樹調度)。之后會介紹調度器是怎么調度這兩種進程。
在linux中,進程主要分為兩種,一種為實時進程,一種為普通進程
  • 實時進程:對系統的響應時間要求很高,它們需要短的響應時間,并且這個時間的變化非常小,典型的實時進程有音樂播放器,視頻播放器等。
  • 普通進程:包括交互進程和非交互進程,交互進程如文本編輯器,它會不斷的休眠,又不斷地通過鼠標鍵盤進行喚醒,而非交互進程就如后臺維護進程,他們對IO,響應時間沒有很高的要求,比如編譯器。
它們在linux內核運行時是共存的,實時進程的優先級為0~99,實時進程優先級不會在運行期間改變(靜態優先級),而普通進程的優先級為100~139,普通進程的優先級會在內核運行期間進行相應的改變(動態優先級)。

調度策略

在linux系統中,調度策略分為
  • SCHED_NORMAL:普通進程使用的調度策略,現在此調度策略使用的是CFS調度器。
  • SCHED_FIFO:實時進程使用的調度策略,此調度策略的進程一旦使用CPU則一直運行,直到有比其更高優先級的實時進程進入隊列,或者其自動放棄CPU,適用于時間性要求比較高,但每次運行時間比較短的進程。
  • SCHED_RR:實時進程使用的時間片輪轉法策略,實時進程的時間片用完后,調度器將其放到隊列末尾,這樣每個實時進程都可以執行一段時間。適用于每次運行時間比較長的實時進程

??

調度

首先,我們需要清楚,什么樣的進程會進入調度器進行選擇,就是處于TASK_RUNNING狀態的進程,而其他狀態下的進程都不會進入調度器進行調度。系統發生調度的時機如下
  • 調用cond_resched()時
  • 顯式調用schedule()時
  • 從系統調用或者異常中斷返回用戶空間時
  • 從中斷上下文返回用戶空間

管理組調度,內核引進了struct task_group結構,如下:

 /* 進程組,用于實現組調度 */struct task_group {/* 用于進程找到其所屬進程組結構 */struct cgroup_subsys_state css;#ifdef CONFIG_FAIR_GROUP_SCHED/* CFS調度器的進程組變量,在 alloc_fair_sched_group() 中進程初始化及分配內存 *//* 該進程組在每個CPU上都有對應的一個調度實體,因為有可能此進程組同時在兩個CPU上運行(它的A進程在CPU0上運行,B進程在CPU1上運行) */struct sched_entity **se;/* 進程組在每個CPU上都有一個CFS運行隊列(為什么需要,稍后解釋) */struct cfs_rq **cfs_rq;/* 用于保存優先級默認為NICE 0的優先級 */unsigned long shares;#ifdef    CONFIG_SMPatomic_long_t load_avg;atomic_t runnable_avg;#endif#endif#ifdef CONFIG_RT_GROUP_SCHED/* 實時進程調度器的進程組變量,同 CFS */struct sched_rt_entity **rt_se;struct rt_rq **rt_rq;struct rt_bandwidth rt_bandwidth;#endifstruct rcu_head rcu;/* 用于建立進程鏈表(屬于此調度組的進程鏈表) */struct list_head list;/* 指向其上層的進程組,每一層的進程組都是它上一層進程組的運行隊列的一個調度實體,在同一層中,進程組和進程被同等對待 */struct task_group *parent;/* 進程組的兄弟結點鏈表 */struct list_head siblings;/* 進程組的兒子結點鏈表 */struct list_head children;#ifdef CONFIG_SCHED_AUTOGROUPstruct autogroup *autogroup;#endifstruct cfs_bandwidth cfs_bandwidth;};

?

?

調度實體(struct sched_entity)

 1 /* 一個調度實體(紅黑樹的一個結點),其包含一組或一個指定的進程,包含一個自己的運行隊列,一個父親指針,一個指向需要調度的運行隊列指針 */2 struct sched_entity {3     /* 權重,在數組prio_to_weight[]包含優先級轉權重的數值 */4     struct load_weight    load;        /* for load-balancing */5     /* 實體在紅黑樹對應的結點信息 */6     struct rb_node        run_node;    7     /* 實體所在的進程組 */8     struct list_head    group_node;9     /* 實體是否處于紅黑樹運行隊列中 */
10     unsigned int        on_rq;
11 
12     /* 開始運行時間 */
13     u64            exec_start;
14     /* 總運行時間 */
15     u64            sum_exec_runtime;
16     /* 虛擬運行時間,在時間中斷或者任務狀態發生改變時會更新
17      * 其會不停增長,增長速度與load權重成反比,load越高,增長速度越慢,就越可能處于紅黑樹最左邊被調度
18      * 每次時鐘中斷都會修改其值
19      * 具體見calc_delta_fair()函數
20      */
21     u64            vruntime;
22     /* 進程在切換進CPU時的sum_exec_runtime值 */
23     u64            prev_sum_exec_runtime;
24 
25     /* 此調度實體中進程移到其他CPU組的數量 */
26     u64            nr_migrations;
27 
28 #ifdef CONFIG_SCHEDSTATS
29     /* 用于統計一些數據 */
30     struct sched_statistics statistics;
31 #endif
32 
33 #ifdef CONFIG_FAIR_GROUP_SCHED
34     /* 代表此進程組的深度,每個進程組都比其parent調度組深度大1 */
35     int            depth;
36     /* 父親調度實體指針,如果是進程則指向其運行隊列的調度實體,如果是進程組則指向其上一個進程組的調度實體
37      * 在 set_task_rq 函數中設置
38      */
39     struct sched_entity    *parent;
40     /* 實體所處紅黑樹運行隊列 */
41     struct cfs_rq        *cfs_rq;        
42     /* 實體的紅黑樹運行隊列,如果為NULL表明其是一個進程,若非NULL表明其是調度組 */
43     struct cfs_rq        *my_q;
44 #endif
45 
46 #ifdef CONFIG_SMP
47     /* Per-entity load-tracking */
48     struct sched_avg    avg;
49 #endif
50 };

?

實際上,紅黑樹是根據 struct?rb_node 建立起關系的,不過 struct rb_node 與 struct sched_entity 是一一對應關系,也可以簡單看為一個紅黑樹結點就是一個調度實體。可以看出,在 struct sched_entity 結構中,包含了一個進程(或進程組)調度的全部數據,其被包含在 struct task_struct 結構中的se中,如下:
 1 struct task_struct {2     ........3     /* 表示是否在運行隊列 */4     int on_rq;5 6     /* 進程優先級 7      * prio: 動態優先級,范圍為100~139,與靜態優先級和補償(bonus)有關8      * static_prio: 靜態優先級,static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)9      * normal_prio: 沒有受優先級繼承影響的常規優先級,具體見normal_prio函數,跟屬于什么類型的進程有關
10      */
11     int prio, static_prio, normal_prio;
12     /* 實時進程優先級 */
13     unsigned int rt_priority;
14     /* 調度類,調度處理函數類 */
15     const struct sched_class *sched_class;
16     /* 調度實體(紅黑樹的一個結點) */
17     struct sched_entity se;
18     /* 調度實體(實時調度使用) */
19     struct sched_rt_entity rt;
20 #ifdef CONFIG_CGROUP_SCHED
21     /* 指向其所在進程組 */
22     struct task_group *sched_task_group;
23 #endif
24     ........
25 }

?

而在 struct task_struct 結構中,我們注意到有個調度類,里面包含的是調度處理函數,它具體如下:
 1 struct sched_class {2     /* 下一優先級的調度類3      * 調度類優先級順序: stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class4      */5     const struct sched_class *next;6 7     /* 將進程加入到運行隊列中,即將調度實體(進程)放入紅黑樹中,并對 nr_running 變量加1 */8     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);9     /* 從運行隊列中刪除進程,并對 nr_running 變量中減1 */
10     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
11     /* 放棄CPU,在 compat_yield sysctl 關閉的情況下,該函數實際上執行先出隊后入隊;在這種情況下,它將調度實體放在紅黑樹的最右端 */
12     void (*yield_task) (struct rq *rq);
13     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
14 
15     /* 檢查當前進程是否可被新進程搶占 */
16     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
17 
18     /*
19      * It is the responsibility of the pick_next_task() method that will
20      * return the next task to call put_prev_task() on the @prev task or
21      * something equivalent.
22      *
23      * May return RETRY_TASK when it finds a higher prio class has runnable
24      * tasks.
25      */
26     /* 選擇下一個應該要運行的進程運行 */
27     struct task_struct * (*pick_next_task) (struct rq *rq,
28                         struct task_struct *prev);
29     /* 將進程放回運行隊列 */
30     void (*put_prev_task) (struct rq *rq, struct task_struct *p);
31 
32 #ifdef CONFIG_SMP
33     /* 為進程選擇一個合適的CPU */
34     int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
35     /* 遷移任務到另一個CPU */
36     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
37     /* 用于上下文切換后 */
38     void (*post_schedule) (struct rq *this_rq);
39     /* 用于進程喚醒 */
40     void (*task_waking) (struct task_struct *task);
41     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
42     /* 修改進程的CPU親和力(affinity) */
43     void (*set_cpus_allowed)(struct task_struct *p,
44                  const struct cpumask *newmask);
45     /* 啟動運行隊列 */
46     void (*rq_online)(struct rq *rq);
47     /* 禁止運行隊列 */
48     void (*rq_offline)(struct rq *rq);
49 #endif
50     /* 當進程改變它的調度類或進程組時被調用 */
51     void (*set_curr_task) (struct rq *rq);
52     /* 該函數通常調用自 time tick 函數;它可能引起進程切換。這將驅動運行時(running)搶占 */
53     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
54     /* 在進程創建時調用,不同調度策略的進程初始化不一樣 */
55     void (*task_fork) (struct task_struct *p);
56     /* 在進程退出時會使用 */
57     void (*task_dead) (struct task_struct *p);
58 
59     /* 用于進程切換 */
60     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
61     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
62     /* 改變優先級 */
63     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
64              int oldprio);
65 
66     unsigned int (*get_rr_interval) (struct rq *rq,
67                      struct task_struct *task);
68 
69     void (*update_curr) (struct rq *rq);
70 
71 #ifdef CONFIG_FAIR_GROUP_SCHED
72     void (*task_move_group) (struct task_struct *p, int on_rq);
73 #endif
74 };

?

這個調度類具體有什么用呢,實際上在內核中不同的調度算法它們的操作都不相同,為了方便修六改、替換調度算法,使用了調度類,每個調度算法只需要實現自己的調度類就可以了,CFS算法有它的調度類,SCHED_FIFO也有它自己的調度類,當一個進程創建時,用什么調度算法就將其 task_struct->sched_class 指向其相應的調度類,調度器每次調度處理時,就通過當前進程的調度類函數進程操作,大大提高了可移植性和易修改性。

?

?六.對操作系統進程模型的看法

?  操作系統Operating System,簡稱OS)是管理和控制計算機硬件軟件資源的計算機程序,是直接運行在“裸機”上的最基本的系統軟件,任何其他軟件都必須在操作系統的支持下才能運行。

操作系統是用戶和計算機的接口,同時也是計算機硬件和其他軟件的接口。操作系統的功能包括管理計算機系統的硬件、軟件及數據資源,控制程序運行,改善人機界面,為其它應用軟件提供支持,讓計算機系統所有資源最大限度地發揮作用,提供各種形式的用戶界面,使用戶有一個好的工作環境,為其它軟件的開發提供必要的服務和相應的接口等。實際上,用戶是不用接觸操作系統的,操作系統管理著計算機硬件資源,同時按照應用程序的資源請求,分配資源,如:劃分CPU時間,內存空間的開辟,調用打印機等。
在進程模型中,計算機上所有可運行的軟件,通常也包括操作系統,被組織成若干順序進程(sequential process),簡稱進程(process)。操作系統中最核心的概念是進程, 進程也是并發程序設計中的一個最重要、 最基本的概念。進程是一個動態的過程, 即進程有生命周期, 它擁有資源, 是程序的執行過程, 其狀態是變化的。?Windows、?unix和Linux是目前最流行的幾個操作系統。

七.參考資料

https://wenku.baidu.com/view/64179a4bcf84b9d528ea7a0a.html
https://www.doc88.com/p-7019532024389.html
http://www.cnblogs.com/tolimit/
https://blog.csdn.net/bit_clearoff/article/details/54292300
https://blog.csdn.net/hgnuxc_1993/article/details/54847732#0-qzone-1-93817-d020d2d2a4e8d1a374a433f596ad1440

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

  

?

?

轉載于:https://www.cnblogs.com/liyuanZhang/p/8971268.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/250374.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/250374.shtml
英文地址,請注明出處:http://en.pswp.cn/news/250374.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于模型驗證那點事兒

今天應笑笑老師之問&#xff0c;做了一個模型驗證的例子&#xff0c;發現之前對這個東西的理解太片面&#xff0c;重新整理了一下思路 字段驗證優先級高于類驗證 什么是類驗證呢&#xff1f;就是兩個字段組合的驗證&#xff0c;比如你Admin不允許修改密碼&#xff0c;你修改密碼…

mongoose --- createUser

說明 源代碼記錄、遺忘回顧mongoDB默認不需要使用賬號密碼即可訪問數據庫.下面是給mongoDB添加超級管理員和普通用戶的方法 以系統管理員的方式運行powershell連接數據庫 mongo查看數據庫: show dbs切換到admin數據庫: use admin創建超級管理員賬戶: db.createUser({user: roo…

Win10安裝MySQL5.7.22 解壓縮版(手動配置)方法

1.下載地址&#xff1a;https://dev.mysql.com/downloads/mysql/5.7.html#downloads 直接點擊下載項 下載后&#xff1a; 2.可以把解壓的內容隨便放到一個目錄&#xff0c;我的是如下目錄&#xff08;放到C盤的話&#xff0c;可能在修改ini文件時涉及權限問題&#xff0c;之后我…

Elemant-UI日期范圍的表單驗證

Form 組件提供了表單驗證的功能&#xff0c;只需要通過 rules 屬性傳入約定的驗證規則&#xff0c;并將 Form-Item 的 prop 屬性設置為需校驗的字段名即可。但是官網的示例只有普通日期類型的驗證&#xff0c;沒有時間范圍的驗證。 一開始&#xff0c;我認為時間時間范圍的是一…

node --- [express項目] 開發環境下使用morgan控制臺輸出訪問信息

說明 源代碼記錄、遺忘回顧 process.env node中提供了一個process.env接口用于訪問計算機中的系統環境變量. 可以利用以上屬性來區分當前的環境是開發環境還是生產環境,代碼如下: if (process.env.NODE_ENV development) {console.log(當前環境是開發環境) } else {consol…

Dynamics CRM 訪問團隊的使用

訪問團隊和負責人團隊的區別是&#xff1a;負責人團隊可以擁有記錄&#xff0c;訪問團隊不能擁有記錄也不能加入解決方案中。 訪問團隊用法1&#xff1a;可以將不同組織的人員加入到訪問組實現數據的更新、刪除、共享 訪問團隊用法2&#xff1a;訪問團隊模板的使用 步驟一&…

業務邏輯

快捷支付接口規范 問題背景 持卡人身份驗證持卡人在發卡銀行提供的身份驗證服務器進行驗證&#xff0c;將結果告知商戶資金清算資金清算在身份驗證通過后進行即時清算&#xff0c;也可能是通過專用資金清算網絡進行傳統方法弊端 持卡人需要訪問很多網站才能完成一次完整支付 &a…

node --- [express] cookie/session 機制與 中間件的使用(路由守衛)

說明 源代碼記憶、遺忘回顧使用 cookie/session 機制,讓 客戶端/服務器 的訪問變得有狀態 cookie 與 session 由于 HTTP 協議的無狀態性,當一次連接斷開后. 服務器并不會記錄用戶是否登錄. 因此需要引入 cookie/session 機制 cookie cookie: 瀏覽器在電腦硬盤中開辟的一塊空…

kprobe原理解析

參考 http://www.cnblogs.com/honpey/p/4575928.html kprobe是linux內核的一個重要特性&#xff0c;是一個輕量級的內核調試工具&#xff0c;同時它又是其他一些更高級的內核調試工具&#xff08;比如perf和systemtap&#xff09;的“基礎設施”&#xff0c;4.0版本的內核中&a…

02 數據類型

轉載于:https://www.cnblogs.com/theoup/p/9875293.html

css --- [學習筆記]背景圖片小結 css三大特性

源代碼 參考 1. 行高(line-height) 目標 理解 - 能說出行高和高度三種關系 - 能簡單理解為什么行高等于單行文字會垂直居應用 使用行高實現單行文字垂直居中能會測量行高 2. CSS 背景(background) 目標 理解 - 背景的作用css 背景圖片和插入圖片的區別 應用 通過 css 背景…

(數據科學學習手札30)樸素貝葉斯分類器的原理詳解Python與R實現

一、簡介 要介紹樸素貝葉斯&#xff08;naive bayes&#xff09;分類器&#xff0c;就不得不先介紹貝葉斯決策論的相關理論&#xff1a; 貝葉斯決策論&#xff08;bayesian decision theory&#xff09;是概率框架下實施決策的基本方法。對分類任務來說&#xff0c;在所有相關概…

【技術累積】【點】【java】【29】MapUtils

內容 是Apache組織下的commons-collections包中的工具類<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version></dependency> Map操作相關的&#xff0c…

css --- [讀書筆記] 盒模型(邊框、內外邊距)

說明 源代碼學習 盒子模型(css重點) css學習三大重點: css盒子模型、 浮動、 定位 目標: 能說出盒子模型由哪四部分組成: 內容、邊框、內外邊距能說出內邊距的作用,設置不同數值分別代表的意思: 控制內部塊級元素和寬框的距離能說出塊級盒子居中對齊需要的2個條件能說出外邊…

Java 泛型,你了解類型擦除嗎?

泛型&#xff0c;一個孤獨的守門者。大家可能會有疑問&#xff0c;我為什么叫做泛型是一個守門者。這其實是我個人的看法而已&#xff0c;我的意思是說泛型沒有其看起來那么深不可測&#xff0c;它并不神秘與神奇。泛型是 Java 中一個很小巧的概念&#xff0c;但同時也是一個很…

css --- [讀書筆記] 浮動(float) 與 清除浮動

說明 源代碼學習 1. 浮動 1.1 CSS布局的三種機制 網頁布局的核心 — 利用 CSS 來擺放盒子 CSS提供了3種機制來設置盒子的擺放位置: 標準流、浮動和定位. 標準流: 塊級元素(div、hr、p、h1~h6、ul、ol、dl、form、table)會獨占一行,從上向下順序排列行內元素(span、a、i、em)…

Shiro身份認證---轉

目錄1.Shro的概念2.Shiro的簡單身份認證實現3.Shiro與spring對身份認證的實現前言&#xff1a; Shiro 可以非常容易的開發出足夠好的應用&#xff0c;其不僅可以用在 JavaSE 環境&#xff0c;也可以用在 JavaEE 環境。Shiro 可以幫助我們完成&#xff1a;認證、授權、加密、會話…

模板 Trie樹

模板 Trie樹 code&#xff1a; #include <iostream> #include <cstdio>using namespace std;const int wx20017;inline int read(){int sum0,f1; char chgetchar();while(ch<0||ch>9){if(ch-)f-1; chgetchar();}while(ch>0&&ch<9){sum(sum<…

css --- [練手小項目]樣式小結(字體、顏色的語義 清除浮動的使用)

說明 源代碼 1.1 CSS屬性書寫順序(重點) 建議遵循以下順序: 1.布局定位屬性: display / position / float / clear / visibility / overflow (建議display第一個寫, 畢竟關系到模式) 2.自身屬性: width / height / margin / padding / border / background 3.文本屬性: co…

《Hive編程指南》14.3 投影變換的實踐出錯原因分析

自己在學習14.3節投影變換執行SQL語句hive (default)> SELECT TRANSFORM(col1, col2) USING /bin/cut -f1 AS newA, newB FROM a;時出現了這個錯誤 Ended Job job_local1231989520_0004 with errors Error during job, obtaining debugging information... FAILED: Executi…