zoukankan      html  css  js  c++  java
  • Linux进程管理(二、 进程创建)

    通常使用fork创建进程, 也可以用vfork()和clone()。
    fork、vfork和clone三个用户态函数均由libc库提供,它们分别会调用Linux内核提供的同名系统调用fork,vfork和clone。
    vfork与fork的区别在于创建进程时, vfork完全共享父进程的地址空间,包括页表项。vfork在早期用来替代fork以避免复制地址空间的耗时操作,
    但是现在fork已经使用了写时复制技术,在进程创建时,只复制父进程的页表项,用只读的方式共享父进程地址空间,在写入时再复制数据。
    fork有了这个优化之后, vfork已较少使用。

    fork调用链:
    | libc::fork() | --->int$0×80软中断--->| kernel: ENTRY(system_call)--->ENTRY(sys_call_table)--->sys_fork()--->do_fork() |

    //----------------------------------------- file: process.c --------------------------------------------
    
    asmlinkage long sys_fork(void)
    {
    	struct pt_regs *regs = task_pt_regs(current);
    	return do_fork(SIGCHLD, regs->gprs[15], regs, 0, NULL, NULL);
    }
    
    //----------------------------------------- file: fork.c -------------------------------------------------
    
    long do_fork(unsigned long clone_flags,
    	      unsigned long stack_start,
    	      struct pt_regs *regs,
    	      unsigned long stack_size,
    	      int __user *parent_tidptr,
    	      int __user *child_tidptr)
    {
    struct task_struct *p;
    long nr;
    ...
    p = copy_process(clone_flags, stack_start, regs, stack_size,
    			child_tidptr, NULL); //最核心的工作:通过copy_process()创建子进程的描述符,分配pid等
    if (!IS_ERR(p)) {
    	...
    	if (!(clone_flags & CLONE_STOPPED))
    		wake_up_new_task(p, clone_flags); //将进程放到运行队列上,从而让调度器进行调度运行
    	else
    		p->state = TASK_STOPPED;
    	...
    } else {
    	nr = PTR_ERR(p);
    }
    return nr;
    }
    
    //----------------------------------------- file: fork.c -------------------------------------------------
    
    static struct task_struct *copy_process(unsigned long clone_flags,
    					unsigned long stack_start,
    					struct pt_regs *regs,
    					unsigned long stack_size,
    					int __user *child_tidptr,
    					struct pid *pid)
    {
    	int retval;
    	struct task_struct *p;
    	int cgroup_callbacks_done = 0;
    
    	//...
    	//检查clone_flags
    	//...
    	p = dup_task_struct(current);     //创建内核栈, thread_info, task_struct, 里面的值是完全复制的父进程的值
    	if (!p)
    		goto fork_out;
    
    	...
    
    	/*
    	 * If multiple threads are within copy_process(), then this check
    	 * triggers too late. This doesn't hurt, the check is only there
    	 * to stop root fork bombs.
    	 */
         //检查当前进程总数是否超出最大进程数
    	if (nr_threads >= max_threads)
    		goto bad_fork_cleanup_count;
    
    	//...
    	//复制、更新子进程描述符的flags成员,
    	copy_flags(clone_flags, p);
    
    	//...
    	//初始化子进程描述符的各个字段,很多被清零,使得子进程和父进程逐渐区别出来。 不过大部分数据仍然未被修改
    	//...
    
    	//调度器设置。调用sched_fork函数执行调度器相关的设置
    	/* Perform scheduler related setup. Assign this task to a CPU. */
    	sched_fork(p, clone_flags);
    
    
    //-------------------------start 根据clone_flags的具体取值来为子进程拷贝或共享父进程的某些数据结构--------//
    	if ((retval = security_task_alloc(p)))
    		goto bad_fork_cleanup_policy;
    	if ((retval = audit_alloc(p)))
    		goto bad_fork_cleanup_security;
    	/* copy all the process information */
    	if ((retval = copy_semundo(clone_flags, p)))
    		goto bad_fork_cleanup_audit;
    	if ((retval = copy_files(clone_flags, p))) //打开的文件信息
    		goto bad_fork_cleanup_semundo;
    	if ((retval = copy_fs(clone_flags, p))) //所在文件系统信息
    		goto bad_fork_cleanup_files;
    	if ((retval = copy_sighand(clone_flags, p)))
    		goto bad_fork_cleanup_fs;
    	if ((retval = copy_signal(clone_flags, p))) //信号处理函数
    		goto bad_fork_cleanup_sighand;
    	if ((retval = copy_mm(clone_flags, p))) //地址空间信息
    		goto bad_fork_cleanup_signal;
    	if ((retval = copy_keys(clone_flags, p)))
    		goto bad_fork_cleanup_mm;
    	if ((retval = copy_namespaces(clone_flags, p))) //命名空间
    		goto bad_fork_cleanup_keys;
    	//复制线程。通过copy_threads()函数更新子进程的内核栈和寄存器中的值
    	retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
    	if (retval)
    		goto bad_fork_cleanup_namespaces;
    
    //-----------------------------------------end -------------------------------------------------//
    
    	//分配pid
    	if (pid != &init_struct_pid) {
    		retval = -ENOMEM;
    		pid = alloc_pid(task_active_pid_ns(p));
    		if (!pid)
    			goto bad_fork_cleanup_namespaces;
    
    		if (clone_flags & CLONE_NEWPID) {
    			retval = pid_ns_prepare_proc(task_active_pid_ns(p));
    			if (retval < 0)
    				goto bad_fork_free_pid;
    		}
    	}
    
    	p->pid = pid_nr(pid);
    	p->tgid = p->pid;
    	if (clone_flags & CLONE_THREAD)
    		p->tgid = current->tgid;
    
    	p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
    	
    	...
    	//更改p的其他一些字段
    	...
    
    	if (likely(p->pid)) {
    		add_parent(p);
    		if (unlikely(p->ptrace & PT_PTRACED))
    			__ptrace_link(p, current->parent);
    
    		if (thread_group_leader(p)) {
    			if (clone_flags & CLONE_NEWPID)
    				p->nsproxy->pid_ns->child_reaper = p;
    
    			p->signal->tty = current->signal->tty;
    			set_task_pgrp(p, task_pgrp_nr(current));
    			set_task_session(p, task_session_nr(current));
    			attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
    			attach_pid(p, PIDTYPE_SID, task_session(current));
    			list_add_tail_rcu(&p->tasks, &init_task.tasks);
    			__get_cpu_var(process_counts)++;
    		}
    		attach_pid(p, PIDTYPE_PID, pid);
    		nr_threads++;
    	}
    
    	total_forks++;
    	spin_unlock(&current->sighand->siglock);
    	write_unlock_irq(&tasklist_lock);
    	proc_fork_connector(p);
    	cgroup_post_fork(p);
    	return p;
    
    	//...
    	//错误处理
    	//...
    }
    

        看到这里应该可以知道刚开始提出的第一个问题, 子进程是否能使用父进程的文件描述符?
        答案是肯定的,因为task_struct里面成员struct files_struct *files 保存了所有进程打开的文件描述符。
    创建子进程的时候,会将files复制一份到子进程files结构体里, 里面的值跟父进程的值是一样的,可以用来操作父进程打开的文件。
    如下面代码里的:newf = dup_fd(oldf, &error);
    如果调用do_fork的时候传入clone_flags: CLONE_FILES, 那么子进程直接共享父进程files成员,不会复制, 这时候当然更可以用来操作父进程的文件。
    线程就是这么做的。

    static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
    {
    	struct files_struct *oldf, *newf;
    	int error = 0;
    
    	/*
    	 * A background process may not have any files ...
    	 */
    	oldf = current->files;
    	if (!oldf)
    		goto out;
    
    	if (clone_flags & CLONE_FILES) {
    		atomic_inc(&oldf->count);
    		goto out;
    	}
    
    	/*
    	 * Note: we may be using current for both targets (See exec.c)
    	 * This works because we cache current->files (old) as oldf. Don't
    	 * break this.
    	 */
    	tsk->files = NULL;
    	newf = dup_fd(oldf, &error);
    	if (!newf)
    		goto out;
    
    	tsk->files = newf;
    	error = 0;
    out:
    	return error;
    }
    

    子进程复制父进程的所有文件描述符:

  • 相关阅读:
    Brackets 前端编辑器试用
    java面试题之第一回
    java数据类型
    [转]JAVA标识符和关键字
    Servlet的几个关键知识点
    一个Servlet中可以有多个处理请求的方法
    基于java的聊天室/群发控制台程序
    java 创建string对象机制 字符串缓冲池 字符串拼接机制
    git 常用命令
    ajax axios 下载文件时如何获取进度条 process
  • 原文地址:https://www.cnblogs.com/hushpa/p/5681436.html
Copyright © 2011-2022 走看看