Linux内核设计与实现(三) linux进程管理 之 进程创建-2
2016-08-11 14:27
483 查看
一、进程创建
现代Unix内核通过引入三种不同的机制解决了这个问题:
写时复制技术允许父子进程读相同的物理页。只要两者中有一个试图写一个物理页。内核就把这个页的内容拷贝到一个新的物理页,并把这个新物理页分配给正在写的进程。
轻量级进程允许父子进程共享每进程在内核的很多数据结构,如页表,打开文件表及信号处理。
vfork()系统调用创建的进程共享其父进程的内存地址空间。为了防止父进程重写子进程需要的数据,阻塞父进程的执行,一直到子进程退出或执行一个新程序为止。
二、进程创建标志
CLONE_PARENT :创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS :子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES :子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS :在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND:子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE :若父进程被trace,子进程也被trace
CLONE_VFORK :父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM :子进程与父进程运行于相同的内存空间
CLONE_PID :子进程在创建时PID与父进程一致
CLONE_THREAD :Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
三、代码分析
do_fork()函数负责处理clone()、fork()和vfork()系统调用,执行时使用下列参数:
clone_flags与clone()的参数flags相同
stack_start与clone()的参数stack_start相同
regs指向通用寄存器值的指针,通用寄存器的值是从用户态切换到内核态时被保存到内核态堆栈中的。
stack_size未使用
parent_tidptr,child_tidptr与clone()中的对应参数ptid和ctid相同。
1.
int do_fork(unsigned long clone_flags, unsigned long stack_start,struct
pt_regs *regs, unsigned long stack_size)
{
int retval = ENOMEM;
struct task_struct *p;
DECLARE_MUTEX_LOCKED(sem);//声明信号量
if (clone_flags & CLONE_PID) {//CLONE_PID为1时,表示父子进程共享同一个进程号
if (current->pid)//只有0号进程才允许这样,别的进程都出错
return =-EPERM;
}
current->vfork_sem = &sem;
p = alloc_task_struct();//为子进程分配两个连续的物理页面
if (!p)//低端用于task_struct结构,高端用于系统空间堆栈
goto fork_out;
*p = *current;//整个数据结构的赋值,父进程的整个task_struct结构就复制给了子进程
retval = -EAGAIN;
//该进程所属的用户的进程数量不能超过上限
if (atomic_read(&p->user->processes) >= p->rlim[RLIMIT_NPROC].rlim_cur)
goto bad_fork_free;
atomic_inc(&p->user->__count);//该进程所属的用户进程计数递加
atomic_inc(&p->user->processes);//该进程所属的用户的进程数量递加
//是否超过内核限定的最大进程数量
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
//linux执行域,递增具体模块的数据结构中的计数器
get_exec_domain(p->exec_domain);
//每个进程所执行的程序属于某种可执行映像,对于不同模块的支持是通过模块安装来实现的。
if (p->binfmt && p->binfmt->module)
__MOD_INC_USE_COUNT(p->binfmt->module);//递增模块计数
p->did_exec = 0;//清零,记录进程执行execve()系统调用的次数
p->swappable = 0;//表示虚拟进程空间不允许换出
p->state = TASK_UNINTERRUPTIBLE;//先将进程设置为睡眠状态
copy_flags(clone_flags, p);//将clone_flags标志略加变换写入p->flags
p->pid = get_pid(clone_flags);//根据标志位返回进程PID号
p->run_list.next = NULL;//初始化进程的运行列表
p->run_list.prev = NULL;
if ((clone_flags & CLONE_VFORK) || !(clone_flags & CLONE_PARENT)) {
p->p_opptr = current;//子进程的父进程是current
if (!(p->ptrace & PT_PTRACED))
p->p_pptr = current;//子进程的父进程是current
}
p->p_cptr = NULL;//指向最年轻的子进程指针设为空
init_waitqueue_head(&p->wait_chldexit);//初始化等待队列头
p->vfork_sem = NULL;
spin_lock_init(&p->alloc_lock);//初始化锁
p->sigpending = 0;//等待信号清空
init_sigpending(&p->pending);//初始化子进程的待处理信号队列
p->it_real_value = p->it_virt_value = p->it_prof_value = 0;
p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;
init_timer(&p->real_timer);//初始化进程的实时定时器
p->real_timer.data = (unsigned
long) p;
p->leader = 0; /* session
leadership doesn't inherit */
p->tty_old_pgrp = 0;
p->times.tms_utime = p->times.tms_stime = 0;//时间初始化
p->times.tms_cutime = p->times.tms_cstime = 0;
#ifdef CONFIG_SMP
{//多处理器处理
int i;
p->has_cpu = 0;
p->processor = current->processor;//当前进程所在的处理器编号
for(i = 0; i < smp_num_cpus; i++)//初始化每cpu的时间变量
p->per_cpu_utime[i] = p->per_cpu_stime[i] = 0;
spin_lock_init(&p->sigmask_lock);
}
#endif
p->lock_depth = 1;//锁深度为1
p->start_time = jiffies;//进程创建时间
retval = -ENOMEM;
if (copy_files(clone_flags, p))//根据标志共享或者复制父进程已打开的文件结构
goto bad_fork_cleanup;
if (copy_fs(clone_flags, p))//根据标志共享或者复制父进程的fs_struct结构
goto bad_fork_cleanup_files;
if (copy_sighand(clone_flags, p))//根据标志共享或者复制父进程的信号处理
goto bad_fork_cleanup_fs;
if (copy_mm(clone_flags, p))//根据标志共享或者复制父进程用户空间
goto bad_fork_cleanup_sighand;
//前面分配了两个页面,低端数据task_struct结构已复制完毕,下面复制高端的系统空间堆栈
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);//复制父进程的系统空间堆栈
if (retval)
goto bad_fork_cleanup_sighand;
p->semundo = NULL;
p->parent_exec_id = p->self_exec_id;//执行域
p->swappable = 1;//子进程的存储页面可以换出
p->exit_signal = clone_flags & CSIGNAL;//子进程执行exit()时向父进程发送的信号
p->pdeath_signal = 0;//父进程在执行exit()时向子进程发送的信号
//子进程的运行时间配额,是父进程的一半
p->counter = (current->counter + 1) >> 1;
current->counter >>= 1;//父进程的运行时间配额也减半
if (!current->counter)//父进程的时间配额用完
current->need_resched = 1;//调度标志置1
retval = p->pid;
p->tgid = retval;
INIT_LIST_HEAD(&p->thread_group);
write_lock_irq(&tasklist_lock);
if (clone_flags & CLONE_THREAD) {//若创建的是线程
p->tgid = current->tgid;
list_add(&p->thread_group, ¤t->thread_group);//与父进程连接起来,形成一个线程组
}
SET_LINKS(p);//将子进程的task_struc结构链入内核进程队列
hash_pid(p);//将子进程的pid链入杂凑队列
nr_threads++;//系统中的进程数量递加一个
write_unlock_irq(&tasklist_lock);
if (p->ptrace & PT_PTRACED)
send_sig(SIGSTOP, p, 1);//子进程的停止信号
//唤醒子进程,使其挂入可执行队列进行调度
wake_up_process(p);
++total_forks;
fork_out:
//若设置了CLONE_VFORK,不能同时将父子进程返回用户空间,
//因为设置了这个标志,资源没有复制,而是共享的,共用同一个堆栈会引起混乱
//所以这里通过down(&sem)扣留了父进程的返回
if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;
}
static int get_pid(unsigned long flags)
{
static int next_safe = PID_MAX;//0x8000
struct task_struct *p;
if (flags & CLONE_PID)//CLONE_PID为1时,表示父子进程共享同一个进程号
return current->pid;//返回父进程的pid号
spin_lock(&lastpid_lock);
if((++last_pid) & 0xffff8000) {//进程号最大为0x7fff,超过它就设为300
last_pid = 300; //0-299是为系统进程保留的
goto inside;
}
if(last_pid >= next_safe) {//前边last_pid加1,是否大于0x8000
inside:
next_safe = PID_MAX;//32768
read_lock(&tasklist_lock);
repeat:
for_each_task(p) {//遍历所有进程,检测last_pid是否已被使用
if(p->pid == last_pid ||p->pgrp == last_pid ||p->session == last_pid) {
if(++last_pid >= next_safe) {
if(last_pid & 0xffff8000)////进程号最大为0x7fff,超过它就设为300
last_pid = 300;
next_safe = PID_MAX;
}
goto repeat;
}
if(p->pid > last_pid && next_safe > p->pid)
next_safe = p->pid;
if(p->pgrp > last_pid && next_safe > p->pgrp)
next_safe = p->pgrp;
if(p->session > last_pid && next_safe > p->session)
next_safe = p->session;
}
read_unlock(&tasklist_lock);
}
spin_unlock(&lastpid_lock);
return last_pid;
}
2.拷贝打开的文件
//表示打开的文件的数据结构
struct files_struct {
atomic_t count;
rwlock_t file_lock;
int max_fds;//file结构指针数组的容量
int max_fdset;//位图的容量
int next_fd;
struct file ** fd;//指向fd_array[]
fd_set *close_on_exec;//指向close_on_exec_init每次打开文件分配打开文件号时,就把close_on_exec所指向位图中的相应位设为0
fd_set *open_fds;//指向open_fds_init。每次打开文件分配打开文件号时,就把open_fds所指向位图中的相应位设为1
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];//下标是打开文件号
};
//根据标志共享或者复制父进程已打开的文件结构
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;//文件读写上下文结构
int open_files, nfds, size, i, error = 0;
oldf = current->files;//当前进程的files_struct
if (!oldf)
goto out;
if (clone_flags & CLONE_FILES) {//CLONE_FILES为1,递增父进程的files_struct计数,就返回,表示与父进程共享
atomic_inc(&oldf->count);//递增共享计数
goto out;
}
//CLONE_FILES为0,就要进行复制啦
tsk->files = NULL;
error = -ENOMEM;
newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);//通过slab分配器分配一个files_struct结构
if (!newf)
goto out;
//设置共享计数为1
atomic_set(&newf->count, 1);
newf->file_lock = RW_LOCK_UNLOCKED;
newf->next_fd = 0;
newf->max_fds = NR_OPEN_DEFAULT;//file结构指针数组的容量
newf->max_fdset = __FD_SETSIZE;//位图最大值
newf->close_on_exec = &newf->close_on_exec_init;//关闭文件位图
newf->open_fds = &newf->open_fds_init;//打开文件位图
newf->fd = &newf->fd_array[0];//fd位图,打开文件号
size = oldf->max_fdset;
if (size > __FD_SETSIZE) {//超过位图最大值
newf->max_fdset = 0;
write_lock(&newf->file_lock);
error = expand_fdset(newf, size);//扩展位图
write_unlock(&newf->file_lock);
if (error)
goto out_release;
}
read_lock(&oldf->file_lock);
//根据位图计算出打开文件的数量
open_files = count_open_files(oldf, size);
nfds = NR_OPEN_DEFAULT;
if (open_files > nfds) {//打开文件数量超过最大值
read_unlock(&oldf->file_lock);
newf->max_fds = 0;
write_lock(&newf->file_lock);
error = expand_fd_array(newf, open_files);//扩展打开文件的位图,即扩展file结构指针数组的容量
write_unlock(&newf->file_lock);
if (error)
goto out_release;
nfds = newf->max_fds;
read_lock(&oldf->file_lock);
}
old_fds = oldf->fd;
new_fds = newf->fd;//file结构指针数组
//拷贝两个位图
memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);
memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/8);
for (i = open_files; i != 0; --i){//遍历所有父进程已打开文件
struct file *f = *old_fds++;
if (f)
get_file(f);//递增文件的使用计数
*new_fds++ = f;//将父进程已打开文件的file结构复制给子进程
}
read_unlock(&oldf->file_lock);
//子进程剩余的file空间大小
size = (newf->max_fds-open_files) * sizeof(struct
file *);
//清0
memset(new_fds, 0, size);
//子进程最大位图数值超过父进程以打开文件的数量
if (newf->max_fdset > open_files) {
int left = (newf->max_fdset-open_files)/8;
nt start = open_files / (8 * sizeof(unsigned
long));
//超过的位图部分清0
memset(&newf->open_fds->fds_bits[start], 0, left);
memset(&newf->close_on_exec->fds_bits[start], 0, left);
}
tsk->files = newf;
error = 0;
out:
return error;
out_release:
free_fdset (newf->close_on_exec, newf->max_fdset);
free_fdset (newf->open_fds, newf->max_fdset);
kmem_cache_free(files_cachep, newf);
goto out;
}
3.拷贝文件系统信息结构fs_struct
static inline int copy_fs(unsigned long clone_flags, struct task_struct * tsk)
{
if (clone_flags & CLONE_FS) {//CLONE_FS为1,表示只是共享父进程的结构
atomic_inc(¤t->fs->count);//递增计数返回
return 0;
}//否则进行复制
tsk->fs = __copy_fs_struct(current->fs);
if (!tsk->fs)
return 1;
return 0;
}
static inline struct fs_struct *__copy_fs_struct(struct fs_struct *old)
{
//通过slab分配器分配一个fs_struct数据结构
struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
if (fs) {//递增结构中的共享计数,这里只是复制fs_struct,而结构中更深层次的内容只是共享
atomic_set(&fs->count, 1);//使用计数设为1
fs->lock = RW_LOCK_UNLOCKED;
fs->umask = old->umask;
read_lock(&old->lock);
fs->rootmnt = mntget(old->rootmnt);//挂载根目录
fs->root = dget(old->root);//父进程根目录
fs->pwdmnt = mntget(old->pwdmnt);
fs->pwd = dget(old->pwd);//父进程的当前目录
if (old->altroot) {//是否有替换根目录
fs->altrootmnt = mntget(old->altrootmnt);
fs->altroot = dget(old->altroot);
} else {
fs->altrootmnt = NULL;
fs->altroot = NULL;
}
read_unlock(&old->lock);
}
return fs;
}
4.拷贝信号处理
struct signal_struct {
atomic_t count;
struct k_sigaction action[_NSIG];//确定进程对各种信号的处理
spinlock_t siglock;
};
static inline int copy_sighand(unsigned long clone_flags, struct task_struct * tsk)
{
struct signal_struct *sig;
if (clone_flags & CLONE_SIGHAND) {//CLONE_SIGHAND标志为1,表示只是共享
atomic_inc(¤t->sig->count);//递增父进程的结构的共享计数
return 0;
}
//CLONE_SIGHAND=0,即复制,通过slab分配器分配一个signal_struct结构
sig = kmem_cache_alloc(sigact_cachep, GFP_KERNEL);
tsk->sig = sig;
if (!sig)
return 1;
spin_lock_init(&sig->siglock);
atomic_set(&sig->count, 1);//使用计数设为1
memcpy(tsk->sig->action, current->sig->action, sizeof(tsk->sig->action));//复制父进程的信号处理函数
return 0;
}
5.拷贝内存描述符mm_struct
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
struct mm_struct * mm, *oldmm;
int retval;
tsk->min_flt = tsk->maj_flt = 0;
tsk->cmin_flt = tsk->cmaj_flt = 0;
tsk->nswap = tsk->cnswap = 0;
tsk->mm = NULL;
tsk->active_mm = NULL;
oldmm = current->mm;//父进程的用户空间内存描述符
if (!oldmm)
return 0;
if (clone_flags & CLONE_VM) {//CLONE_VM=1时,只是共享父进程的虚拟空间
atomic_inc(&oldmm->mm_users);//递增父进程的共享计数
mm = oldmm;
goto good_mm;
}
//CLONE_VM=0时,才真正的进行内存描述符的复制,不止包括内存描述符的复制,还包括更深层次的结构的复制,
//重要的包括vm_area_struct数据结构和页面映射表的复制
retval = -ENOMEM;
mm = allocate_mm();//从slab分配器分配一个新的内存描述符
if (!mm)
goto fail_nomem;
//将父进程内存描述符内容复制给子进程
memcpy(mm, oldmm, sizeof(*mm));
if (!mm_init(mm))
goto fail_nomem;
down(&oldmm->mmap_sem);
retval = dup_mmap(mm);//对更深层次的结构,如vm_area_struct数据结构和页面映射表的复制
up(&oldmm->mmap_sem);
spin_lock(&mmlist_lock);
list_add(&mm->mmlist, &oldmm->mmlist);//将子进程的内存描述符插入队列
spin_unlock(&mmlist_lock);
if (retval)
goto free_pt;
copy_segments(tsk, mm);//处理进程可能具有LDT
if (init_new_context(tsk,mm))//对386是空语句
goto free_pt;
good_mm:
tsk->mm = mm;//将mm_struct结构链入task_struct结构
tsk->active_mm = mm;
return 0;
free_pt:
mmput(mm);
fail_nomem:
return retval;
}
static inline int dup_mmap(struct mm_struct * mm)
{
struct vm_area_struct * mpnt, *tmp, **pprev;
int retval;
flush_cache_mm(current->mm);
mm->locked_vm = 0;//初始化内存描述符的一些字段
mm->mmap = NULL;
mm->mmap_avl = NULL;
mm->mmap_cache = NULL;
mm->map_count = 0;
mm->cpu_vm_mask = 0;
mm->swap_cnt = 0;
mm->swap_address = 0;
pprev = &mm->mmap;//mm->mmap指向进程第一个线性区间的vm_area_struct结构
for (mpnt = current->mm->mmap ; mpnt ; mpnt = mpnt->vm_next) {//遍历进程所有的线性区间
struct file *file;
retval = -ENOMEM;
if(mpnt->vm_flags & VM_DONTCOPY)//线性区间设置不允许复制标志,就跳过
continue;
tmp = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//通过slab分配vm_area_struct结构
if (!tmp)
goto fail_nomem;
*tmp = *mpnt;//结构内容复制
tmp->vm_flags &= ~VM_LOCKED;//清除锁定页标志
tmp->vm_mm = mm;//指向内存描述符
mm->map_count++;//内存描述符中区间计数递加
tmp->vm_next = NULL;
file = tmp->vm_file;
if (file) {//如果区间映射文件
struct inode *inode = file->f_dentry->d_inode;
get_file(file);//递增文件使用计数
if (tmp->vm_flags & VM_DENYWRITE)
atomic_dec(&inode->i_writecount);//递减计数,不允许使用常规读写文件
spin_lock(&inode->i_mapping->i_shared_lock);
//vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构
if((tmp->vm_next_share = mpnt->vm_next_share) != NULL)
mpnt->vm_next_share->vm_pprev_share =&tmp->vm_next_share;
mpnt->vm_next_share = tmp;
tmp->vm_pprev_share = &mpnt->vm_next_share;
spin_unlock(&inode->i_mapping->i_shared_lock);
}
//为每个区间进行页面目录项和页面表项的复制
retval = copy_page_range(mm, current->mm, tmp);
if (!retval && tmp->vm_ops && tmp->vm_ops->open)
tmp->vm_ops->open(tmp);
*pprev = tmp;//链入到队列中
pprev = &tmp->vm_next;
if (retval)
goto fail_nomem;
}
retval = 0;
if (mm->map_count >= AVL_MIN_MAP_COUNT)//内存描述符中区间数量达到一定数量
build_mmap_avl(mm);//要建立AVL树(红-黑树)
fail_nomem:
flush_tlb_mm(current->mm);
return retval;
}
int copy_page_range(struct mm_struct *dst, struct
mm_struct *src,struct vm_area_struct *vma)
{
pgd_t * src_pgd, * dst_pgd;
unsigned long address = vma->vm_start;//区间起始地址
unsigned long end = vma->vm_end;//区间结束地址
unsigned long cow = (vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
src_pgd = pgd_offset(src, address)-1;//源空间起始地址的目录项
dst_pgd = pgd_offset(dst, address)-1;//目标空间起始地址的目录项
for (;;) {//页面目录项循环
pmd_t * src_pmd, * dst_pmd;
src_pgd++;
dst_pgd++;
if (pgd_none(*src_pgd))//源目录项是否为空
goto skip_copy_pmd_range;
if (pgd_bad(*src_pgd)) {//原目录项是否出错
pgd_ERROR(*src_pgd);
pgd_clear(src_pgd);
skip_copy_pmd_range:
address = (address + PGDIR_SIZE) & PGDIR_MASK;//递加到下一个目录项
if (!address || (address >= end))//超出范围,则退出
goto out;
continue;//下次目录项循环
}
if (pgd_none(*dst_pgd)) {//目标目录项是否为空
if (!pmd_alloc(dst_pgd, 0))//为空就分配一个目录项
goto nomem;
}
src_pmd = pmd_offset(src_pgd, address);//源中间目录项
dst_pmd = pmd_offset(dst_pgd, address);//目标中间目录项
do {//中间目录项循环
pte_t * src_pte, * dst_pte;
if (pmd_none(*src_pmd))
goto skip_copy_pte_range;
if (pmd_bad(*src_pmd)) {//中间目录项出错
pmd_ERROR(*src_pmd);
pmd_clear(src_pmd);
skip_copy_pte_range:
address = (address + PMD_SIZE) & PMD_MASK;//跳到下一个中间目录项
if (address >= end)
goto out;
goto cont_copy_pmd_range;
}
if (pmd_none(*dst_pmd)) {//中间目录项为空就分配一个
if (!pte_alloc(dst_pmd, 0)
14e77
)
goto nomem;
}
//根据中间目录项,找到页表
src_pte = pte_offset(src_pmd, address);
dst_pte = pte_offset(dst_pmd, address);
do {//页表项循环
pte_t pte = *src_pte;
struct page *ptepage;
if (pte_none(pte))//源页表为空
goto cont_copy_pte_range_noset;
if (!pte_present(pte)) {//源页表不在内存中,已调出到交换设备
swap_duplicate(pte_to_swp_entry(pte));//递增共享计数
goto cont_copy_pte_range;
}
ptepage = pte_page(pte);//根据页表找到page结构
if ((!VALID_PAGE(ptepage)) ||PageReserved(ptepage))//不是有效的页面或者是保留的页面
goto cont_copy_pte_range;//将表项复制到子进程的表项中
if (cow) {//写时复制
ptep_set_wrprotect(src_pte);//将父进程的页改成写保护
pte = *src_pte;//源页表内容拷贝给pte
}
if (vma->vm_flags & VM_SHARED)//若区间共享
pte = pte_mkclean(pte);//清除页面的写保护,即不会写时复制
pte = pte_mkold(pte);//清除
Accessed 标志(把此页标记为未访问)。
get_page(ptepage);//增加page页计数
cont_copy_pte_range:
set_pte(dst_pte, pte);//将页面表项写入目标页表中
cont_copy_pte_range_noset:
address += PAGE_SIZE;//下一页
if (address >= end)
goto out;
src_pte++;
dst_pte++;
} while ((unsigned
long)src_pte & PTE_TABLE_MASK);
cont_copy_pmd_range:
src_pmd++;
dst_pmd++;
} while ((unsigned
long)src_pmd & PMD_TABLE_MASK);
}
out:
return 0;
nomem:
return ENOMEM;
}
void copy_segments(struct task_struct *p, struct mm_struct *new_mm)
{
struct mm_struct * old_mm;
void *old_ldt, *ldt;
ldt = NULL;
old_mm = current->mm;
if (old_mm && (old_ldt = old_mm->context.segments) != NULL) {
ldt = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
if (!ldt)
printk(KERN_WARNING "ldt allocation failed\n");
else
memcpy(ldt, old_ldt, LDT_ENTRIES*LDT_ENTRY_SIZE);
}
new_mm->context.segments = ldt;
}
6.将父进程(当前进程current)的内核堆栈内容复制到子进程(p)的内核堆栈中
int copy_thread(int nr, unsigned
long clone_flags, unsigned long esp,unsigned long unused,struct task_struct * p, struct
pt_regs * regs)
{
struct pt_regs * childregs;
//p是子进程的task_struct结构,也是两个物理页面的起始地址,下边得到子进程系统空间堆栈的struct pt_regs结构
childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned
long) p))-1;
struct_cpy(childregs, regs);//复制父进程的struct
pt_regs结构
//调整子进程内核堆栈的局部内容(子进程内核堆栈的内容和父进程不完全一样)
childregs->eax = 0;//从系统调用返回时的返回值,这个就是为什么子进程返回值是0
childregs->esp = esp;//esp寄存器是子进程在用户空间的堆栈位置,实际上还是指向父进程原来在用户空间的堆栈位置
//所以子进程和父进程在用户空间具有相同的返回地址,然后才会因为用户空间的程序而分开
//thread_struct thread记录着进程在切换时的系统空间堆栈指针,返回地址等信息
p->thread.esp = (unsigned
long) childregs;//子进程系统空间堆栈struct
pt_regs结构的起始地址
p->thread.esp0 = (unsigned
long) (childregs+1);//子进程系统空间堆栈的顶端
p->thread.eip = (unsigned
long) ret_from_fork;//调用或中断的返回地址,使创建的子进程在首次被调度运行时就从这运行
savesegment(fs,p->thread.fs);//保存段寄存器fs
savesegment(gs,p->thread.gs);//保存段寄存器gs
unlazy_fpu(current);
struct_cpy(&p->thread.i387, ¤t->thread.i387);
return 0;
}
现代Unix内核通过引入三种不同的机制解决了这个问题:
写时复制技术允许父子进程读相同的物理页。只要两者中有一个试图写一个物理页。内核就把这个页的内容拷贝到一个新的物理页,并把这个新物理页分配给正在写的进程。
轻量级进程允许父子进程共享每进程在内核的很多数据结构,如页表,打开文件表及信号处理。
vfork()系统调用创建的进程共享其父进程的内存地址空间。为了防止父进程重写子进程需要的数据,阻塞父进程的执行,一直到子进程退出或执行一个新程序为止。
二、进程创建标志
CLONE_PARENT :创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS :子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES :子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS :在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND:子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE :若父进程被trace,子进程也被trace
CLONE_VFORK :父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM :子进程与父进程运行于相同的内存空间
CLONE_PID :子进程在创建时PID与父进程一致
CLONE_THREAD :Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
三、代码分析
do_fork()函数负责处理clone()、fork()和vfork()系统调用,执行时使用下列参数:
clone_flags与clone()的参数flags相同
stack_start与clone()的参数stack_start相同
regs指向通用寄存器值的指针,通用寄存器的值是从用户态切换到内核态时被保存到内核态堆栈中的。
stack_size未使用
parent_tidptr,child_tidptr与clone()中的对应参数ptid和ctid相同。
1.
int do_fork(unsigned long clone_flags, unsigned long stack_start,struct
pt_regs *regs, unsigned long stack_size)
{
int retval = ENOMEM;
struct task_struct *p;
DECLARE_MUTEX_LOCKED(sem);//声明信号量
if (clone_flags & CLONE_PID) {//CLONE_PID为1时,表示父子进程共享同一个进程号
if (current->pid)//只有0号进程才允许这样,别的进程都出错
return =-EPERM;
}
current->vfork_sem = &sem;
p = alloc_task_struct();//为子进程分配两个连续的物理页面
if (!p)//低端用于task_struct结构,高端用于系统空间堆栈
goto fork_out;
*p = *current;//整个数据结构的赋值,父进程的整个task_struct结构就复制给了子进程
retval = -EAGAIN;
//该进程所属的用户的进程数量不能超过上限
if (atomic_read(&p->user->processes) >= p->rlim[RLIMIT_NPROC].rlim_cur)
goto bad_fork_free;
atomic_inc(&p->user->__count);//该进程所属的用户进程计数递加
atomic_inc(&p->user->processes);//该进程所属的用户的进程数量递加
//是否超过内核限定的最大进程数量
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
//linux执行域,递增具体模块的数据结构中的计数器
get_exec_domain(p->exec_domain);
//每个进程所执行的程序属于某种可执行映像,对于不同模块的支持是通过模块安装来实现的。
if (p->binfmt && p->binfmt->module)
__MOD_INC_USE_COUNT(p->binfmt->module);//递增模块计数
p->did_exec = 0;//清零,记录进程执行execve()系统调用的次数
p->swappable = 0;//表示虚拟进程空间不允许换出
p->state = TASK_UNINTERRUPTIBLE;//先将进程设置为睡眠状态
copy_flags(clone_flags, p);//将clone_flags标志略加变换写入p->flags
p->pid = get_pid(clone_flags);//根据标志位返回进程PID号
p->run_list.next = NULL;//初始化进程的运行列表
p->run_list.prev = NULL;
if ((clone_flags & CLONE_VFORK) || !(clone_flags & CLONE_PARENT)) {
p->p_opptr = current;//子进程的父进程是current
if (!(p->ptrace & PT_PTRACED))
p->p_pptr = current;//子进程的父进程是current
}
p->p_cptr = NULL;//指向最年轻的子进程指针设为空
init_waitqueue_head(&p->wait_chldexit);//初始化等待队列头
p->vfork_sem = NULL;
spin_lock_init(&p->alloc_lock);//初始化锁
p->sigpending = 0;//等待信号清空
init_sigpending(&p->pending);//初始化子进程的待处理信号队列
p->it_real_value = p->it_virt_value = p->it_prof_value = 0;
p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;
init_timer(&p->real_timer);//初始化进程的实时定时器
p->real_timer.data = (unsigned
long) p;
p->leader = 0; /* session
leadership doesn't inherit */
p->tty_old_pgrp = 0;
p->times.tms_utime = p->times.tms_stime = 0;//时间初始化
p->times.tms_cutime = p->times.tms_cstime = 0;
#ifdef CONFIG_SMP
{//多处理器处理
int i;
p->has_cpu = 0;
p->processor = current->processor;//当前进程所在的处理器编号
for(i = 0; i < smp_num_cpus; i++)//初始化每cpu的时间变量
p->per_cpu_utime[i] = p->per_cpu_stime[i] = 0;
spin_lock_init(&p->sigmask_lock);
}
#endif
p->lock_depth = 1;//锁深度为1
p->start_time = jiffies;//进程创建时间
retval = -ENOMEM;
if (copy_files(clone_flags, p))//根据标志共享或者复制父进程已打开的文件结构
goto bad_fork_cleanup;
if (copy_fs(clone_flags, p))//根据标志共享或者复制父进程的fs_struct结构
goto bad_fork_cleanup_files;
if (copy_sighand(clone_flags, p))//根据标志共享或者复制父进程的信号处理
goto bad_fork_cleanup_fs;
if (copy_mm(clone_flags, p))//根据标志共享或者复制父进程用户空间
goto bad_fork_cleanup_sighand;
//前面分配了两个页面,低端数据task_struct结构已复制完毕,下面复制高端的系统空间堆栈
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);//复制父进程的系统空间堆栈
if (retval)
goto bad_fork_cleanup_sighand;
p->semundo = NULL;
p->parent_exec_id = p->self_exec_id;//执行域
p->swappable = 1;//子进程的存储页面可以换出
p->exit_signal = clone_flags & CSIGNAL;//子进程执行exit()时向父进程发送的信号
p->pdeath_signal = 0;//父进程在执行exit()时向子进程发送的信号
//子进程的运行时间配额,是父进程的一半
p->counter = (current->counter + 1) >> 1;
current->counter >>= 1;//父进程的运行时间配额也减半
if (!current->counter)//父进程的时间配额用完
current->need_resched = 1;//调度标志置1
retval = p->pid;
p->tgid = retval;
INIT_LIST_HEAD(&p->thread_group);
write_lock_irq(&tasklist_lock);
if (clone_flags & CLONE_THREAD) {//若创建的是线程
p->tgid = current->tgid;
list_add(&p->thread_group, ¤t->thread_group);//与父进程连接起来,形成一个线程组
}
SET_LINKS(p);//将子进程的task_struc结构链入内核进程队列
hash_pid(p);//将子进程的pid链入杂凑队列
nr_threads++;//系统中的进程数量递加一个
write_unlock_irq(&tasklist_lock);
if (p->ptrace & PT_PTRACED)
send_sig(SIGSTOP, p, 1);//子进程的停止信号
//唤醒子进程,使其挂入可执行队列进行调度
wake_up_process(p);
++total_forks;
fork_out:
//若设置了CLONE_VFORK,不能同时将父子进程返回用户空间,
//因为设置了这个标志,资源没有复制,而是共享的,共用同一个堆栈会引起混乱
//所以这里通过down(&sem)扣留了父进程的返回
if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;
}
static int get_pid(unsigned long flags)
{
static int next_safe = PID_MAX;//0x8000
struct task_struct *p;
if (flags & CLONE_PID)//CLONE_PID为1时,表示父子进程共享同一个进程号
return current->pid;//返回父进程的pid号
spin_lock(&lastpid_lock);
if((++last_pid) & 0xffff8000) {//进程号最大为0x7fff,超过它就设为300
last_pid = 300; //0-299是为系统进程保留的
goto inside;
}
if(last_pid >= next_safe) {//前边last_pid加1,是否大于0x8000
inside:
next_safe = PID_MAX;//32768
read_lock(&tasklist_lock);
repeat:
for_each_task(p) {//遍历所有进程,检测last_pid是否已被使用
if(p->pid == last_pid ||p->pgrp == last_pid ||p->session == last_pid) {
if(++last_pid >= next_safe) {
if(last_pid & 0xffff8000)////进程号最大为0x7fff,超过它就设为300
last_pid = 300;
next_safe = PID_MAX;
}
goto repeat;
}
if(p->pid > last_pid && next_safe > p->pid)
next_safe = p->pid;
if(p->pgrp > last_pid && next_safe > p->pgrp)
next_safe = p->pgrp;
if(p->session > last_pid && next_safe > p->session)
next_safe = p->session;
}
read_unlock(&tasklist_lock);
}
spin_unlock(&lastpid_lock);
return last_pid;
}
2.拷贝打开的文件
//表示打开的文件的数据结构
struct files_struct {
atomic_t count;
rwlock_t file_lock;
int max_fds;//file结构指针数组的容量
int max_fdset;//位图的容量
int next_fd;
struct file ** fd;//指向fd_array[]
fd_set *close_on_exec;//指向close_on_exec_init每次打开文件分配打开文件号时,就把close_on_exec所指向位图中的相应位设为0
fd_set *open_fds;//指向open_fds_init。每次打开文件分配打开文件号时,就把open_fds所指向位图中的相应位设为1
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];//下标是打开文件号
};
//根据标志共享或者复制父进程已打开的文件结构
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;//文件读写上下文结构
int open_files, nfds, size, i, error = 0;
oldf = current->files;//当前进程的files_struct
if (!oldf)
goto out;
if (clone_flags & CLONE_FILES) {//CLONE_FILES为1,递增父进程的files_struct计数,就返回,表示与父进程共享
atomic_inc(&oldf->count);//递增共享计数
goto out;
}
//CLONE_FILES为0,就要进行复制啦
tsk->files = NULL;
error = -ENOMEM;
newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);//通过slab分配器分配一个files_struct结构
if (!newf)
goto out;
//设置共享计数为1
atomic_set(&newf->count, 1);
newf->file_lock = RW_LOCK_UNLOCKED;
newf->next_fd = 0;
newf->max_fds = NR_OPEN_DEFAULT;//file结构指针数组的容量
newf->max_fdset = __FD_SETSIZE;//位图最大值
newf->close_on_exec = &newf->close_on_exec_init;//关闭文件位图
newf->open_fds = &newf->open_fds_init;//打开文件位图
newf->fd = &newf->fd_array[0];//fd位图,打开文件号
size = oldf->max_fdset;
if (size > __FD_SETSIZE) {//超过位图最大值
newf->max_fdset = 0;
write_lock(&newf->file_lock);
error = expand_fdset(newf, size);//扩展位图
write_unlock(&newf->file_lock);
if (error)
goto out_release;
}
read_lock(&oldf->file_lock);
//根据位图计算出打开文件的数量
open_files = count_open_files(oldf, size);
nfds = NR_OPEN_DEFAULT;
if (open_files > nfds) {//打开文件数量超过最大值
read_unlock(&oldf->file_lock);
newf->max_fds = 0;
write_lock(&newf->file_lock);
error = expand_fd_array(newf, open_files);//扩展打开文件的位图,即扩展file结构指针数组的容量
write_unlock(&newf->file_lock);
if (error)
goto out_release;
nfds = newf->max_fds;
read_lock(&oldf->file_lock);
}
old_fds = oldf->fd;
new_fds = newf->fd;//file结构指针数组
//拷贝两个位图
memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);
memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/8);
for (i = open_files; i != 0; --i){//遍历所有父进程已打开文件
struct file *f = *old_fds++;
if (f)
get_file(f);//递增文件的使用计数
*new_fds++ = f;//将父进程已打开文件的file结构复制给子进程
}
read_unlock(&oldf->file_lock);
//子进程剩余的file空间大小
size = (newf->max_fds-open_files) * sizeof(struct
file *);
//清0
memset(new_fds, 0, size);
//子进程最大位图数值超过父进程以打开文件的数量
if (newf->max_fdset > open_files) {
int left = (newf->max_fdset-open_files)/8;
nt start = open_files / (8 * sizeof(unsigned
long));
//超过的位图部分清0
memset(&newf->open_fds->fds_bits[start], 0, left);
memset(&newf->close_on_exec->fds_bits[start], 0, left);
}
tsk->files = newf;
error = 0;
out:
return error;
out_release:
free_fdset (newf->close_on_exec, newf->max_fdset);
free_fdset (newf->open_fds, newf->max_fdset);
kmem_cache_free(files_cachep, newf);
goto out;
}
3.拷贝文件系统信息结构fs_struct
static inline int copy_fs(unsigned long clone_flags, struct task_struct * tsk)
{
if (clone_flags & CLONE_FS) {//CLONE_FS为1,表示只是共享父进程的结构
atomic_inc(¤t->fs->count);//递增计数返回
return 0;
}//否则进行复制
tsk->fs = __copy_fs_struct(current->fs);
if (!tsk->fs)
return 1;
return 0;
}
static inline struct fs_struct *__copy_fs_struct(struct fs_struct *old)
{
//通过slab分配器分配一个fs_struct数据结构
struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
if (fs) {//递增结构中的共享计数,这里只是复制fs_struct,而结构中更深层次的内容只是共享
atomic_set(&fs->count, 1);//使用计数设为1
fs->lock = RW_LOCK_UNLOCKED;
fs->umask = old->umask;
read_lock(&old->lock);
fs->rootmnt = mntget(old->rootmnt);//挂载根目录
fs->root = dget(old->root);//父进程根目录
fs->pwdmnt = mntget(old->pwdmnt);
fs->pwd = dget(old->pwd);//父进程的当前目录
if (old->altroot) {//是否有替换根目录
fs->altrootmnt = mntget(old->altrootmnt);
fs->altroot = dget(old->altroot);
} else {
fs->altrootmnt = NULL;
fs->altroot = NULL;
}
read_unlock(&old->lock);
}
return fs;
}
4.拷贝信号处理
struct signal_struct {
atomic_t count;
struct k_sigaction action[_NSIG];//确定进程对各种信号的处理
spinlock_t siglock;
};
static inline int copy_sighand(unsigned long clone_flags, struct task_struct * tsk)
{
struct signal_struct *sig;
if (clone_flags & CLONE_SIGHAND) {//CLONE_SIGHAND标志为1,表示只是共享
atomic_inc(¤t->sig->count);//递增父进程的结构的共享计数
return 0;
}
//CLONE_SIGHAND=0,即复制,通过slab分配器分配一个signal_struct结构
sig = kmem_cache_alloc(sigact_cachep, GFP_KERNEL);
tsk->sig = sig;
if (!sig)
return 1;
spin_lock_init(&sig->siglock);
atomic_set(&sig->count, 1);//使用计数设为1
memcpy(tsk->sig->action, current->sig->action, sizeof(tsk->sig->action));//复制父进程的信号处理函数
return 0;
}
5.拷贝内存描述符mm_struct
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
struct mm_struct * mm, *oldmm;
int retval;
tsk->min_flt = tsk->maj_flt = 0;
tsk->cmin_flt = tsk->cmaj_flt = 0;
tsk->nswap = tsk->cnswap = 0;
tsk->mm = NULL;
tsk->active_mm = NULL;
oldmm = current->mm;//父进程的用户空间内存描述符
if (!oldmm)
return 0;
if (clone_flags & CLONE_VM) {//CLONE_VM=1时,只是共享父进程的虚拟空间
atomic_inc(&oldmm->mm_users);//递增父进程的共享计数
mm = oldmm;
goto good_mm;
}
//CLONE_VM=0时,才真正的进行内存描述符的复制,不止包括内存描述符的复制,还包括更深层次的结构的复制,
//重要的包括vm_area_struct数据结构和页面映射表的复制
retval = -ENOMEM;
mm = allocate_mm();//从slab分配器分配一个新的内存描述符
if (!mm)
goto fail_nomem;
//将父进程内存描述符内容复制给子进程
memcpy(mm, oldmm, sizeof(*mm));
if (!mm_init(mm))
goto fail_nomem;
down(&oldmm->mmap_sem);
retval = dup_mmap(mm);//对更深层次的结构,如vm_area_struct数据结构和页面映射表的复制
up(&oldmm->mmap_sem);
spin_lock(&mmlist_lock);
list_add(&mm->mmlist, &oldmm->mmlist);//将子进程的内存描述符插入队列
spin_unlock(&mmlist_lock);
if (retval)
goto free_pt;
copy_segments(tsk, mm);//处理进程可能具有LDT
if (init_new_context(tsk,mm))//对386是空语句
goto free_pt;
good_mm:
tsk->mm = mm;//将mm_struct结构链入task_struct结构
tsk->active_mm = mm;
return 0;
free_pt:
mmput(mm);
fail_nomem:
return retval;
}
static inline int dup_mmap(struct mm_struct * mm)
{
struct vm_area_struct * mpnt, *tmp, **pprev;
int retval;
flush_cache_mm(current->mm);
mm->locked_vm = 0;//初始化内存描述符的一些字段
mm->mmap = NULL;
mm->mmap_avl = NULL;
mm->mmap_cache = NULL;
mm->map_count = 0;
mm->cpu_vm_mask = 0;
mm->swap_cnt = 0;
mm->swap_address = 0;
pprev = &mm->mmap;//mm->mmap指向进程第一个线性区间的vm_area_struct结构
for (mpnt = current->mm->mmap ; mpnt ; mpnt = mpnt->vm_next) {//遍历进程所有的线性区间
struct file *file;
retval = -ENOMEM;
if(mpnt->vm_flags & VM_DONTCOPY)//线性区间设置不允许复制标志,就跳过
continue;
tmp = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//通过slab分配vm_area_struct结构
if (!tmp)
goto fail_nomem;
*tmp = *mpnt;//结构内容复制
tmp->vm_flags &= ~VM_LOCKED;//清除锁定页标志
tmp->vm_mm = mm;//指向内存描述符
mm->map_count++;//内存描述符中区间计数递加
tmp->vm_next = NULL;
file = tmp->vm_file;
if (file) {//如果区间映射文件
struct inode *inode = file->f_dentry->d_inode;
get_file(file);//递增文件使用计数
if (tmp->vm_flags & VM_DENYWRITE)
atomic_dec(&inode->i_writecount);//递减计数,不允许使用常规读写文件
spin_lock(&inode->i_mapping->i_shared_lock);
//vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构
if((tmp->vm_next_share = mpnt->vm_next_share) != NULL)
mpnt->vm_next_share->vm_pprev_share =&tmp->vm_next_share;
mpnt->vm_next_share = tmp;
tmp->vm_pprev_share = &mpnt->vm_next_share;
spin_unlock(&inode->i_mapping->i_shared_lock);
}
//为每个区间进行页面目录项和页面表项的复制
retval = copy_page_range(mm, current->mm, tmp);
if (!retval && tmp->vm_ops && tmp->vm_ops->open)
tmp->vm_ops->open(tmp);
*pprev = tmp;//链入到队列中
pprev = &tmp->vm_next;
if (retval)
goto fail_nomem;
}
retval = 0;
if (mm->map_count >= AVL_MIN_MAP_COUNT)//内存描述符中区间数量达到一定数量
build_mmap_avl(mm);//要建立AVL树(红-黑树)
fail_nomem:
flush_tlb_mm(current->mm);
return retval;
}
int copy_page_range(struct mm_struct *dst, struct
mm_struct *src,struct vm_area_struct *vma)
{
pgd_t * src_pgd, * dst_pgd;
unsigned long address = vma->vm_start;//区间起始地址
unsigned long end = vma->vm_end;//区间结束地址
unsigned long cow = (vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
src_pgd = pgd_offset(src, address)-1;//源空间起始地址的目录项
dst_pgd = pgd_offset(dst, address)-1;//目标空间起始地址的目录项
for (;;) {//页面目录项循环
pmd_t * src_pmd, * dst_pmd;
src_pgd++;
dst_pgd++;
if (pgd_none(*src_pgd))//源目录项是否为空
goto skip_copy_pmd_range;
if (pgd_bad(*src_pgd)) {//原目录项是否出错
pgd_ERROR(*src_pgd);
pgd_clear(src_pgd);
skip_copy_pmd_range:
address = (address + PGDIR_SIZE) & PGDIR_MASK;//递加到下一个目录项
if (!address || (address >= end))//超出范围,则退出
goto out;
continue;//下次目录项循环
}
if (pgd_none(*dst_pgd)) {//目标目录项是否为空
if (!pmd_alloc(dst_pgd, 0))//为空就分配一个目录项
goto nomem;
}
src_pmd = pmd_offset(src_pgd, address);//源中间目录项
dst_pmd = pmd_offset(dst_pgd, address);//目标中间目录项
do {//中间目录项循环
pte_t * src_pte, * dst_pte;
if (pmd_none(*src_pmd))
goto skip_copy_pte_range;
if (pmd_bad(*src_pmd)) {//中间目录项出错
pmd_ERROR(*src_pmd);
pmd_clear(src_pmd);
skip_copy_pte_range:
address = (address + PMD_SIZE) & PMD_MASK;//跳到下一个中间目录项
if (address >= end)
goto out;
goto cont_copy_pmd_range;
}
if (pmd_none(*dst_pmd)) {//中间目录项为空就分配一个
if (!pte_alloc(dst_pmd, 0)
14e77
)
goto nomem;
}
//根据中间目录项,找到页表
src_pte = pte_offset(src_pmd, address);
dst_pte = pte_offset(dst_pmd, address);
do {//页表项循环
pte_t pte = *src_pte;
struct page *ptepage;
if (pte_none(pte))//源页表为空
goto cont_copy_pte_range_noset;
if (!pte_present(pte)) {//源页表不在内存中,已调出到交换设备
swap_duplicate(pte_to_swp_entry(pte));//递增共享计数
goto cont_copy_pte_range;
}
ptepage = pte_page(pte);//根据页表找到page结构
if ((!VALID_PAGE(ptepage)) ||PageReserved(ptepage))//不是有效的页面或者是保留的页面
goto cont_copy_pte_range;//将表项复制到子进程的表项中
if (cow) {//写时复制
ptep_set_wrprotect(src_pte);//将父进程的页改成写保护
pte = *src_pte;//源页表内容拷贝给pte
}
if (vma->vm_flags & VM_SHARED)//若区间共享
pte = pte_mkclean(pte);//清除页面的写保护,即不会写时复制
pte = pte_mkold(pte);//清除
Accessed 标志(把此页标记为未访问)。
get_page(ptepage);//增加page页计数
cont_copy_pte_range:
set_pte(dst_pte, pte);//将页面表项写入目标页表中
cont_copy_pte_range_noset:
address += PAGE_SIZE;//下一页
if (address >= end)
goto out;
src_pte++;
dst_pte++;
} while ((unsigned
long)src_pte & PTE_TABLE_MASK);
cont_copy_pmd_range:
src_pmd++;
dst_pmd++;
} while ((unsigned
long)src_pmd & PMD_TABLE_MASK);
}
out:
return 0;
nomem:
return ENOMEM;
}
void copy_segments(struct task_struct *p, struct mm_struct *new_mm)
{
struct mm_struct * old_mm;
void *old_ldt, *ldt;
ldt = NULL;
old_mm = current->mm;
if (old_mm && (old_ldt = old_mm->context.segments) != NULL) {
ldt = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
if (!ldt)
printk(KERN_WARNING "ldt allocation failed\n");
else
memcpy(ldt, old_ldt, LDT_ENTRIES*LDT_ENTRY_SIZE);
}
new_mm->context.segments = ldt;
}
6.将父进程(当前进程current)的内核堆栈内容复制到子进程(p)的内核堆栈中
int copy_thread(int nr, unsigned
long clone_flags, unsigned long esp,unsigned long unused,struct task_struct * p, struct
pt_regs * regs)
{
struct pt_regs * childregs;
//p是子进程的task_struct结构,也是两个物理页面的起始地址,下边得到子进程系统空间堆栈的struct pt_regs结构
childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned
long) p))-1;
struct_cpy(childregs, regs);//复制父进程的struct
pt_regs结构
//调整子进程内核堆栈的局部内容(子进程内核堆栈的内容和父进程不完全一样)
childregs->eax = 0;//从系统调用返回时的返回值,这个就是为什么子进程返回值是0
childregs->esp = esp;//esp寄存器是子进程在用户空间的堆栈位置,实际上还是指向父进程原来在用户空间的堆栈位置
//所以子进程和父进程在用户空间具有相同的返回地址,然后才会因为用户空间的程序而分开
//thread_struct thread记录着进程在切换时的系统空间堆栈指针,返回地址等信息
p->thread.esp = (unsigned
long) childregs;//子进程系统空间堆栈struct
pt_regs结构的起始地址
p->thread.esp0 = (unsigned
long) (childregs+1);//子进程系统空间堆栈的顶端
p->thread.eip = (unsigned
long) ret_from_fork;//调用或中断的返回地址,使创建的子进程在首次被调度运行时就从这运行
savesegment(fs,p->thread.fs);//保存段寄存器fs
savesegment(gs,p->thread.gs);//保存段寄存器gs
unlazy_fpu(current);
struct_cpy(&p->thread.i387, ¤t->thread.i387);
return 0;
}
相关文章推荐
- linux内核设计与实现(三) linux进程管理 之 进程描述—1
- linux内核设计与实现 进程管理 访问子进程的方法详解
- Linux内核设计与实现(三) linux进程管理 之 概述
- Linux进程管理、进程创建、线程实现、僵尸进程
- Linux内核设计第六周学习总结 分析Linux内核创建一个新进程的过程
- Linux内核设计与实现----进程管理
- [Linux内核设计与实现]Linux进程管理
- linux内核设计与实现【第三版】摘记----第三章:进程管理
- 进程管理(Linux内核设计与实现 整理)
- Linux内核设计与实现笔记--chapter3 进程管理
- 【Linux内核设计与实现】进程管理
- 读薄「Linux 内核设计与实现」(2) - 进程管理和调度
- Linux内核设计与实现——读书笔记2:进程管理
- linux内核设计与实现笔记之第三章进程管理
- Linux进程(Linux内核设计与实现学习笔记)
- Linux内核设计与实现之进程管理
- Linux内核设计与实现----进程管理
- Linux内核设计与实现-进程管理与进程调度
- linux内核设计与实现(进程管理、进程调度读书笔记)
- Linux内核设计与实现-第三章 进程管理