进程管理¶
task_struct
调度与进程切换¶
与调度相关的:
在 task_struct 中有几个字段是表示进程优先级的,在进程调度的时候会根据这几个字段来决定优先让哪个任务(进程或线程)开始执行
- static_prio: 用来保存静态优先级,可以调用 nice 系统直接来修改取值范围为 100~139
- rt_priority: 用来保存实时优先级,取值范围为 0~99
- prio: 用来保存动态优先级
- normal_prio: 它的值取决于静态优先级和调度策略
struct task_struct {
......
/*
*调度类。用 sched_class 对调度器进行抽象
*Stop调度器:stop_sched_class
*Deadline调度器:dl_sched_class
*RT调度器:rt_sched_class
*CFS调度器:cfs_sched_class
*IDLE-Task调度器:idle_sched_class
*/
const struct sched_class *sched_class;
//CFS调度实体
struct sched_entity se;
//RT调度实体
struct sched_rt_entity rt;
......
#ifdef CONFIG_CGROUP_SCHED
//任务组(在每个CPU上都会维护一个CFS调度实体、CFS运行队列; RT调度实体,RT运行队列)
struct task_group *sched_task_group;
#endif
//DL调度实体
struct sched_dl_entity dl;
......
/*
*进程的调度策略,有6种。
*限期进程调度策略:SCHED_DEADLINE。DL调度器
*实时进程调度策略:SCHED_FIFO,SCHED_RR。RT调度器
*普通进程调度策略:SCHED_NORMAL,SCHED_BATCH,SCHED_IDLE。CFS调度器
*/
unsigned int policy;
......
}
进程地址空间¶
struct mm_struct {
struct vm_area_struct * mmap; /* list of VMAs */
struct rb_root mm_rb;
unsigned long mmap_base; /* base of mmap area */
unsigned long task_size; /* size of task vm space */
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
}
用户mm¶
内核mm¶
进程文件系统信息¶
//file:include/linux/fs_struct.h
struct fs_struct {
...
struct path root, pwd;
};
//file:include/linux/path.h
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
fs_struct
包含两个path对象,每个path中都指向一个struct dentry
进程打开文件信息¶
namespace¶
用于隔离内核资源
具体实现:把一个或者多个进程的相关资源指定在同一个namespace中
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns;
struct net *net_ns;
};
命名空间包括PID命名空间、挂载点命名空间、网络命名空间等多个
fork()¶
do_fork()¶
do_fork
传入的第一个参数是一个flag选项,可以传入的值:
这里传入的是一个SIGCHILD(子进程在终止后向父进程发送SIGCHILD信号通知)
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
//复制一个 task_struct 出来
struct task_struct *p;
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace);
//子任务加入到就绪队列中去,等待调度器调度
wake_up_new_task(p);
...
}
在创建完毕后,调用 wake_up_new_task 将新创建的任务添加到就绪队列中,等待调度器调度执行
copy_process()¶
static struct task_struct *copy_process(...)
{
//3.1 复制进程 task_struct 结构体
struct task_struct *p;
p = dup_task_struct(current);
...
//3.2 拷贝 files_struct
retval = copy_files(clone_flags, p);
//3.3 拷贝 fs_struct
retval = copy_fs(clone_flags, p);
//3.4 拷贝 mm_struct
retval = copy_mm(clone_flags, p);
//3.5 拷贝进程的命名空间 nsproxy
retval = copy_namespaces(clone_flags, p);
//3.6 申请 pid && 设置进程号
pid = alloc_pid(p->nsproxy->pid_ns);
p->pid = pid_nr(pid);
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid;
......
}
复制进程task_struct¶
dup_task_struct
时传入的参数是current
,它表示的是当前进程- 在
dup_task_struct
里,会申请一个新的task_struct
内核对象,然后将当前进程复制给它 - 这次拷贝只会拷贝 task_struct 结构体本身,它内部包含的 mm_struct 等成员只是复制了指针,仍然指向和 current 相同的对象
dup_task_struct()¶
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
//申请 task_struct 内核对象
tsk = alloc_task_struct_node(node);
//复制 task_struct
err = arch_dup_task_struct(tsk, orig);
...
}
static struct kmem_cache *task_struct_cachep;
//申请 task_struct 内核对象
static inline struct task_struct *alloc_task_struct_node(int node)
{
return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}
//复制 task_struct
int arch_dup_task_struct(struct task_struct *dst,
struct task_struct *src)
{
*dst = *src;
return 0;
}
复制files_struct¶
static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
{
struct files_struct *oldf, *newf;
oldf = current->files;
if (clone_flags & CLONE_FILES) { //判断是否有CLONE_FILES,如果有的话就不执行dup_fd
atomic_inc(&oldf->count); //增加引用计数
goto out;
}
newf = dup_fd(oldf, &error);
tsk->files = newf;
...
}
由于前面fork()的时候没有传入CLONE_FILES标志,所以这里会执行到dup_fd函数
struct files_struct *dup_fd(struct files_struct *oldf, ...)
{
//为新 files_struct 申请内存
struct files_struct *newf;
newf = kmem_cache_alloc(files_cachep, GFP_KERNEL);
//初始化 & 拷贝
new_fdt->max_fds = NR_OPEN_DEFAULT;
...
}
复制fs_struct¶
static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{
struct fs_struct *fs = current->fs;
if (clone_flags & CLONE_FS) { //在创建进程的时候,没有传递 CLONE_FS 这个标志,所会进入到 copy_fs_struct 函数中申请新的 fs_struct 并进行赋值
fs->users++;
return 0;
}
tsk->fs = copy_fs_struct(fs);
return 0;
}
struct fs_struct *copy_fs_struct(struct fs_struct *old)
{
//申请内存
struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
//赋值
fs->users = 1;
fs->root = old->root;
fs->pwd = old->pwd;
...
return fs;
}
复制mm_struct¶
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm;
oldmm = current->mm; //表示当前的mm
if (clone_flags & CLONE_VM) { //do_fork 被调用时也没有传 CLONE_VM,所以会调用 dup_mm 申请一个新的地址空间出来
atomic_inc(&oldmm->mm_users);
mm = oldmm; //新的为当前的
goto good_mm;
}
mm = dup_mm(tsk);
good_mm:
return 0;
}
struct mm_struct *dup_mm(struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm = current->mm;
mm = allocate_mm();
memcpy(mm, oldmm, sizeof(*mm));
...
}
在 dup_mm 中,通过 allocate_mm 申请了新的 mm_struct,而且还将当前进程地址空间 current->mm 拷贝到新的 mm_struct 对象里了
复制进程命名空间¶
申请pid¶
static struct task_struct *copy_process(...)
{
...
//申请pid
pid = alloc_pid(p->nsproxy->pid_ns);
//赋值
p->pid = pid_nr(pid);
p->tgid = p->pid;
...
}
struct pid *alloc_pid(struct pid_namespace *ns)
{
//申请 pid 内核对象
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid)
goto out;
//调用到alloc_pidmap来分配一个空闲的pid编号
//注意,在每一个命令空间中都需要分配进程号
tmp = ns;
pid->level = ns->level;
for (i = ns->level; i >= 0; i--) {
nr = alloc_pidmap(tmp);
pid->numbers[i].nr = nr;
...
}
...
return pid
}
- 这里PID是一个结构体,先使用
kmem_cache_alloc
将其申请出来 - 接下来调用
alloc_pidmap
到 pid 命名空间中申请一个 pid 号出来,申请完后赋值记录
操作系统是如何记录使用过的进程号呢?
在Linux内部,为了节约内存,进程号是通过 bitmap 来管理的
在每一个 pid 命名空间内部,会有一个或者多个页面来作为 bitmap。其中每一个 bit 位(注意是 bit 位,不是字节)的 0 或者 1 的状态来表示当前序号的 pid 是否被占用
#define BITS_PER_PAGE (PAGE_SIZE * 8)
#define PIDMAP_ENTRIES ((PID_MAX_LIMIT+BITS_PER_PAGE-1)/BITS_PER_PAGE)
struct pid_namespace {
struct pidmap pidmap[PIDMAP_ENTRIES];
...
}
在 alloc_pidmap 中就是以 bit 的方式来遍历整个 bitmap,找到合适的未使用的 bit,将其设置为已使用,然后返回
static int alloc_pidmap(struct pid_namespace *pid_ns)
{
...
map = &pid_ns->pidmap[pid/BITS_PER_PAGE];
}
进入就绪队列¶
long do_fork(...)
{
//复制一个 task_struct 出来
struct task_struct *p;
p = copy_process(clone_flags, stack_start, ...);
//子任务加入到就绪队列中去,等待调度器调度
wake_up_new_task(p);
...
}