Linux进程管理和调度-基于linux3.10

在linux操作系统的主要目的是管理和分配硬件资源并为应用层提供一个良好的抽象接口。不论是内存管理子系统还是文件子系统亦或是网络子系统等都是为应用程序提供服务的,而应用程序映射到linux内核中称之为进程,由性能原因多个进程可能会被简化并组织为若干线程。

Linux进程可以把linux内核的其它子系统串接在一起,其和linux内核各个子系统均有联系,本文着重点就是梳理linux进程和各个子系统之间的关系,把握linux的脉络。还是基于以前分析的linux版本3.10

进程表示

Linux中进程表示的数据结构是task_struct,该结构是如此之重要,所以这里不得一行不落的抄在这里。这个结构体有点长,就来细细的品吧。

<include/linux/sched.h>
1034 struct task_struct {
1035     volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
1036     void *stack;
1037     atomic_t usage;
1038     unsigned int flags; /* per process flags, defined below */
1039     unsigned int ptrace;
1040 
1041 #ifdef CONFIG_SMP
1042     struct llist_node wake_entry;
1043     int on_cpu;
1044 #endif
1045     int on_rq;
1046 
1047     int prio, static_prio, normal_prio;
1048     unsigned int rt_priority;
1049     const struct sched_class *sched_class;
1050     struct sched_entity se;
1051     struct sched_rt_entity rt;
1052 #ifdef CONFIG_CGROUP_SCHED
1053     struct task_group *sched_task_group;
1054 #endif
1055 
1056 #ifdef CONFIG_PREEMPT_NOTIFIERS
1057     /* 抢占情况下的通知链,哈希链*/
1058     struct hlist_head preempt_notifiers;
1059 #endif
1060 
1061     /*
1062      * fpu_counter contains the number of consecutive context switches
1063      * that the FPU is used. If this is over a threshold, the lazy fpu
1064      * saving becomes unlazy to save the trap. This is an unsigned char
1065      * so that after 256 times the counter wraps and the behavior turns
1066      * lazy again; this to deal with bursty apps that only use FPU for
1067      * a short time
1068      */
1069     unsigned char fpu_counter;
1070 #ifdef CONFIG_BLK_DEV_IO_TRACE
1071     unsigned int btrace_seq;
1072 #endif
1073 
1074     unsigned int policy;
1075     int nr_cpus_allowed;
1076     cpumask_t cpus_allowed;
1077 
1078 #ifdef CONFIG_PREEMPT_RCU
1079     int rcu_read_lock_nesting;
1080     char rcu_read_unlock_special;
1081     struct list_head rcu_node_entry;
1082 #endif /* #ifdef CONFIG_PREEMPT_RCU */
1083 #ifdef CONFIG_TREE_PREEMPT_RCU
1084     struct rcu_node *rcu_blocked_node;
1085 #endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
1086 #ifdef CONFIG_RCU_BOOST
1087     struct rt_mutex *rcu_boost_mutex;
1088 #endif /* #ifdef CONFIG_RCU_BOOST */
1089 
1090 #if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
1091     struct sched_info sched_info;
1092 #endif
1093 
1094     struct list_head tasks;
1095 #ifdef CONFIG_SMP
1096     struct plist_node pushable_tasks;
1097 #endif
1098 
1099     struct mm_struct *mm, *active_mm;
1100 #ifdef CONFIG_COMPAT_BRK
1101     unsigned brk_randomized:1;
1102 #endif
1103 #if defined(SPLIT_RSS_COUNTING)
1104     struct task_rss_stat    rss_stat;
1105 #endif
1106 /* task state */
1107     int exit_state;
1108     int exit_code, exit_signal;
1109     int pdeath_signal;  /*  The signal sent when the parent dies  */
1110     unsigned int jobctl;    /* JOBCTL_*, siglock protected */
1111 
1112     /* Used for emulating ABI behavior of previous Linux versions */
1113     unsigned int personality;
1114 
1115     unsigned did_exec:1;
1116     unsigned in_execve:1;   /* Tell the LSMs that the process is doing an
1117                  * execve */
1118     unsigned in_iowait:1;
1119 
1120     /* task may not gain privileges */
1121     unsigned no_new_privs:1;
1122 
1123     /* Revert to default priority/policy when forking */
1124     unsigned sched_reset_on_fork:1;
1125     unsigned sched_contributes_to_load:1;
1126 
1127     pid_t pid;
1128     pid_t tgid;
1129 
1130 #ifdef CONFIG_CC_STACKPROTECTOR
1131     /* Canary value for the -fstack-protector gcc feature */
1132     unsigned long stack_canary;
1133 #endif
1134     /*
1135      * pointers to (original) parent process, youngest child, younger sibling,
1136      * older sibling, respectively.  (p->father can be replaced with
1137      * p->real_parent->pid)
1138      */
1139     struct task_struct __rcu *real_parent; /* real parent process */
1140     struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
1141     /*
1142      * children/sibling forms the list of my natural children
1143      */
1144     struct list_head children;  /* list of my children */
1145     struct list_head sibling;   /* linkage in my parent's children list */
1146     struct task_struct *group_leader;   /* threadgroup leader */
1147 
1148     /*
1149      * ptraced is the list of tasks this task is using ptrace on.
1150      * This includes both natural children and PTRACE_ATTACH targets.
1151      * p->ptrace_entry is p's link on the p->parent->ptraced list.
1152      */
1153     struct list_head ptraced;
1154     struct list_head ptrace_entry;
1155 
1156     /* PID/PID hash table linkage. */
1157     struct pid_link pids[PIDTYPE_MAX];
1158     struct list_head thread_group;
1159 
1160     struct completion *vfork_done;      /* for vfork() */
1161     int __user *set_child_tid;      /* CLONE_CHILD_SETTID */
1162     int __user *clear_child_tid;        /* CLONE_CHILD_CLEARTID */
1163 
1164     cputime_t utime, stime, utimescaled, stimescaled;
1165     cputime_t gtime;
1166 #ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
1167     struct cputime prev_cputime;
1168 #endif
1169 #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
1170     seqlock_t vtime_seqlock;
1171     unsigned long long vtime_snap;
1172     enum {
1173         VTIME_SLEEPING = 0,
1174         VTIME_USER,
1175         VTIME_SYS,
1176     } vtime_snap_whence;
1177 #endif
1178     unsigned long nvcsw, nivcsw; /*进程切换计数 */
1179     struct timespec start_time;         /* monotonic time */
1180     struct timespec real_start_time;    /*启动以来的时间 */
1181 /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
1182     unsigned long min_flt, maj_flt;
1183 
1184     struct task_cputime cputime_expires;
1185     struct list_head cpu_timers[3];
1186 
1187 /* process credentials */
1188     const struct cred __rcu *real_cred; /* objective and real subjective task
1189                      * credentials (COW) */
1190     const struct cred __rcu *cred;  /* effective (overridable) subjective task
1191                      * credentials (COW) */
1192     char comm[TASK_COMM_LEN]; /* executable name excluding path
1193                      - access with [gs]et_task_comm (which lock
1194                        it with task_lock())
1195                      - initialized normally by setup_new_exec */
1196 /* file system info */
1197     int link_count, total_link_count;
1198 #ifdef CONFIG_SYSVIPC
1199 /* ipc stuff */
1200     struct sysv_sem sysvsem;
1201 #endif
1202 #ifdef CONFIG_DETECT_HUNG_TASK
1203 /* hung task detection */
1204     unsigned long last_switch_count;
1205 #endif
1206 /* CPU-specific state of this task */
1207     struct thread_struct thread;
1208 /* filesystem information */
1209     struct fs_struct *fs;
1210 /* open file information */
1211     struct files_struct *files;
1212 /* namespaces */
1213     struct nsproxy *nsproxy;
1214 /* signal handlers */
1215     struct signal_struct *signal;
1216     struct sighand_struct *sighand;
1217 
1218     sigset_t blocked, real_blocked;
1219     sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
1220     struct sigpending pending;
1221 
1222     unsigned long sas_ss_sp;
1223     size_t sas_ss_size;
1224     int (*notifier)(void *priv);
1225     void *notifier_data;
1226     sigset_t *notifier_mask;
1227     struct callback_head *task_works;
1228 
1229     struct audit_context *audit_context;
1230 #ifdef CONFIG_AUDITSYSCALL
1231     kuid_t loginuid;
1232     unsigned int sessionid;
1233 #endif
1234     struct seccomp seccomp;
1235 
1236 /* Thread group tracking */
1237     u32 parent_exec_id;
1238     u32 self_exec_id;
1239 /* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,
1240  * mempolicy */
1241     spinlock_t alloc_lock;
1242 
1243     /* Protection of the PI data structures: */
1244     raw_spinlock_t pi_lock;
1245 
1246 #ifdef CONFIG_RT_MUTEXES
1247     /* PI waiters blocked on a rt_mutex held by this task */
1248     struct plist_head pi_waiters;
1249     /* Deadlock detection and priority inheritance handling */
1250     struct rt_mutex_waiter *pi_blocked_on;
1251 #endif
1252 
1253 #ifdef CONFIG_DEBUG_MUTEXES
1254     /* mutex deadlock detection */
1255     struct mutex_waiter *blocked_on;
1256 #endif
1257 #ifdef CONFIG_TRACE_IRQFLAGS
1258     unsigned int irq_events;
1259     unsigned long hardirq_enable_ip;
1260     unsigned long hardirq_disable_ip;
1261     unsigned int hardirq_enable_event;
1262     unsigned int hardirq_disable_event;
1263     int hardirqs_enabled;
1264     int hardirq_context;
1265     unsigned long softirq_disable_ip;
1266     unsigned long softirq_enable_ip;
1267     unsigned int softirq_disable_event;
1268     unsigned int softirq_enable_event;
1269     int softirqs_enabled;
1270     int softirq_context;
1271 #endif
1272 #ifdef CONFIG_LOCKDEP
1273 # define MAX_LOCK_DEPTH 48UL
1274     u64 curr_chain_key;
1275     int lockdep_depth;
1276     unsigned int lockdep_recursion;
1277     struct held_lock held_locks[MAX_LOCK_DEPTH];
1278     gfp_t lockdep_reclaim_gfp;
1279 #endif
1280 
1281 /* journalling filesystem info */
1282     void *journal_info;
1283 
1284 /* stacked block device info */
1285     struct bio_list *bio_list;
1286 
1287 #ifdef CONFIG_BLOCK
1288 /* stack plugging */
1289     struct blk_plug *plug;
1290 #endif
1291 
1292 /* VM state */
1293     struct reclaim_state *reclaim_state;
1294 
1295     struct backing_dev_info *backing_dev_info;
1296 
1297     struct io_context *io_context;
1298 
1299     unsigned long ptrace_message;
1300     siginfo_t *last_siginfo; /* For ptrace use.  */
1301     struct task_io_accounting ioac;
1302 #if defined(CONFIG_TASK_XACCT)
1303     u64 acct_rss_mem1;  /* accumulated rss usage */
1304     u64 acct_vm_mem1;   /* accumulated virtual memory usage */
1305     cputime_t acct_timexpd; /* stime + utime since last update */
1306 #endif
1307 #ifdef CONFIG_CPUSETS
1308     nodemask_t mems_allowed;    /* Protected by alloc_lock */
1309     seqcount_t mems_allowed_seq;    /* Seqence no to catch updates */
1310     int cpuset_mem_spread_rotor;
1311     int cpuset_slab_spread_rotor;
1312 #endif
1313 #ifdef CONFIG_CGROUPS
1314     /* Control Group info protected by css_set_lock */
1315     struct css_set __rcu *cgroups;
1316     /* cg_list protected by css_set_lock and tsk->alloc_lock */
1317     struct list_head cg_list;
1318 #endif
1319 #ifdef CONFIG_FUTEX
1320     struct robust_list_head __user *robust_list;
1321 #ifdef CONFIG_COMPAT
1322     struct compat_robust_list_head __user *compat_robust_list;
1323 #endif
1324     struct list_head pi_state_list;
1325     struct futex_pi_state *pi_state_cache;
1326 #endif
1327 #ifdef CONFIG_PERF_EVENTS
1328     struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];
1329     struct mutex perf_event_mutex;
1330     struct list_head perf_event_list;
1331 #endif
1332 #ifdef CONFIG_NUMA
1333     struct mempolicy *mempolicy;    /* Protected by alloc_lock */
1334     short il_next;
1335     short pref_node_fork;
1336 #endif
1337 #ifdef CONFIG_NUMA_BALANCING
1338     int numa_scan_seq;
1339     int numa_migrate_seq;
1340     unsigned int numa_scan_period;
1341     u64 node_stamp;         /* migration stamp  */
1342     struct callback_head numa_work;
1343 #endif /* CONFIG_NUMA_BALANCING */
1344 
1345     struct rcu_head rcu;
1346 
1347     /*
1348      * cache last used pipe for splice
1349      */
1350     struct pipe_inode_info *splice_pipe;
1351 
1352     struct page_frag task_frag;
1353 
1354 #ifdef  CONFIG_TASK_DELAY_ACCT
1355     struct task_delay_info *delays;
1356 #endif
1357 #ifdef CONFIG_FAULT_INJECTION
1358     int make_it_fail;
1359 #endif
1360     /*
1361      * when (nr_dirtied >= nr_dirtied_pause), it's time to call
1362      * balance_dirty_pages() for some dirty throttling pause
1363      */
1364     int nr_dirtied;
1365     int nr_dirtied_pause;
1366     unsigned long dirty_paused_when; /* start of a write-and-pause period */
1367 
1368 #ifdef CONFIG_LATENCYTOP
1369     int latency_record_count;
1370     struct latency_record latency_record[LT_SAVECOUNT];
1371 #endif
1372     /*
1373      * time slack values; these are used to round up poll() and
1374      * select() etc timeout values. These are in nanoseconds.
1375      */
1376     unsigned long timer_slack_ns;
1377     unsigned long default_timer_slack_ns;
1378 
1379 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
1380     /* Index of current stored address in ret_stack */
1381     int curr_ret_stack;
1382     /* Stack of return addresses for return function tracing */
1383     struct ftrace_ret_stack *ret_stack;
1384     /* time stamp for last schedule */
1385     unsigned long long ftrace_timestamp;
1386     /*
1387      * Number of functions that haven't been traced
1388      * because of depth overrun.
1389      */
1390     atomic_t trace_overrun;
1391     /* Pause for the tracing */
1392     atomic_t tracing_graph_pause;
1393 #endif
1394 #ifdef CONFIG_TRACING
1395     /* state flags for use by tracers */
1396     unsigned long trace;
1397     /* bitmask and counter of trace recursion */
1398     unsigned long trace_recursion;
1399 #endif /* CONFIG_TRACING */
1400 #ifdef CONFIG_MEMCG /* memcg uses this to do batch job */
1401     struct memcg_batch_info {
1402         int do_batch;   /* incremented when batch uncharge started */
1403         struct mem_cgroup *memcg; /* target memcg of uncharge */
1404         unsigned long nr_pages; /* uncharged usage */
1405         unsigned long memsw_nr_pages; /* uncharged mem+swap usage */
1406     } memcg_batch;
1407     unsigned int memcg_kmem_skip_account;
1408 #endif
1409 #ifdef CONFIG_HAVE_HW_BREAKPOINT
1410     atomic_t ptrace_bp_refcnt;
1411 #endif
1412 #ifdef CONFIG_UPROBES
1413     struct uprobe_task *utask;
1414 #endif
1415 #if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE)
1416     unsigned int    sequential_io;
1417     unsigned int    sequential_io_avg;
1418 #endif
1419 };

进程状态

<include/linux/sched.h>
//进程正在运行,current指针指向的内容
#define TASK_RUNNING		0  
//分别是可中断和不可中断的等待状态,这类进程通常在资源或者某个事件发生
#define TASK_INTERRUPTIBLE	1
#define TASK_UNINTERRUPTIBLE	2
//进程暂停,SIGSTOP、SIGTSIP、SIGTTIN、SIGTTOUT这类信号会触发进程到这一状态
#define __TASK_STOPPED		4
//不属于进程状态,用于从停止的进程中,将当前被调试的那些与常规的进程区分出来。
#define __TASK_TRACED		8
/* in tsk->exit_state */
//1107可能的状态,僵死状态,进程已终止,但是父进程没有执行wait()系统调用,终止进程的信息也没有回收。
#define EXIT_ZOMBIE		16
//wait()系统调用已经发出,而进程完全从系统移除之前的状态。
#define EXIT_DEAD		32
/* in tsk->state again */
#define TASK_DEAD		64
#define TASK_WAKEKILL		128
#define TASK_WAKING		256
#define TASK_PARKED		512
#define TASK_STATE_MAX		1024

1108, exit_code进程退出码,exit_signal进程退出时发送的信号量,比如如果一个进程的退出引起一个进程组成为孤儿进程,则一个SIGUP信号将被发送给进程组。

1109父进程终止时向子进程发送的信号量。

1110作业控制,其可选值定义于include/linux/sched.h的1691~1710行

进程调度信息

1047~1051行,1074~1076

         prio当前任务的动态优先级,其值影响任务的调度顺序

         normal_prio任务的常规优先级,基于static_prio和调度策略计算

         static_prio静态优先级,在进程创建时分配,该值会影响分配给任务的时间片的长短和非实时任务的动态优先级的计算。

         rt_priority实时优先级,0表示非实时任务,[1,99]表示实时任务,值越大则优先级越高。

sched_class调度类,该调度类支持的操作函数集。

se调度实体,对单个任务或任务组进行调度。

Policy调度策略。有以下四种:

#define SCHED_NORMAL		0
#define SCHED_FIFO		1
#define SCHED_RR		2
#define SCHED_BATCH		3

nr_cpus_allowed多核情况下,允许最多在几个核上运行。

cpus_allowed限制其能在运行的CPU。

进程管理

1094~1094行

Tasks所有进程均串接在该链表上。

pushable_tasks  SMP实时调度管理per-CPU任务。

内存

1099~1105行

1099描述进程内存分布结构体,text段,data段,堆均位于此。

1101启用随机分配内存基地址标志,

1104,rss_stat线程缓存的信息

ID管理

1127~1128行

Pid进程ID,tgid线程组ID

进程创建接口

fork,创建子进程,父进程的所有资源以适当方式复制到子进程。为减少与调用相关的工作量,使用写时复制技术(copy-on-write)

vfork类似fork,但是共享父进程的数据,COW技术使该技术不再具有优势。

Exec从一个可执行的二进制文件加载另一个程序来代替当前的进程。

Clone创建linux线程库。对父子之间的资源进行精确的控制。

do_fork

Do_fork函数复制进程,如果复制成功会启动新进程并等待其完成。

<kernel/fork.c>
1563 long do_fork(unsigned long clone_flags,
1564           unsigned long stack_start,
1565           unsigned long stack_size,
1566           int __user *parent_tidptr,
1567           int __user *child_tidptr)

以上是do_fork函数的定义,之所以单独列出来是因为该函数,clone_flags是父子进程复制属性控制。可选的参数如下,这些flag的标志后面都有对其意义的注释:

#define CSIGNAL		0x000000ff	/* signal mask to be sent at exit */
#define CLONE_VM	0x00000100	/* set if VM shared between processes */
#define CLONE_FS	0x00000200	/* set if fs info shared between processes */
#define CLONE_FILES	0x00000400	/* set if open files shared between processes */
#define CLONE_SIGHAND	0x00000800	/* set if signal handlers and blocked signals shared */
#define CLONE_PTRACE	0x00002000	/* set if we want to let tracing continue on the child too */
#define CLONE_VFORK		0x00004000/* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT	0x00008000	/* set if we want to have the same parent as the cloner */
#define CLONE_THREAD	0x00010000	/* Same thread group? */
#define CLONE_NEWNS	0x00020000	/* New namespace group? */
#define CLONE_SYSVSEM	0x00040000	/* share system V SEM_UNDO semantics */
#define CLONE_SETTLS	0x00080000	/* create a new TLS for the child */
#define CLONE_PARENT_SETTID	0x00100000	/* set the TID in the parent */
#define CLONE_CHILD_CLEARTID	0x00200000	/* clear the TID in the child */
#define CLONE_DETACHED		0x00400000	/* Unused, ignored */
#define CLONE_UNTRACED	0x00800000/* set if the tracing process can't force CLONE_PTRACE on this clone */
#define CLONE_CHILD_SETTID	0x01000000	/* set the TID in the child */
/* 0x02000000 was previously the unused CLONE_STOPPED (Start in stopped state)
   and is now available for re-use. */
#define CLONE_NEWUTS		0x04000000	/* New utsname group? */
#define CLONE_NEWIPC		0x08000000	/* New ipcs */
#define CLONE_NEWUSER		0x10000000	/* New user namespace */
#define CLONE_NEWPID		0x20000000	/* New pid namespace */
#define CLONE_NEWNET		0x40000000	/* New network namespace */
#define CLONE_IO			0x80000000	/* Clone io context */

以上是do_fork函数的定义,之所以单独列出来是因为该函数,clone_flags是父子进程复制属性控制。可选的参数如下,这些flag的标志后面都有对其意义的注释:

Stack_start是用户态下栈的起始地址。

Regs是指向寄存器集合的指针,存放了调用参数。

Stack_size是用户态下栈大小。

parent_tidptr和child_tidptr分别指向用户空间的父子进程的PID。

线程和进程创建的系统调用接口如下:

1641 pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
1642 {
1643     return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
1644         (unsigned long)arg, NULL, NULL);
1645 }
1646 
1647 #ifdef __ARCH_WANT_SYS_FORK
1648 SYSCALL_DEFINE0(fork)
1649 {
1650 #ifdef CONFIG_MMU
1651     return do_fork(SIGCHLD, 0, 0, NULL, NULL);
1652 #else
1653     /* can not support in nommu mode */
1654     return(-EINVAL);
1655 #endif
1656 }
1657 #endif
1667 #ifdef __ARCH_WANT_SYS_CLONE
1668 #ifdef CONFIG_CLONE_BACKWARDS
1669 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
1670          int __user *, parent_tidptr,
1671          int, tls_val,
1672          int __user *, child_tidptr)
1673 #elif defined(CONFIG_CLONE_BACKWARDS2)
1674 SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
1675          int __user *, parent_tidptr,
1676          int __user *, child_tidptr,
1677          int, tls_val)
1678 #else
1679 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
1680          int __user *, parent_tidptr,
1681          int __user *, child_tidptr,
1682          int, tls_val)
1683 #endif
1684 {
1685     return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
1686 }
1687 #endif

上述函数进程/线程的创建归结为对do_fork的调用。如果do_fork成功了,则返回进程的PID,否则返回错误码。创建一个新进程和以往相比麻烦的地方是对命名空间的处理。由于fork会返回新进程的PID,如果创建了新的PID命名空间,则会调用task_pid_nr_ns获取在父命名空间中为新进程选择的PID,即发出fork调用的那个命名空间。如果没有创建新的PID命名空间,则直接调用task_pid_vnr获取局部的PID返回就可以了。

图1.2.1do_fork函数调用流程

         该函数首先调用copy_process执行新进程的创建工作,其返回值就是一个task_struct类型的指针,这里创建的意义是能复制,尽量复用父进程的task_struct是创建的精髓,在创建新的网络命名空间也是这么一个意义。

         copy_process的第一个参数控制复制内容,主要对命名空间、线程、资源共享的处理。最后一个参数trace用于指示是否启用对进程监控的功能。copy_process完成了do_fork的大部分工作。函数的执行过程如下。


图1.2.2 copy_process函数执行过程

<kernel/fork.c>
1132 static struct task_struct *copy_process(unsigned long clone_flags,
1133                     unsigned long stack_start,
1134                     unsigned long stack_size,
1135                     int __user *child_tidptr,
1136                     struct pid *pid,
1137                     int trace)

对flags和安全的合法性检查这里就跳过了,current是当前进程的task_struct结构体指针,这里对其进行复制。

1186     p = dup_task_struct(current);

dup_task_struct的主要完成task_struct和thread_info内存分配和thread_info的拷贝工作。

图1.2.3 dup_task_struct函数主要工作

         为task_struct和thread_info分配内存不是本文重点,有兴趣可以参考《内存管理-之内核内存管理-基于linux3.10》,剩下的是拷贝fpu内容和thread_info内容。这里总结一下该函数主要完成的工作:

1、这里fpu和thread_info的内容拷贝完毕了。

2、如果设置了随机分配栈起始,还会调用get_random_int函数设置task_struct的stack_canary值。

3、将usage成员设置成2,当前创建的进程正在使用,另外releas_task函数也会使用。

4、在复制完成之后,该函数会检查打开的进程数是否超限,如果超限则放弃创建进程,除非当前用户是root用户或者分配了CAP_SYS_RESOURCE或CAP_SYS_ADMIN权限。

1200     if (atomic_read(&p->real_cred->user->processes) >=
1201             task_rlimit(p, RLIMIT_NPROC)) {
1202         if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
1203             p->real_cred->user != INIT_USER)
1204             goto bad_fork_free;
1205     }

如果资源限制检查通过,则会初始化task_struct的一些成员,

…
p->did_exec = 0;
	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
	copy_flags(clone_flags, p);
	INIT_LIST_HEAD(&p->children);
	INIT_LIST_HEAD(&p->sibling);
	rcu_copy_process(p);
	p->vfork_done = NULL;
	spin_lock_init(&p->alloc_lock);
…

接着调用sched_fork执行一些调度相关的设置,这包括将进程设置为TASK_RUNNING状态,为进程选择合适的调度类型(实时、CFS)以及优先级。即将该进程分配给一个CPU(单核还是SMP情况)。

shed_fork首先调用_sched_frok对调度实体和进行相应的初始化,该函数还根据编译配置选项进行适当的初始化,这些编译选项包括调度统计、调度组、抢占通知函数、NUMA是否启动等。调度实体的初始化如下:

        p->se.on_rq			= 0;
	p->se.exec_start		= 0;
	p->se.sum_exec_runtime		= 0;
	p->se.prev_sum_exec_runtime	= 0;
	p->se.nr_migrations		= 0;
	p->se.vruntime			= 0;
	INIT_LIST_HEAD(&p->se.group_node);

上述的工作完成之后,就将进程的状态设置成TASK_RUNNING,

p->state = TASK_RUNNING;

同时将父进程的优先传递给子进程。

p->prio = current->normal_prio;

接下来判断子进程是否采用了实时调度方法,如果非实时调度方法,则将其调度类成员设置成公平调度类。

if (!rt_prio(p->prio))
				p->sched_class = &fair_sched_class;

接着使用RCU安全方式设置进程所在的CPU,这是由set_task_cpu完成的,在SMP情况下,这有点复杂,对于单核情况下,该函数就是空的。

        raw_spin_lock_irqsave(&p->pi_lock, flags);
	set_task_cpu(p, cpu);
	raw_spin_unlock_irqrestore(&p->pi_lock, flags);

set_task_cpu要判断的第一件事就是新创建的进程是否和父进程在同一个CPU核上,如果不在同一个核上,则说明发生了任务迁移(从一个核到另一个核),发生了迁移则需要设置迁移通知链并且需要将创建进程所在的CPU核号进行更新。

接下会调用若干的copy函数来复制或者共享父进程所拥有的一些内核子系统资源。就以一个进程所拥有的文件资源为例说明复制和拥有的差异。

931 static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
 932 {
 933     struct files_struct *oldf, *newf;
 934     int error = 0;
 935 
 936     /*
 937      * A background process may not have any files ...
 938      */
 939     oldf = current->files;
 940     if (!oldf)
 941         goto out;
 942 
 943     if (clone_flags & CLONE_FILES) {
 944         atomic_inc(&oldf->count);
 945         goto out;
 946     }
 947 
 948     newf = dup_fd(oldf, &error);
 949     if (!newf)
 950         goto out;
 951 
 952     tsk->files = newf;
 953     error = 0;
 954 out:
 955     return error;
 956 }

939行判断进程是否拥有文件,对于后台进程是不需要的,如果设置了CLONE_FILES,则是共享文件,这是简单将父进程的文件引用计数加1就可以了,如果没有设置CLONE_FILES标志,则会调用dup_fd复制父进程所有的文件。其它资源的处理方法类似。这些flags在1.2.1节的开始就列出来了。

copy_namespaces有些特殊,其不是对父进程资源进行控制,而是根据flags表示为子进程选择是否创建相应的命名空间,可以参考《linux namespace-之使用》《网络命名空间(内核源码实现)--基于Linux3.10

copy_thread的语义就更特殊一些了,其是依赖于CPU架构的,复制和包含一些线程数据,这些数据比较偏寄存器级。

接下来分配structpid实例,这个参数是copy_process函数的倒数第二个参数,如果创建的不是init的进程,则需要在进程的命名空间中分配一个pid实例。

1350     if (pid != &init_struct_pid) {
1351         retval = -ENOMEM;
1352         pid = alloc_pid(p->nsproxy->pid_ns);
1353         if (!pid)
1354             goto bad_fork_cleanup_io;
1355     }

关于启动一个新程序execve系统调用,参考《linux应用程序如何运行

调度器实现

Linux采用红黑树和虚拟时钟技术来实现调度器的管理,所有进程按时间在一个红黑树中排序,等待CPU时间最长的进程是树的最左下侧的那个节点。调度的方式有两种一种是进程自动放弃,另一种是周期性调度。

Task_struct中和调度有关的成员如下:

struct task_struct {
	int prio, static_prio, normal_prio;
	unsigned int rt_priority;
	const struct sched_class *sched_class;
	struct sched_entity se;
	struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
	struct task_group *sched_task_group;
#endif

unsigned int policy;
	int nr_cpus_allowed;
	cpumask_t cpus_allowed;
}

prio,static_prio, normal_prio分别用于表示动态优先级、静态优先级和动态优先级。静态优先级在进程启动时分配。可以通过nice和sched_setscheduler系统调用修改。normal_prio则是基于静态优先级和调度策略计算而得的动态优先级。Prio是任务当前的动态优先级,其值影响调度顺序。

rt_priority是实时调度优先级,为0则表示非实时,[1,99]表示实时优先级,值越大优先级越高。

sched_class所属调度器类。

Se是调度实体,CFS(completelyfair scheduler)使用。

Rt 实时调度实体。

sched_task_group,进程调度组,这些组里的进程具有同样的调度优先级。

Policy调度策略。可选值如下:

#define SCHED_NORMAL		0//CFS使用,默认
#define SCHED_FIFO		1//实时进程先进先出调度
#define SCHED_RR		2//实时进程轮询调度
#define SCHED_BATCH		3//CFS使用,批处理情况使用。

nr_cpus_allowed该进程可以在多少个CPU上运行。

cpus_allowed该进程可以在那些进程上运行的掩码,这里举个例子,说明这一字段的意义,一个CPU内有8个核,核0正在跑一个应用程序,该应用程序正在等待网络传递的视频数据,加入这时核号为3的收到了该数据,这时需要将数据拷贝到0核cache或者将进程切换到核号为3的那个核上,但是想一想,这样开销必然大,有没有办提高效率呢,有其中一种办法就是将接收网络视频数据的进程限制在0号核上,这时接收到的数据cache一致性将非常。

调度类

调度类提供了通用调度器和各个调度方法之间的关联。

<kernel/sched/sched.h>
struct sched_class {
	const struct sched_class *next;

	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
	void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
	void (*yield_task) (struct rq *rq);
	bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);

	void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);

	struct task_struct * (*pick_next_task) (struct rq *rq);
	void (*put_prev_task) (struct rq *rq, struct task_struct *p);

#ifdef CONFIG_SMP
	int  (*select_task_rq)(struct task_struct *p, int sd_flag, int flags);
	void (*migrate_task_rq)(struct task_struct *p, int next_cpu);

	void (*pre_schedule) (struct rq *this_rq, struct task_struct *task);
	void (*post_schedule) (struct rq *this_rq);
	void (*task_waking) (struct task_struct *task);
	void (*task_woken) (struct rq *this_rq, struct task_struct *task);

	void (*set_cpus_allowed)(struct task_struct *p,
				 const struct cpumask *newmask);

	void (*rq_online)(struct rq *rq);
	void (*rq_offline)(struct rq *rq);
#endif

	void (*set_curr_task) (struct rq *rq);
	void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
	void (*task_fork) (struct task_struct *p);

	void (*switched_from) (struct rq *this_rq, struct task_struct *task);
	void (*switched_to) (struct rq *this_rq, struct task_struct *task);
	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
			     int oldprio);

	unsigned int (*get_rr_interval) (struct rq *rq,
					 struct task_struct *task);

#ifdef CONFIG_FAIR_GROUP_SCHED
	void (*task_move_group) (struct task_struct *p, int on_rq);
#endif
};

不论是实时调度类还是非实时调度类,层次上它们位于同一层,但是实时进程会在完全公平进程之前被处理。其next成员将不同调度类的sched_class实例串接起来。调度类各个方法还是比较明显的。

enqueuer_task和dequeue_task分别向就绪队列添加、删除一个新进程。

yield_task和yield_to_task都是自主放弃CPU控制权,但是yield_to_task是将CPU控制权移交给特定的CPU。

check_preempt_curr唤醒新进程来强制当前进程,wake_up_new_task会调用更改函数。

pick_next_task选择下一个将要运行的进程,在当前进程由其它进程替换之前put_prev_task则被调用。

set_curr_task调度策略改变时会调用。

task_tick每次周期性调度器被激活时会调用该函数。

调度实体

972 struct sched_entity {
 973     struct load_weight  load;       /* for load-balancing */
 974     struct rb_node      run_node;
 975     struct list_head    group_node;
 976     unsigned int        on_rq;
 977 
 978     u64         exec_start;
 979     u64         sum_exec_runtime;
 980     u64         vruntime;
 981     u64         prev_sum_exec_runtime;
 982 
 983     u64         nr_migrations;
 984 
 985 #ifdef CONFIG_SCHEDSTATS
 986     struct sched_statistics statistics;
 987 #endif
 988 
 989 #ifdef CONFIG_FAIR_GROUP_SCHED
 990     struct sched_entity *parent;
 991     /* rq on which this entity is (to be) queued: */
 992     struct cfs_rq       *cfs_rq;
 993     /* rq "owned" by this entity/group: */
 994     struct cfs_rq       *my_q;
 995 #endif
 996 
 997 /*
 998  * Load-tracking only depends on SMP, FAIR_GROUP_SCHED dependency below may be
 999  * removed when useful for applications beyond shares distribution (e.g.
1000  * load-balance).
1001  */
1002 #if defined(CONFIG_SMP) && defined(CONFIG_FAIR_GROUP_SCHED)
1003     /* Per-entity load-tracking */
1004     struct sched_avg    avg;
1005 #endif
1006 };

load为每一个调度实体确定了一个权重,该权重决定了各个实体占队列总负荷的比例。

run_node标准的树节点,允许调度实体存储在一个红黑树上。

On_rq指明该实体是否在一个调度队列上。

exec_start、sum_exec_runtime用于记录消耗的CPU时间,完全公平调度器使用。当进程的CPU控制权失去时会调用将sum_exec_runtime记录的时间值存放到prev_sum_exec_runtime里。

Vruntime是虚拟进程执行期间虚拟时钟上流逝的时间和前一个进程总的消耗的时间。

进程优先级计算方法

图1.3.1 进程优先级

         进程的优先级分为实时优先级和普通优先级,可以通过nice命令设置进程的静态优先级,nice值的范围是[-20,19],进程的实际优先级是动态优先级和静态优先级之和。

<include/linux/sched/rt.h>	
#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)
 <kernel/sched/sched.h>
#define NICE_TO_PRIO(nice)	(MAX_RT_PRIO + (nice) + 20)
#define PRIO_TO_NICE(prio)	((prio) - MAX_RT_PRIO - 20)
#define TASK_NICE(p)		PRIO_TO_NICE((p)->static_prio)

进程优先级的计算由effective_prio方法完成,通过nice值设置静态优先级就是调用其完成优先级的变更的。Normal_prio计算进程的普通优先级,该函数首先判断进程使用的是否是实时进程,实时进程的调度策略要么是SCHED_FIFO,要么是SCHED_RR,如果是实时进程,则其动态优先级的值是MAX_RT_PRIO-1 - p->rt_priority;如果是普通进程,则其优先级实际上就是静态优先级。

<kernel/sched/core.c>
900 static inline int normal_prio(struct task_struct *p)
 901 {
 902     int prio;
 903 
 904     if (task_has_rt_policy(p))
 905         prio = MAX_RT_PRIO-1 - p->rt_priority;
 906     else
 907         prio = __normal_prio(p);
 908     return prio;
 909 }

该函数计算进程的优先级,首先通过normal_prio函数计算进程的普通优先级,然后调用rt_prio检查动态优先级是否在实时进程优先级中,如果在实时进程优先级中,则返回该动态优先级。

<kernel/sched/core.c>
918 static int effective_prio(struct task_struct *p)
 919 {
 920     p->normal_prio = normal_prio(p);
 921     /*
 922      * If we are RT tasks or we were boosted to RT priority,
 923      * keep the priority unchanged. Otherwise, update priority
 924      * to the normal priority:
 925      */
 926     if (!rt_prio(p->prio))
 927         return p->normal_prio;
 928     return p->prio;
 929 }

进程load计算

调度实体的load成员反映的是进程的权重。

<include/linux/sched.h>
struct load_weight {
	unsigned long weight, inv_weight;
};

进程的nice值每降低一个,则将多获得10%的CPU时间,为了实现优先级和能够使用的CPU时间挂钩,采用权重转换表,权重转换表相邻项之间的比值近似为1.25,如果一个进程多获得10%的CPU时间,则意味着另一个进程必须减少10%的CPU时间,这意味着相邻权重的差值约为25%。如果只有权重为1024和820的两个进程,这两个进程占用的CPU差值应该是10%,1024/(1024+820) = ~55%,而820/(1024+820) = ~ 45%。差值约是10%。

<include/linux/sched.h>
static const int prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};

优先级和权重转换由set_load_weight函数完成。755行判断是否是idle进程,idle优先级是最低的。

<kernel/sched/core.c>
747 static void set_load_weight(struct task_struct *p)
 748 {
 749     int prio = p->static_prio - MAX_RT_PRIO;
 750     struct load_weight *load = &p->se.load;
 751 
 752     /*
 753      * SCHED_IDLE tasks get minimal weight:
 754      */
 755     if (p->policy == SCHED_IDLE) {
 756         load->weight = scale_load(WEIGHT_IDLEPRIO);
 757         load->inv_weight = WMULT_IDLEPRIO;
 758         return;
 759     }
 760 
 761     load->weight = scale_load(prio_to_weight[prio]);
 762     load->inv_weight = prio_to_wmult[prio];
 763 }

761行就是去前面的数组中获取权重,而762行是761行权重的倒数。

主调度器schedule

__schedule

__schedule函数进入可能源于以下时间:

1、  显示阻塞:mutex、semaphore、waitqueue等

2、  TIF_NEED_RESCHED标志,中断或者用户空间程序返回检测该标志。

3、  Wakeup之类的函数,实际上并不直接调用schedule(),而是将进程添加到run-queue。如果添加到运行队列的进程抢占了当前进程,wakeup函数将设置TIF_NEED_RESCHED标志,schedule函数将在下列可能的场景被调用:

a)        如果内核支持抢占(CONFIG_PREEMPT=y),在syscall或者异常上下文,在最外层的preempt_enable()可能调用schedule。

b)        在中断上下文,中断函数返回到可抢占上下文

c)        如果内核未使能抢占,则发生在如下场景,cond_resched()、explicit schedule()、syscall或者用户空间异常、中断处理返回到用户空间。

<kernel/sched/core.c>
2950 static void __sched __schedule(void)
2951 {
2952     struct task_struct *prev, *next;
2953     unsigned long *switch_count;
2954     struct rq *rq;
2955     int cpu;

prev指向即将要调度出去的进程,next是调度要执行的进程。在某些情况下,二者可能是一样的,比如只有一个进程可以运行时,则调度器查找的进程将还是这个进程。

2957  need_resched:
2958     preempt_disable();
2959     cpu = smp_processor_id();
2960     rq = cpu_rq(cpu);
2961     rcu_note_context_switch(cpu);
2962     prev = rq->curr;

2958行禁止抢占,2959获得进程所在的当前CPU号,这是因为运行队列使用了per-CPU变量方式,必须根据在取得CPU号的基础上获得对应的变量。

2960获得该CPU的rq,每个核号均对应一个rq,这是一种多核之间的一种免锁算法,内核中很多地方使用到。

2961行rcu_note_context_switch设置rcu调度标志

2962行prev指向当前CPU上运行的进程,因为该CPU是要被调度出去的对象。

2969     raw_spin_lock_irq(&rq->lock);
2970 
2971     switch_count = &prev->nivcsw;
2972     if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
2973         if (unlikely(signal_pending_state(prev->state, prev))) {
2974             prev->state = TASK_RUNNING;
2975         } else {
2976             deactivate_task(rq, prev, DEQUEUE_SLEEP);
2977             prev->on_rq = 0;
2978 
2979             /*
2980              * If a worker went to sleep, notify and ask workqueue
2981              * whether it wants to wake up a task to maintain
2982              * concurrency.
2983              */
2984             if (prev->flags & PF_WQ_WORKER) {
2985                 struct task_struct *to_wakeup;
2986 
2987                 to_wakeup = wq_worker_sleeping(prev, cpu);
2988                 if (to_wakeup)
2989                     try_to_wake_up_local(to_wakeup);
2990             }
2991         }
2992         switch_count = &prev->nvcsw;
2993     }

2069行,rq不允许并发修改,这里先获取修改的自旋锁。

2971行,切换计数。

2972行,若果prev->state不等于0,即意味着进程处于不可运行或停止状态,(preempt_count() & PREEMPT_ACTIVE)非零则意味着内核支持抢占。这一样的意义就是如果该进程处于不可运行状态并且内核也不支持抢占,这就意味着该进程需要主动放弃CPU。

2973这行判断是否有信号量等待处理,unlikely的意义是不太可能有信号量需要处理,不过如果真的有信号量要处理,只能将进程状态再次设置成TASK_RUNNING状态。否则,2976行将该进程从rq上deactivate,其方法是调度类的dequeue_task方法。

2979~2990是对worker的处理,如果worker将要休眠,则通知一个工作队列以判断其是否想要唤醒一个进程以维持并发。

2995     pre_schedule(rq, prev);
2996 
2997     if (unlikely(!rq->nr_running))
2998         idle_balance(cpu, rq);
2999 
3000     put_prev_task(rq, prev);
3001     next = pick_next_task(rq);
3002     clear_tsk_need_resched(prev);
3003     rq->skip_clock_update = 0;

2995行,到这里说明是抢占式调度,该行调用调度类的pre_schedule方法进行相应的设置。

2997~2998如果当前CPU上可以运行的进程数等于0,则调用idle_balance对CPU资源进行均衡,如果是单核,则情况较为简单,什么也不过,如果是SMP情况,那么就需要从其它可运行进程较多的CPU核上迁移一些进程到该CPU的rq上。

3000行调用调度类的put_prev_task方法通知调度类自己当前的进程要被替换掉。

3001行调用调度类的pick_next_task方法选取合适的可以运行的进程。

3002行将先前进程的TIF_NEED_RESCHED标志清掉。

3005     if (likely(prev != next)) {
3006         rq->nr_switches++;
3007         rq->curr = next;
3008         ++*switch_count;
3009 
3010         context_switch(rq, prev, next); /* unlocks the rq */
3011         /*
3012          * The context switch have flipped the stack from under us
3013          * and restored the local variables which were saved when
3014          * this task called schedule() in the past. prev == current
3015          * is still correct, but it can be moved to another cpu/rq.
3016          */
3017         cpu = smp_processor_id();
3018         rq = cpu_rq(cpu);
3019     } else
3020         raw_spin_unlock_irq(&rq->lock);

3005行判断prev和next是否相等,这是可能的,如果其它进程均在休眠,只有当前的进程可以运行,则pick_next_task方法选取到的进程依然是当前的进程。如果相等则执行3020行释放自旋锁。

否则3006~3008跟新统计信息,3010执行进程上下文切换。

3017获得该进程所在的CPU核号,然后3018行更新其rq。

3022     post_schedule(rq);
3023 
3024     sched_preempt_enable_no_resched();
3025     if (need_resched())
3026         goto need_resched;
3027 }

3022行调用调度类的post方法,进行后处理。

3025行判断是否需要进行重新调度,就是判断当前线程的TIF_NEED_RESCHED标志是否被设置,如果设置则跳转到2957行再次调度。

context_switch

进程切换由context_switch完成,该函数和CPU架构是息息相关的,对于IA32和IA64而言,新进程的全局描述符表和局部描述符表需要更新的,另外寄存器的内容也要切换成新进程之前的值。

1978 static inline void
1979 context_switch(struct rq *rq, struct task_struct *prev,
1980            struct task_struct *next)
1981 {
1982     struct mm_struct *mm, *oldmm;
1983 
1984     prepare_task_switch(rq, prev, next);
1985 
1986     mm = next->mm;
1987     oldmm = prev->active_mm;
1988     /*
1989      * For paravirt, this is coupled with an exit in switch_to to
1990      * combine the page table reload and the switch backend into
1991      * one hypercall.
1992      */
1993     arch_start_context_switch(prev);
1994 
1995     if (!mm) {
1996         next->active_mm = oldmm;
1997         atomic_inc(&oldmm->mm_count);
1998         enter_lazy_tlb(oldmm, next);
1999     } else
2000         switch_mm(oldmm, mm, next);
2001 
2002     if (!prev->mm) {
2003         prev->active_mm = NULL;
2004         rq->prev_mm = oldmm;
2005     }
2006     /*
2007      * Since the runqueue lock will be released by the next
2008      * task (which is an invalid locking op but in the case
2009      * of the scheduler it's an obvious special-case), so we
2010      * do an early lockdep release here:
2011      */
2012 #ifndef __ARCH_WANT_UNLOCKED_CTXSW
2013     spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
2014 #endif
2015 
2016     context_tracking_task_switch(prev, next);
2017     /* Here we just switch the register state and the stack. */
2018     switch_to(prev, next, prev);
2019 
2020     barrier();
2021     /*
2022      * this_rq must be evaluated again because prev may have moved
2023      * CPUs since it called schedule(), thus the 'rq' on its stack
2024      * frame will be invalid.
2025      */
2026     finish_task_switch(this_rq(), prev);
2027 }

1984行,prepare_task_switch为进程切换之前做些准备工作,这是为不同的CPU而提供的,在正式切换之前可以做些CPU需要特定完成的事。

1986~1987行,mm含义是进程拥有的内存描述符,而active_mm是进程使用的内存描述符,这两个字段咋一看似乎应该是一样的,事实上对于一般意义上的进程这两个字段确实是相同的,但是对于内核线程而言,其没有自己的地址空间,所以其mm字段等于NULL。

1993行,arch_start_context_switch是调用参数虚拟化函数。

1995行,判断是否是内核线程。

1996~1998行,内核线程的处理方法,其active_mm使用之前进程的,使用计数原子加一。接着enter_lazy_tlb将TLB(translation look aside buffer)设置为惰性模式,这种模式在SMP情况下才有意义,如果是单核,由于所有用户态进程和内核进程共享同一段内核地址空间且不存在并发访问,所以可以不刷新TLB,而问题是在SMP情况下情况就复杂一点,但是惰性TLB技术还是可以延迟TLB刷新操作以期提高系统性能。

2002~2005行,判断前一个进程是否是内核线程,如果是内核线程则其使用的内存描符必须无效,并把prev的内存描述符的指针保存到运行队列的prev_mm字段。

2012行,切换栈和寄存器里的值以适应新调度运行进程的需要。

2020行,指示2026的操作需要操作内存而非cache。这是因为SMP情况下,prev进程可能调度到其它CPU(负载均衡技术)上运行了,所以需要跟新其栈上的rq队列。

完全公平调度类

<kernel/sched/fair.c>
6135 const struct sched_class fair_sched_class = {
6136     .next           = &idle_sched_class,
6137     .enqueue_task       = enqueue_task_fair,
6138     .dequeue_task       = dequeue_task_fair,
6139     .yield_task     = yield_task_fair,
6140     .yield_to_task      = yield_to_task_fair,
6141 
6142     .check_preempt_curr = check_preempt_wakeup,
6143 
6144     .pick_next_task     = pick_next_task_fair,
6145     .put_prev_task      = put_prev_task_fair,
6146 
6147 #ifdef CONFIG_SMP
6148     .select_task_rq     = select_task_rq_fair,
6149 #ifdef CONFIG_FAIR_GROUP_SCHED
6150     .migrate_task_rq    = migrate_task_rq_fair,
6151 #endif
6152     .rq_online      = rq_online_fair,
6153     .rq_offline     = rq_offline_fair,
6154 
6155     .task_waking        = task_waking_fair,
6156 #endif
6157 
6158     .set_curr_task          = set_curr_task_fair,
6159     .task_tick      = task_tick_fair,
6160     .task_fork      = task_fork_fair,
6161 
6162     .prio_changed       = prio_changed_fair,
6163     .switched_from      = switched_from_fair,
6164     .switched_to        = switched_to_fair,
6165 
6166     .get_rr_interval    = get_rr_interval_fair,
6167 
6168 #ifdef CONFIG_FAIR_GROUP_SCHED
6169     .task_move_group    = task_move_group_fair,
6170 #endif
6171 };

CFS虚拟时钟

完全公平调度算法依赖于虚拟时钟,。于虚拟时钟相关的计算由update_curr()函数完成。该函数计算当前进程的执行时间并将该时间值存放于delta_exec变量,然后将该值作为参数传递给__update_curr()函数,该函数根据可运行进程数和进程优先级(load值)重新计算时间。当前进程的vruntime由权重时间增加。

系统时钟会周期性唤醒update_curr(),当一个进程变成可运行、阻塞、或者编程不可运行时该函数也会被调用。使用这种方式,vruntime是运行时间的准确度量并且指示了下一个要运行的进程。

<kernel/sched/fair.c>
686 static void update_curr(struct cfs_rq *cfs_rq)
 687 {
 688     struct sched_entity *curr = cfs_rq->curr;
 689     u64 now = rq_of(cfs_rq)->clock_task;
 690     unsigned long delta_exec;
 691 
 692     if (unlikely(!curr))
 693         return;
 694 
 695     /*
 696      * Get the amount of time the current task was running
 697      * since the last time we changed load (this cannot
 698      * overflow on 32 bits):
 699      */
 700     delta_exec = (unsigned long)(now - curr->exec_start);
 701     if (!delta_exec)
 702         return;
 703 
 704     __update_curr(cfs_rq, curr, delta_exec);
 705     curr->exec_start = now;
 706 
 707     if (entity_is_task(curr)) {
 708         struct task_struct *curtask = task_of(curr);
 709 
 710         trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
 711         cpuacct_charge(curtask, delta_exec);
 712         account_group_exec_runtime(curtask, delta_exec);
 713     }
 714 
 715     account_cfs_rq_runtime(cfs_rq, delta_exec);
 716 }

686行,该函数的参数是CFS调度队列,689行获得当前调度队列的实际时钟值。

692行,如果当前就绪队列上没有进程在运行,则直接返回。

700行,注释里说的很明白是自上一次负载权重改变之后当前进程执行的时间。

704行的函数主要是更新运行时间统计信息,后面会细细分析。

705行,将当前调度实体(进程)的执行时间更新为当前运行队列的时间值。

707行,判断是调度实体还是一个进程。调度实体和调度类的区别在于,调度实体有其自己的运行队列,而进程是没有运行队列的。

708~712是针对针对进程情况的进行的一些工作,这些工作包括统计信息变更,支持cgroup情况下的CPU统计信息,以及当前进程运行的总时间更新。

<kernel/sched/fair.c>
669 static inline void
 670 __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
 671           unsigned long delta_exec)
 672 {
 673     unsigned long delta_exec_weighted;
 674 
 675     schedstat_set(curr->statistics.exec_max,
 676               max((u64)delta_exec, curr->statistics.exec_max));
 677 
 678     curr->sum_exec_runtime += delta_exec;
 679     schedstat_add(cfs_rq, exec_clock, delta_exec);
 680     delta_exec_weighted = calc_delta_fair(delta_exec, curr);
 681 
 682     curr->vruntime += delta_exec_weighted;
 683     update_min_vruntime(cfs_rq);
 684 }

576行将调度实体一次运行的最长时间值exec_max设置成delta_exec和当前最长时间值中较大的那个。

678跟新当前调度实体执行的总时间,总时间=以前执行时间之和+本次执行时间之和

679行在内核配置选项打开了调度统计后,该函数会将rq的exec_clock成员加上delta_exec以跟新调度队列执行的总时间。

680根据前一次调度实体运行的时间值从新计算时间。这个计算过程有点琐碎。

<kernel/sched/fair.c>
597 static inline unsigned long
 598 calc_delta_fair(unsigned long delta, struct sched_entity *se)
 599 {
 600     if (unlikely(se->load.weight != NICE_0_LOAD))
 601         delta = calc_delta_mine(delta, NICE_0_LOAD, &se->load);
 602 
 603     return delta;
 604 }

首先600行的NICE_0_LOAD,从字面意思来看是nice值等于0时的load值,该值在1.3.4节的prio_to_weight数组里定义好了,值为1024。所以这句话看来就是该调度实体的load值不太可能是nice值等于0的进程,当然如果真是nice值等于0的进程,则直接返回上一个进程执行的时间值,否则进入601行。

calc_delta_mine是前一个进程执行的时间值,第二个参数是1024,第三个参数是调度实体的load值。

<kernel/sched/fair.c>
179 static unsigned long
 180 calc_delta_mine(unsigned long delta_exec, unsigned long weight,
 181         struct load_weight *lw)
 182 {
 183     u64 tmp;
 184 
 185     /*
 186      * weight can be less than 2^SCHED_LOAD_RESOLUTION for task group sched
 187      * entities since MIN_SHARES = 2. Treat weight as 1 if less than
 188      * 2^SCHED_LOAD_RESOLUTION.
 189      */
 190     if (likely(weight > (1UL << SCHED_LOAD_RESOLUTION)))
 191         tmp = (u64)delta_exec * scale_load_down(weight);
 192     else
 193         tmp = (u64)delta_exec;
 194 
 195     if (!lw->inv_weight) {
 196         unsigned long w = scale_load_down(lw->weight);
 197 
 198         if (BITS_PER_LONG > 32 && unlikely(w >= WMULT_CONST))
 199             lw->inv_weight = 1;
 200         else if (unlikely(!w))
 201             lw->inv_weight = WMULT_CONST;
 202         else
 203             lw->inv_weight = WMULT_CONST / w;
 204     }
 205 
 206     /*
 207      * Check whether we'd overflow the 64-bit multiplication:
 208      */
 209     if (unlikely(tmp > WMULT_CONST))
 210         tmp = SRR(SRR(tmp, WMULT_SHIFT/2) * lw->inv_weight,
 211             WMULT_SHIFT/2);
 212     else
 213         tmp = SRR(tmp * lw->inv_weight, WMULT_SHIFT);
 214 
 215     return (unsigned long)min(tmp, (u64)(unsigned long)LONG_MAX);
 216 }

190~193行,得到tmp= (u64)delta_exec * 1024;

195行的inv_weight变量从名称意义上看是权重的倒数,实际上是1.3.4节的prio_to_weight的倒数。这个inv_weight值的计算方法是:(2^32/l),所以会得到一个求导后的数组。

static const u32 prio_to_wmult[40] = {
 /* -20 */     48388,     59856,     76040,     92818,    118348,
 /* -15 */    147320,    184698,    229616,    287308,    360437,
 /* -10 */    449829,    563644,    704093,    875809,   1099582,
 /*  -5 */   1376151,   1717300,   2157191,   2708050,   3363326,
 /*   0 */   4194304,   5237765,   6557202,   8165337,  10153587,
 /*   5 */  12820798,  15790321,  19976592,  24970740,  31350126,
 /*  10 */  39045157,  49367440,  61356676,  76695844,  95443717,
 /*  15 */ 119304647,  148102320,  186737708,  238609294,  286331153,
};

抛去195行~212行先不看,因为他们成立的概率比较小,213行的代码

SRR(tmp * lw->inv_weight, WMULT_SHIFT); WMULT_SHIFT的值等于32,经过翻译后可以得到如下的:

(u64)delta_exec * 1024 * (2^32/load)>>32

把上面的计算式整理一下并将其赋给真正使用这个值的地方。


   curr->vruntime += delta_exec_weighted;

通过上面两行就可以知道虚拟运行时间是如何计算的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值