Linux fork系统调用

在Linux中可以使用fork系统调用创建子进程,子进程会继承父进程的的大部分属性,然后子进程会调用exec簇函数来运行其他程序。
下面来看一个具体的例子,该例子可以说明父子进程之间的关系

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{
	
	pid_t pid = -1; 
	char *const argv[] = {"Linux","Hello",NULL};
	int status;
	pid = fork();  // 调用fork系统创建子进程
	
	if(pid == 0) // 子进程
	{
		if(execv("hello.out",argv) < 0)
		{
			perror("exec");
		}
	}
	else if(pid > 0)  // 父进程
	{
		sleep(5);  // 父进程睡眠5秒等待子进程退出
		if(wait(&status) < -1)      // 如果wait返回值为-1,则表示回收失败
		{
			perror("wait");
		}
		if(WIFEXITED(status))
		{
			printf("child exit\r\n");
		}

	}
	else  if(pid < 0) // 进程创建失败
	{
		printf("fork error\r\n");
	}
	return 0;
}

fork系统调用会返回三个值
pid > 0 则表示该进程为父进程
pid == 0 则表示该进程为子进程
pid < 0则表示fork失败
当fork成功时,内核会复制父进程的一些属性给到子进程,如文件描述符,信号等一些属性,具体怎么复制可以在内核源码中找到。copy_process是内核用于复制进程的函数,也是fork系统调用的主体函数。

static struct task_struct *copy_process(unsigned long clone_flags,
					unsigned long stack_start,
					unsigned long stack_size,
					int __user *child_tidptr,
					struct pid *pid,
					int trace)
{
	int retval;
	struct task_struct *p;
	/*******************前面省略************************************/
	/* Perform scheduler related setup. Assign this task to a CPU. */
	retval = sched_fork(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_policy;

	retval = perf_event_init_task(p);
	if (retval)
		goto bad_fork_cleanup_policy;
	retval = audit_alloc(p);
	if (retval)
		goto bad_fork_cleanup_perf;
	/* copy all the process information */
	shm_init_task(p);
	retval = copy_semundo(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_audit;
	retval = copy_files(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_semundo;
	retval = copy_fs(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_files;
	retval = copy_sighand(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_fs;
	retval = copy_signal(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_sighand;
	retval = copy_mm(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_signal;
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
	retval = copy_io(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread(clone_flags, stack_start, stack_size, p);
	if (retval)
		goto bad_fork_cleanup_io;

	if (pid != &init_struct_pid) {
		pid = alloc_pid(p->nsproxy->pid_ns_for_children);
		if (IS_ERR(pid)) {
			retval = PTR_ERR(pid);
			goto bad_fork_cleanup_io;
		}
	}

	p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
	/*
	 * Clear TID on mm_release()?
	 */
	p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;
#ifdef CONFIG_BLOCK
	p->plug = NULL;
#endif
#ifdef CONFIG_FUTEX
	p->robust_list = NULL;
#ifdef CONFIG_COMPAT
	p->compat_robust_list = NULL;
#endif
	INIT_LIST_HEAD(&p->pi_state_list);
	p->pi_state_cache = NULL;
#endif
	/*
	 * sigaltstack should be cleared when sharing the same VM
	 */
	if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
		p->sas_ss_sp = p->sas_ss_size = 0;

	/*
	 * Syscall tracing and stepping should be turned off in the
	 * child regardless of CLONE_PTRACE.
	 */
	user_disable_single_step(p);
	clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
#ifdef TIF_SYSCALL_EMU
	clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
#endif
	clear_all_latency_tracing(p);

	/* ok, now we should be set up.. */
	p->pid = pid_nr(pid);
	if (clone_flags & CLONE_THREAD) {
		p->exit_signal = -1;
		p->group_leader = current->group_leader;
		p->tgid = current->tgid;
	} else {
		if (clone_flags & CLONE_PARENT)
			p->exit_signal = current->group_leader->exit_signal;
		else
			p->exit_signal = (clone_flags & CSIGNAL);
		p->group_leader = p;
		p->tgid = p->pid;
	}

	p->nr_dirtied = 0;
	p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
	p->dirty_paused_when = 0;

	p->pdeath_signal = 0;
	INIT_LIST_HEAD(&p->thread_group);
	p->task_works = NULL;

	/*
	 * Make it visible to the rest of the system, but dont wake it up yet.
	 * Need tasklist lock for parent etc handling!
	 */
	write_lock_irq(&tasklist_lock);

	/* CLONE_PARENT re-uses the old parent */
	if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
		p->real_parent = current->real_parent;
		p->parent_exec_id = current->parent_exec_id;
	} else {
		p->real_parent = current;
		p->parent_exec_id = current->self_exec_id;
	}

	spin_lock(&current->sighand->siglock);

	/*
	 * Copy seccomp details explicitly here, in case they were changed
	 * before holding sighand lock.
	 */
	copy_seccomp(p);

	/***********************省略*************************************/
}

继续看回应用程序,父进程创建子进程成功后,子进程会调用execv函数去执行新的程序,在例子中执行的程序为hello.out。hello.out由hello.c编译而成。
hello.c的程序如下

#include <stdio.h>
#include <unistd.h>
int main(int argc ,char *argv[])
{
	
	printf("hello world\r\n");
	
	printf("argv[0] = %s argv[1] = %s\r\n",argv[0],argv[1]);
	
	return 0;
}

程序很简单,打印一些信息然后退出。
Linux的exec簇函数用于子进程运行新的程序,该簇函数有以下几个函数

int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ..., char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[])

这几个函数使用方法都差不多,可以在Linux的man手册中查看几个函数的具体用法。

在应用程序中,父进程会先睡眠5秒,然后等待子进程退出,回收子进程的资源。因为当子进程退出时,如何父进程不回收子进程的资源,会导致子进程进入僵尸态,所谓的僵尸态意思是进程已经结束了,但是资源没有被回收,仍然占用操作系统资源。在上述的例子中,父进程调用wait系统调用来回收子进程的资源。wait系统调用如果返回-1,则表示回收子进程失败。应用程序可以调用操作系统提供的宏定义来判断子进程的退出状态。
例如WIFEXITED宏判断子进程是否正常退出
WIFSIGNALED宏判断子进程是否被信号杀死

总结:
应用程序可以使用fork系统调用创建子进程。
子进程可以使用exec函数簇来运行其他的应用程序
当子进程结束运行时,父进程要及时利用wait系统调用回收子进程的资源。
最后看一下上述应用程序的运行结果
在这里插入图片描述当在命令行运行./fork程序时,父进程会创建子进程,子进程随之运行hello.out程序,在命令行打印出“hello world”和传进去的参数,然后子进程退出运行。此时父进程先睡眠5秒等待子进程退出,然后调用wait系统调用回收子进程资源,回收成功的同时打印出“child exit”信息,最后父进程退出运行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值