oracle seq_task,第六周:深入了解进程的创建

前言:这个星期的作业是分析Linux内核创建一个新进程的过程,老师要求我们学会阅读并理解task_struct;并且分析分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构;使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证对Linux系统创建一个新进程的理解。

署名部分:

作者:彭家进

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

1,阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235;

一共有430行,我知道你不会看的,折叠起来,下面是解释。

点击(此处)折叠或打开

struct task_struct {

volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */

void *stack;

atomic_t usage;

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

unsigned int ptrace;

#ifdef CONFIG_SMP

struct llist_node wake_entry;

int on_cpu;

struct task_struct *last_wakee;

unsigned long wakee_flips;

unsigned long wakee_flip_decay_ts;

int wake_cpu;

#endif

int on_rq;

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

struct sched_dl_entity dl;

#ifdef CONFIG_PREEMPT_NOTIFIERS

/* list of struct preempt_notifier: */

struct hlist_head preempt_notifiers;

#endif

#ifdef CONFIG_BLK_DEV_IO_TRACE

unsigned int btrace_seq;

#endif

unsigned int policy;

int nr_cpus_allowed;

cpumask_t cpus_allowed;

#ifdef CONFIG_PREEMPT_RCU

int rcu_read_lock_nesting;

union rcu_special rcu_read_unlock_special;

struct list_head rcu_node_entry;

#endif /* #ifdef CONFIG_PREEMPT_RCU */

#ifdef CONFIG_TREE_PREEMPT_RCU

struct rcu_node *rcu_blocked_node;

#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */

#ifdef CONFIG_TASKS_RCU

unsigned long rcu_tasks_nvcsw;

bool rcu_tasks_holdout;

struct list_head rcu_tasks_holdout_list;

int rcu_tasks_idle_cpu;

#endif /* #ifdef CONFIG_TASKS_RCU */

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)

struct sched_info sched_info;

#endif

struct list_head tasks;

#ifdef CONFIG_SMP

struct plist_node pushable_tasks;

struct rb_node pushable_dl_tasks;

#endif

struct mm_struct *mm, *active_mm;

#ifdef CONFIG_COMPAT_BRK

unsigned brk_randomized:1;

#endif

/* per-thread vma caching */

u32 vmacache_seqnum;

struct vm_area_struct *vmacache[VMACACHE_SIZE];

#if defined(SPLIT_RSS_COUNTING)

struct task_rss_stat    rss_stat;

#endif

/* task state */

int exit_state;

int exit_code, exit_signal;

int pdeath_signal; /* The signal sent when the parent dies */

unsigned int jobctl;    /* JOBCTL_*, siglock protected */

/* Used for emulating ABI behavior of previous Linux versions */

unsigned int personality;

unsigned in_execve:1;    /* Tell the LSMs that the process is doing an

* execve */

unsigned in_iowait:1;

/* Revert to default priority/policy when forking */

unsigned sched_reset_on_fork:1;

unsigned sched_contributes_to_load:1;

unsigned long atomic_flags; /* Flags needing atomic access. */

pid_t pid;

pid_t tgid;

#ifdef CONFIG_CC_STACKPROTECTOR

/* Canary value for the -fstack-protector gcc feature */

unsigned long stack_canary;

#endif

/*

* pointers to (original) parent process, youngest child, younger sibling,

* older sibling, respectively. (p->father can be replaced with

* p->real_parent->pid)

*/

struct task_struct __rcu *real_parent; /* real parent process */

struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */

/*

* children/sibling forms the list of my natural children

*/

struct list_head children;    /* list of my children */

struct list_head sibling;    /* linkage in my parent's children list */

struct task_struct *group_leader;    /* threadgroup leader */

/*

* ptraced is the list of tasks this task is using ptrace on.

* This includes both natural children and PTRACE_ATTACH targets.

* p->ptrace_entry is p's link on the p->parent->ptraced list.

*/

struct list_head ptraced;

struct list_head ptrace_entry;

/* PID/PID hash table linkage. */

struct pid_link pids[PIDTYPE_MAX];

struct list_head thread_group;

struct list_head thread_node;

struct completion *vfork_done;        /* for vfork() */

int __user *set_child_tid;        /* CLONE_CHILD_SETTID */

int __user *clear_child_tid;        /* CLONE_CHILD_CLEARTID */

cputime_t utime, stime, utimescaled, stimescaled;

cputime_t gtime;

#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE

struct cputime prev_cputime;

#endif

#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN

seqlock_t vtime_seqlock;

unsigned long long vtime_snap;

enum {

VTIME_SLEEPING = 0,

VTIME_USER,

VTIME_SYS,

} vtime_snap_whence;

#endif

unsigned long nvcsw, nivcsw; /* context switch counts */

u64 start_time;        /* monotonic time in nsec */

u64 real_start_time;    /* boot based time in nsec */

/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */

unsigned long min_flt, maj_flt;

struct task_cputime cputime_expires;

struct list_head cpu_timers[3];

/* process credentials */

const struct cred __rcu *real_cred; /* objective and real subjective task

* credentials (COW) */

const struct cred __rcu *cred;    /* effective (overridable) subjective task

* credentials (COW) */

char comm[TASK_COMM_LEN]; /* executable name excluding path

- access with [gs]et_task_comm (which lock

it with task_lock())

- initialized normally by setup_new_exec */

/* file system info */

int link_count, total_link_count;

#ifdef CONFIG_SYSVIPC

/* ipc stuff */

struct sysv_sem sysvsem;

struct sysv_shm sysvshm;

#endif

#ifdef CONFIG_DETECT_HUNG_TASK

/* hung task detection */

unsigned long last_switch_count;

#endif

/* CPU-specific state of this task */

struct thread_struct thread;

/* filesystem information */

struct fs_struct *fs;

/* open file information */

struct files_struct *files;

/* namespaces */

struct nsproxy *nsproxy;

/* signal handlers */

struct signal_struct *signal;

struct sighand_struct *sighand;

sigset_t blocked, real_blocked;

sigset_t saved_sigmask;    /* restored if set_restore_sigmask() was used */

struct sigpending pending;

unsigned long sas_ss_sp;

size_t sas_ss_size;

int (*notifier)(void *priv);

void *notifier_data;

sigset_t *notifier_mask;

struct callback_head *task_works;

struct audit_context *audit_context;

#ifdef CONFIG_AUDITSYSCALL

kuid_t loginuid;

unsigned int sessionid;

#endif

struct seccomp seccomp;

/* Thread group tracking */

u32 parent_exec_id;

u32 self_exec_id;

/* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,

* mempolicy */

spinlock_t alloc_lock;

/* Protection of the PI data structures: */

raw_spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES

/* PI waiters blocked on a rt_mutex held by this task */

struct rb_root pi_waiters;

struct rb_node *pi_waiters_leftmost;

/* Deadlock detection and priority inheritance handling */

struct rt_mutex_waiter *pi_blocked_on;

#endif

#ifdef CONFIG_DEBUG_MUTEXES

/* mutex deadlock detection */

struct mutex_waiter *blocked_on;

#endif

#ifdef CONFIG_TRACE_IRQFLAGS

unsigned int irq_events;

unsigned long hardirq_enable_ip;

unsigned long hardirq_disable_ip;

unsigned int hardirq_enable_event;

unsigned int hardirq_disable_event;

int hardirqs_enabled;

int hardirq_context;

unsigned long softirq_disable_ip;

unsigned long softirq_enable_ip;

unsigned int softirq_disable_event;

unsigned int softirq_enable_event;

int softirqs_enabled;

int softirq_context;

#endif

#ifdef CONFIG_LOCKDEP

# define MAX_LOCK_DEPTH 48UL

u64 curr_chain_key;

int lockdep_depth;

unsigned int lockdep_recursion;

struct held_lock held_locks[MAX_LOCK_DEPTH];

gfp_t lockdep_reclaim_gfp;

#endif

/* journalling filesystem info */

void *journal_info;

/* stacked block device info */

struct bio_list *bio_list;

#ifdef CONFIG_BLOCK

/* stack plugging */

struct blk_plug *plug;

#endif

/* VM state */

struct reclaim_state *reclaim_state;

struct backing_dev_info *backing_dev_info;

struct io_context *io_context;

unsigned long ptrace_message;

siginfo_t *last_siginfo; /* For ptrace use. */

struct task_io_accounting ioac;

#if defined(CONFIG_TASK_XACCT)

u64 acct_rss_mem1;    /* accumulated rss usage */

u64 acct_vm_mem1;    /* accumulated virtual memory usage */

cputime_t acct_timexpd;    /* stime + utime since last update */

#endif

#ifdef CONFIG_CPUSETS

nodemask_t mems_allowed;    /* Protected by alloc_lock */

seqcount_t mems_allowed_seq;    /* Seqence no to catch updates */

int cpuset_mem_spread_rotor;

int cpuset_slab_spread_rotor;

#endif

#ifdef CONFIG_CGROUPS

/* Control Group info protected by css_set_lock */

struct css_set __rcu *cgroups;

/* cg_list protected by css_set_lock and tsk->alloc_lock */

struct list_head cg_list;

#endif

#ifdef CONFIG_FUTEX

struct robust_list_head __user *robust_list;

#ifdef CONFIG_COMPAT

struct compat_robust_list_head __user *compat_robust_list;

#endif

struct list_head pi_state_list;

struct futex_pi_state *pi_state_cache;

#endif

#ifdef CONFIG_PERF_EVENTS

struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];

struct mutex perf_event_mutex;

struct list_head perf_event_list;

#endif

#ifdef CONFIG_DEBUG_PREEMPT

unsigned long preempt_disable_ip;

#endif

#ifdef CONFIG_NUMA

struct mempolicy *mempolicy;    /* Protected by alloc_lock */

short il_next;

short pref_node_fork;

#endif

#ifdef CONFIG_NUMA_BALANCING

int numa_scan_seq;

unsigned int numa_scan_period;

unsigned int numa_scan_period_max;

int numa_preferred_nid;

unsigned long numa_migrate_retry;

u64 node_stamp;            /* migration stamp */

u64 last_task_numa_placement;

u64 last_sum_exec_runtime;

struct callback_head numa_work;

struct list_head numa_entry;

struct numa_group *numa_group;

/*

* Exponential decaying average of faults on a per-node basis.

* Scheduling placement decisions are made based on the these counts.

* The values remain static for the duration of a PTE scan

*/

unsigned long *numa_faults_memory;

unsigned long total_numa_faults;

/*

* numa_faults_buffer records faults per node during the current

* scan window. When the scan completes, the counts in

* numa_faults_memory decay and these values are copied.

*/

unsigned long *numa_faults_buffer_memory;

/*

* Track the nodes the process was running on when a NUMA hinting

* fault was incurred.

*/

unsigned long *numa_faults_cpu;

unsigned long *numa_faults_buffer_cpu;

/*

* numa_faults_locality tracks if faults recorded during the last

* scan window were remote/local. The task scan period is adapted

* based on the locality of the faults with different weights

* depending on whether they were shared or private faults

*/

unsigned long numa_faults_locality[2];

unsigned long numa_pages_migrated;

#endif /* CONFIG_NUMA_BALANCING */

struct rcu_head rcu;

/*

* cache last used pipe for splice

*/

struct pipe_inode_info *splice_pipe;

struct page_frag task_frag;

#ifdef    CONFIG_TASK_DELAY_ACCT

struct task_delay_info *delays;

#endif

#ifdef CONFIG_FAULT_INJECTION

int make_it_fail;

#endif

/*

* when (nr_dirtied >= nr_dirtied_pause), it's time to call

* balance_dirty_pages() for some dirty throttling pause

*/

int nr_dirtied;

int nr_dirtied_pause;

unsigned long dirty_paused_when; /* start of a write-and-pause period */

#ifdef CONFIG_LATENCYTOP

int latency_record_count;

struct latency_record latency_record[LT_SAVECOUNT];

#endif

/*

* time slack values; these are used to round up poll() and

* select() etc timeout values. These are in nanoseconds.

*/

unsigned long timer_slack_ns;

unsigned long default_timer_slack_ns;

#ifdef CONFIG_FUNCTION_GRAPH_TRACER

/* Index of current stored address in ret_stack */

int curr_ret_stack;

/* Stack of return addresses for return function tracing */

struct ftrace_ret_stack    *ret_stack;

/* time stamp for last schedule */

unsigned long long ftrace_timestamp;

/*

* Number of functions that haven't been traced

* because of depth overrun.

*/

atomic_t trace_overrun;

/* Pause for the tracing */

atomic_t tracing_graph_pause;

#endif

#ifdef CONFIG_TRACING

/* state flags for use by tracers */

unsigned long trace;

/* bitmask and counter of trace recursion */

unsigned long trace_recursion;

#endif /* CONFIG_TRACING */

#ifdef CONFIG_MEMCG /* memcg uses this to do batch job */

unsigned int memcg_kmem_skip_account;

struct memcg_oom_info {

struct mem_cgroup *memcg;

gfp_t gfp_mask;

int order;

unsigned int may_oom:1;

} memcg_oom;

#endif

#ifdef CONFIG_UPROBES

struct uprobe_task *utask;

#endif

#if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE)

unsigned int    sequential_io;

unsigned int    sequential_io_avg;

#endif

};

对task_struct各项参数的解释:

1.进程状态(State)

进程执行时,它会根据具体情况改变状态 。进程状态是调度和对换的依据。Linux中的进程主要有如下状态,如表4.1所示。

表Linux进程的状态

内核表示

含义

TASK_RUNNING

可运行

TASK_INTERRUPTIBLE

可中断的等待状态

TASK_UNINTERRUPTIBLE

不可中断的等待状态

TASK_ZOMBIE

僵死

TASK_STOPPED

暂停

TASK_SWAPPING

换入/换出

·可运行状态

处于这种状态的进程,要么正在运行、要么正准备运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行。在后面我们讨论进程调度的时候,可以看到运行队列的作用。当前运行进程一直处于该队列中,也就是说,current总是指向运行队列中的某个元素,只是具体指向谁由调度程序决定。

·等待状态

处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中。Linux中处于等待状态的进程分为两种:可中断的等待状态和不可中断的等待状态。处于可中断等待态的进程可以被信号唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度;而处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。

·暂停状态

此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。

·僵死状态

进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。

2.进程调度信息

调度程序利用这部分信息决定系统中哪个进程最应该运行,并结合进程的状态信息保证系统运转的公平和高效。这一部分信息通常包括进程的类别(普通进程还是实时进程)、进程的优先级等等。如表4.2所示:

表4.2进程调度信息

域名

含义

need_resched

调度标志

Nice

静态优先级

Counter

动态优先级

Policy

调度策略

rt_priority

实时优先级

counter代表进程剩余的时间片,是进程调度的主要依据,也可以说是进程的动态优先级,因为这个值在不断地减少;nice是进程的静态优先级,同时也代表进程的时间片,用于对counter赋值,可以用nice()系统调用改变这个值;policy是适用于该进程的调度策略,实时进程和普通进程的调度策略是不同的;rt_priority只对实时进程有意义,它是实时进程调度的依据。

进程的调度策略有三种,如表4.3所示。

表4.3进程调度的策略

名称

解释

适用范围

SCHED_OTHER

其他调度

普通进程

SCHED_FIFO

先来先服务调度

实时进程

SCHED_RR

时间片轮转调度

只有root用户能通过sched_setscheduler()系统调用来改变调度策略。

3 .标识符(Identifiers)

每个进程有进程标识符、用户标识符、组标识符,如表4.4所示。

不管对内核还是普通用户来说,怎么用一种简单的方式识别不同的进程呢?这就引入了进程标识符(PID:process identifier),每个进程都有一个唯一的标识符,内核通过这个标识符来识别不同的进程,同时,进程标识符PID也是内核提供给用户程序的接口,用户程序通过PID对进程发号施令。PID是32位的无符号整数,它被顺序编号:新创建进程的PID通常是前一个进程的PID加1。然而,为了与16位硬件平台的传统Linux系统保持兼容,在Linux上允许的最大PID号是32767,当内核在系统中创建第32768个进程时,就必须重新开始使用已闲置的PID号。

表4.4各种标识符

域名

含义

Pid

进程标识符

Uid、gid

用户标识符、组标识符

Euid、egid

有效用户标识符、有效组标识符

Suid、sgid

备份用户标识符、备份组标识符

Fsuid、fsgid

文件系统用户标识符、文件系统组标识符

另外,每个进程都属于某个用户组。task_struct结构中定义有用户标识符和组标识符。它们同样是简单的数字,这两种标识符用于系统的安全控制。系统通过这两种标识符控制进程对系统中文件和设备的访问,其它几个标识符将在文件系统中讨论。

4.进程通信有关信息(IPC:Inter_Process Communication)

为了使进程能在同一项任务上协调工作,进程之间必须能进行通信即交流数据。

Linux支持多种不同形式的通信机制。它支持典型的Unix通信机制(IPC Mechanisms):信号(Signals)、管道(Pipes),也支持System V通信机制:共享内存(Shared Memory)、信号量和消息队列(Message Queues),如表4.5。

表4.5进程通信有关信息

域名

含义

Spinlock_t sigmask_lock

信号掩码的自旋锁

Long blocked

信号掩码

Struct signal  *sig

信号处理函数

Struct sem_undo *semundo

为避免死锁而在信号量上设置的取消操作

Struct sem_queue *semsleeping

与信号量操作相关的等待队列

5.进程链接信息(Links)

程序创建的进程具有父/子关系。因为一个进程能创建几个子进程,而子进程之间有兄弟关系,在task_struct结构中有几个域来表示这种关系。

在Linux系统中,除了初始化进程init,其他进程都有一个父进程(parent process)或称为双亲进程。可以通过fork()或clone()系统调用来创建子进程,除了进程标识符(PID)等必要的信息外,子进程的task_struct结构中的绝大部分的信息都是从父进程中拷贝,或说“克隆”过来的。系统有必要记录这种“亲属”关系,使进程之间的协作更加方便,例如父进程给子进程发送杀死(kill)信号、父子进程通信等,就可以用这种关系很方便地实现。

每个进程的task_struct结构有许多指针,通过这些指针,系统中所有进程的task_struct结构就构成了一棵进程树,这棵进程树的根就是初始化进程init的task_struct结构(init进程是Linux内核建立起来后人为创建的一个进程,是所有进程的祖先进程)。表4.6是进程所有的链接信息。

表4.6进程链接信息

名称

英文解释

中文解释[指向哪个进程]

p_opptr

Original parent

祖先

p_pptr

Parent

父进程

p_cptr

Child

子进程

p_ysptr

Younger sibling

弟进程

p_osptr

Older sibling

兄进程

Pidhash_next、

Pidhash_pprev

进程在哈希表中的链接

Next_task、prev_task

进程在双向循环链表中的链接

Run_list

运行队列的链表

6.时间和定时器信息(Times and Timers)

一个进程从创建到终止叫做该进程的生存期(lifetime)。进程在其生存期内使用CPU的时间,内核都要进行记录,以便进行统计、计费等有关操作。进程耗费CPU的时间由两部分组成:一是在用户模式(或称为用户态)下耗费的时间、一是在系统模式(或称为系统态)下耗费的时间。每个时钟滴答,也就是每个时钟中断,内核都要更新当前进程耗费CPU的时间信息。

“时间”对操作系统是极其重要的 。读者可能了解计算机时间的有关知识,例如8353/8254这些物理器件,INT 08、INT1C等时钟中断等。

建立了“时间”的概念,“定时”就是轻而易举的了,无非是判断系统时间是否到达某个时刻,然后执行相关的操作而已。Linux提供了许多种定时方式,用户可以灵活使用这些方式来为自己的程序定时。

表4.7是和时间有关的域,上面所说的counter是指进程剩余的CPU时间片,也和时间有关,所以这里我们再次提及它。表4.8是进程的所有定时器。

表4.7与时间有关的域

域名

含义

Start_time

进程创建时间

Per_cpu_utime

进程在某个CPU上运行时在用户态下耗费的时间

Per_cpu_stime

进程在某个CPU上运行时在系统态下耗费的时间

Counter

进程剩余的时间片

表4.8进程的所有定时器

定时器类型

解释

什么时候更新

用来表示此种定时器的域

ITIMER_REAL

实时定时器

实时更新,即不论该进程是否运行

it_real_value

it_real_incr

real_timer

ITIMER_VIRTUAL

虚拟定时器

只在进程运行于用户态时更新

it_virt_value

it_virt_incr

ITIMER_PROF

概况定时器

进程运行于用户态和系统态时更新

it_prof_value

it_prof_incr

进程有三种类型的定时器:实时定时器、虚拟定时器和概况定时器。这三种定时器的特征共有三个:到期时间、定时间隔、要触发的事件。到期时间就是定时器到什么时候完成定时操作,从而触发相应的事件;定时间隔就是两次定时操作的时间间隔,它决定了定时操作是否继续进行,如果定时间隔大于0,则在定时器到期时,该定时器的到期时间被重新赋值,使定时操作继续进行下去,直到进程结束或停止使用定时器,只不过对不同的定时器,到期时间的重新赋值操作是不同的。在表4.8中,每个定时器都有两个域来表示到期时间和定时间隔:value和incr,二者的单位都是时钟滴答,和jiffies的单位是一致的,Linux所有的时间应用都建立在jiffies之上。虚拟定时器和概况定时器到期时由内核发送相应的信号,而实时定时器比较特殊,它由内核机制提供支持,我们将在后面讨论这个问题。

每个时钟中断,当前进程所有和时间有关的信息都要更新: 当前进程耗费的CPU时间要更新,以便于最后的计费;时间片计数器counter要更新,如果counter<=0,则要执行调度程序;进程申请的延时要更新,如果延时时间到了,则唤醒该进程;所有的定时器都要更新,Linux内核检测这些定时器是否到期,如果到期,则执行相应的操作。在这里,“更新”的具体操作是不同的:对counter,内核要对它减值,而对于所有的定时器,就是检测它的值,内核把系统当前时间和其到期时间作一比较,如果到期时间小于系统时间,则表示该定时器到期。但为了方便,我们把这些操作一概称为“更新”,请读者注意。

请特别注意上面三个定时器的更新时间。实时定时器不管其所属的进程是否运行都要更新,所以,时钟中断来临时,系统中所有进程的实时定时器都被更新,如果有多个进程的实时定时器到期,则内核要一一处理这些定时器所触发的事件。而虚拟定时器和概况定时器只在进程运行时更新,所以,时钟中断来临时,只有当前进程的概况定时器得到更新,如果当前进程运行于用户态,则其虚拟定时器也得到更新。

此外,Linux内核对这三种定时器的处理是不同的,虚拟定时器和概况定时器到期时,内核向当前进程发送相应的信号:SIGVTALRM、SIGPROF;而实时定时器要执行的操作由real_timer决定,real_time是timer_list类型的变量(定义:struct timer_list real_timer),其中容纳了实时定时器的到期时间、定时间隔等信息,我们将在下一章详细讨论这些内容。

7.文件系统信息(File System)

进程可以打开或关闭文件,文件属于系统资源,Linux内核要对进程使用文件的情况进行记录。task_struct结构中有两个数据结构用于描述进程与文件相关的信息。其中,fs_struct中描述了两个VFS索引节点(VFS inode),这两个索引节点叫做root和pwd,分别指向进程的可执行映象所对应的根目录(home directory)和当前目录或工作目录。file_struct结构用来记录了进程打开的文件的描述符(descriptor)。如表4.9所示。

表4.9与文件系统相关的域

定义形式

解释

Sruct fs_struct *fs

进程的可执行映象所在的文件系统

Struct files_struct *files

进程打开的文件

在文件系统中,每个VFS索引节点唯一描述一个文件或目录,同时该节点也是向更低层的文件系统提供的统一的接口。

8.虚拟内存信息(Virtual Memory)

除了内核线程(kernel thread),每个进程都拥有自己的地址空间(也叫虚拟空间),用mm_struct来描述。另外Linux2.4还引入了另外一个域active_mm,这是为内核线程而引入。因为内核线程没有自己的地址空间,为了让内核线程与普通进程具有统一的上下文切换方式,当内核线程进行上下文切换时,让切换进来的线程的active_mm指向刚被调度出去的进程的active_mm(如果进程的mm域不为空,则其active_mm域与mm域相同)。内存信息如表4.10所示。

表4.10虚拟内存描述信息

定义形式

解释

Struct mm_struct *mm

描述进程的地址空间

Struct mm_struct *active_mm

内核线程所借用的地址空间

9.页面管理信息

当物理内存不足时,Linux内存管理子系统需要把内存中的部分页面交换到外存,其交换是以页为单位的。有关页面的描述信息如表4.11。

表4.11页面管理信息

定义形式

解释

Int swappable

进程占用的内存页面是否可换出

Unsigned long min_flat,maj_flt,nswap

进程累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数

Unsigned long cmin_flat,cmaj_flt,cnswap

本进程作为祖先进程,其所有层次子进程的累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数

10.对称多处理机(SMP)信息

Linux2.4对SMP进行了全面的支持,表4.12是与多处理机相关的几个域。

表4.12与多处理机相关的域

定义形式

解释

Int has_cpu

进程是否当前拥有CPU

Int processor

进程当前正在使用的CPU

Int lock_depth

上下文切换时内核锁的深度

11.和处理器相关的环境(上下文)信息(Processor Specific Context)

这里要特别注意标题:和“处理器”相关的环境信息。进程作为一个执行环境的综合,当系统调度某个进程执行,即为该进程建立完整的环境时,处理器(processor)的寄存器、堆栈等是必不可少的。因为不同的处理器对内部寄存器和堆栈的定义不尽相同,所以叫做“和处理器相关的环境”,也叫做“处理机状态”。当进程暂时停止运行时,处理机状态必须保存在进程的task_struct结构中,当进程被调度重新运行时再从中恢复这些环境,也就是恢复这些寄存器和堆栈的值。处理机信息如表4.13所示。

表4.13与处理机相关的信息

定义形式

解释

Struct thread_struct *tss

任务切换状态

12.其它

(1)struct wait_queue *wait_chldexit

在进程结束时,或发出系统调用wait4时,为了等待子进程的结束,而将自己(父进程)睡眠在该等待队列上,设置状态标志为TASK_INTERRUPTIBLE,并且把控制权转给调度程序。

(2)Struct rlimit rlim[RLIM_NLIMITS];

每一个进程可以通过系统调用setlimit和getlimit来限制它资源的使用。

(3)Int exit_code exit_signal;

程序的返回代码以及程序异常终止产生的信号,这些数据由父进程(子进程完成后)轮流查询。

(4)Char comm[16]

这个域存储进程执行的程序的名字,这个名字用在调试中。

(5)Unsigned long personality;

Linux可以运行X86平台上其它Unix操作系统生成的符合iBCS2标准的程序, personality进一步描述进程执行的程序属于何种Unix平台的“个性”信息。通常有PER_Linux,PER_Linux_32BIT,PER_Linux_EM86,PER_SVR4,PER_SVR3,PER_SCOSVR3,PER_WYSEV386,PER_ISCR4,PER_BSD,PER_XENIX和PER_MASK等,参见include/Linux/personality.h>。

(6)intdid_exec:1;

按POSIX要求设计的布尔量,区分进程正在执行老程序代码,还是用系统调用execve()装入一个新的程序。

(7)struct linux_binfmt *binfmt

指向进程所属的全局执行文件格式结构,共有a.out、script、elf、java等四种。

task_struct结构是进程实体的核心,Linux内核通过该结构来控制进程:首先通过其中的调度信息决定该进程是否运行;当该进程运行时,根据其中保存的处理机状态信息来恢复进程运行现场,然后根据虚拟内存信息,找到程序的正文和数据;通过其中的通信信息和其他进程实现同步、通信等合作。几乎所有的操作都要依赖该结构,所以,task_struct结构是一个进程存在的唯一标志。

参考资料:http://oss.org.cn/kernel-book/ch04/4.3.htm

2,使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone 。1,做好前期工作,包括下载新的menu和编译等。

e185e3e65761ee7fc2884c0a3145e836.png

2,建立调试环境,这些都在前面作业写过,不再阐述。

9ea536cdf5313d0098d5379f6c8752a0.png

3,设置断点:分别是

1,sys_clone

2,do_fork

3,dup_task_struct

4,copy_process

5,copy_thread

6,ret_from_fork

这里要注意的是,请在linux运行到出现MenuOS>>后才设置断点,不然的话将会耗费大量的时间。尤其是在实验楼平台。

8a904a3dffd89af29422df2032f67b2e.png

4,输入fork运行如下代码:

点击(此处)折叠或打开

#include 

#include 

#include 

int main(int argc, char * argv[])

{

int pid;

/* fork another process */

pid = fork();

if (pid 

{

/* error occurred */

fprintf(stderr,"Fork Failed!");

exit(-1);

}

else if (pid == 0)

{

/* child process */

printf("This is Child Process!\n");

}

else

{

/* parent process */

printf("This is Parent Process!\n");

/* parent will wait for the child to complete*/

wait(NULL);

printf("Child Complete!\n");

}

}

不出所料,qemu暂停,gdb跟踪到程序进入sys_clone函数:

f3d6d7e92993b570b574d16d1cb98a21.png

5,继续运行,系统进入了do_fork:

a98a385c6d6ef4defdb21e458ea620b2.png

6,单步运行调试代码,再次调用了do_fork:

56d98707f9e6705ec355f2a128f21853.png

7,继续单步调试,进入copy_process,进行对父进程的拷贝:(这一段代码C++的味道好浓)

7c073b8f48c122c028ac61829f5111ba.png

8,进入copy_thread,这张图是截自老师的,因为自己调试过头了:

78e48efdffd0e8b790d1d8d2229b2fce.png

9,给子进程分配堆栈空间(这段代码与之前的mykernel很像):

23cbbc2e89cf441467a7d8ec97e69e54.png

10,跟踪到了ret_from_fork:

2743e8646c32ff8a0d0cc18a9a08fcaa.png

11,单步调试,跟踪到汇编代码,接下来就跟踪不到了。

b06f0f68aacca3a419e2ef65771fcdb6.png

上面就是本次实验的全部内容。

3,分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构;

1,do_fork的代码对应如下:

点击(此处)折叠或打开

long do_fork(unsigned long clone_flags,

unsigned long stack_start,

unsigned long stack_size,

int __user *parent_tidptr,

int __user *child_tidptr)

{

struct task_struct *p;

int trace = 0;

long nr;

/*

* Determine whether and which event to report to ptracer. When

* called from kernel_thread or CLONE_UNTRACED is explicitly

* requested, no event is reported; otherwise, report if the event

* for the type of forking is enabled.

*/

if (!(clone_flags & CLONE_UNTRACED)) {

if (clone_flags & CLONE_VFORK)

trace = PTRACE_EVENT_VFORK;

else if ((clone_flags & CSIGNAL) != SIGCHLD)

trace = PTRACE_EVENT_CLONE;

else

trace = PTRACE_EVENT_FORK;

if (likely(!ptrace_event_enabled(current, trace)))

trace = 0;

}

p = copy_process(clone_flags, stack_start, stack_size,

child_tidptr, NULL, trace);

/*

* Do this prior waking up the new thread - the thread pointer

* might get invalid after that point, if the thread exits quickly.

*/

if (!IS_ERR(p)) {

struct completion vfork;

struct pid *pid;

trace_sched_process_fork(current, p);

pid = get_task_pid(p, PIDTYPE_PID);

nr = pid_vnr(pid);

if (clone_flags & CLONE_PARENT_SETTID)

put_user(nr, parent_tidptr);

if (clone_flags & CLONE_VFORK) {

p->vfork_done = &vfork;

init_completion(&vfork);

get_task_struct(p);

}

wake_up_new_task(p);

/* forking complete and child started to run, tell ptracer */

if (unlikely(trace))

ptrace_event_pid(trace, pid);

if (clone_flags & CLONE_VFORK) {

if (!wait_for_vfork_done(p, &vfork))

ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);

}

put_pid(pid);

} else {

nr = PTR_ERR(p);

}

return nr;

}

理解:1,第7到第9行进行一些基本声明。

2,17~27行是一些异常处理。

3,如果没有异常,28行进入copy_process。copy_process主要进行对父进程的复制,当然,也可以根据传入的参数进行修改。

4,在copy_process中(fork.c中的1240行),将会调用dup_task_struct,看名字就知道了,duplicate(复制)task(任务)struct(结构)。

5,在dup_task_struct完成后,返回到copy_process,而copy_porcess将会在fork.c中的1396行调用copy_thread。

6,在copy_thread中,主要进行子进程的初始化,代码高亮部分就是一些寄存器的数据初始化。

点击(此处)折叠或打开

int copy_thread(unsigned long clone_flags, unsigned long sp,

unsigned long arg, struct task_struct *p)

{

struct pt_regs *childregs = task_pt_regs(p);

struct task_struct *tsk;

int err;

p->thread.sp = (unsigned long) childregs;

p->thread.sp0 = (unsigned long) (childregs+1);

memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));

if (unlikely(p->flags & PF_KTHREAD)) {

/* kernel thread */

memset(childregs, 0, sizeof(struct pt_regs));

p->thread.ip = (unsigned long) ret_from_kernel_thread;

task_user_gs(p) = __KERNEL_STACK_CANARY;

childregs->ds=__USER_DS;

childregs->es=__USER_DS;

childregs->fs=__KERNEL_PERCPU;

childregs->bx=sp;/* function */

childregs->bp=arg;

childregs->orig_ax= -1;

childregs->cs=__KERNEL_CS|get_kernel_rpl();

childregs->flags=X86_EFLAGS_IF|X86_EFLAGS_FIXED;

p->thread.io_bitmap_ptr = NULL;

return 0;

}

*childregs = *current_pt_regs();

childregs->ax = 0;

if (sp)

childregs->sp = sp;

p->thread.ip = (unsigned long) ret_from_fork;

task_user_gs(p) = get_user_gs(current_pt_regs());

p->thread.io_bitmap_ptr = NULL;

tsk = current;

err = -ENOMEM;

if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {

p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,

IO_BITMAP_BYTES, GFP_KERNEL);

if (!p->thread.io_bitmap_ptr) {

p->thread.io_bitmap_max = 0;

return -ENOMEM;

}

set_tsk_thread_flag(p, TIF_IO_BITMAP);

}

err = 0;

/*

* Set a new TLS for the child thread?

*/

if (clone_flags & CLONE_SETTLS)

err = do_set_thread_area(p, -1,

(struct user_desc __user *)childregs->si, 0);

if (err && p->thread.io_bitmap_ptr) {

kfree(p->thread.io_bitmap_ptr);

p->thread.io_bitmap_max = 0;

}

return err;

}

7,然后do_fork也执行完了,返回到SyS_clone函数体中,下一步执行ret_from_fork。

8,关于ret_from_fork的源码,是放在arch\x86\kernel\entry_32.S中的,有兴趣的可以去看看。这个函数在process_32.c中有一个一个函数声明:

点击(此处)折叠或打开

asmlinkage void ret_from_fork(void) __asm__("ret_from_fork");

9,至此,本次实验的原理分析完毕。

后记:关于操作系统如何学习,关于Linux如何学习,linus有一句话:

18dca4c33683deff56b9a21ebe49cf0c.png

愿与你共勉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值