父子进程、exec函数族

 

父子进程的关系

  • 子进程是父进程的副本。
  • 子进程获得父进程数据段,堆,栈,正文段共享。

        在fork之后,一般情况哪个会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。  

    区别:

  1. fork的返回值
  2. pid不同

进程的创建

  1. 创建之后,父子进程各自拥有4g独立的内存空间 
  2. 各自拥有自己的相关的程序的各个段数据段,所以,各自之间对数据的改变,不会相互影响。 
  3. 子进程会继承父进程已打开的文件描述符,若fork之前打开文件,父子进程操作同一个文件,相互间有影响。fork之后打开文件,父子进程操作同一个文件,但因为各自拥有自己的 "文件表项",所以,各自按照自己的逻辑改变文件。

进程的执行

exec函数族

        用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
新进程,所以调用exec前后该进程的id并未改变。有六种以exec开头的函数,统称exec函数:

        exec函数族用于执行一个文件,用新进程的镜像替换调用进程的镜像。主要包括以下几种函数:

1)int execl (const char *path, const char *arg, ... /* (char  *) NULL */);
2)int execlp (const char *file, const char *arg, ... /* (char  *) NULL */);

3)int execle (const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
4)int execv (const char *path, char *const argv[]);
5)int execvp (const char *file, char *const argv[]);
6)int execvpe (const char *file, char *const argv[], char *const envp[]);

区别:

(1)前4个使用路径名作为参数,后面两个使用文件名做参数。当filename中,含有/时视为路径名,否则就按PATH变量,在指定目录下查找可执行文件。
(2)相关的参数表传递

  •     l表示list,v表示vector
  •     execl,execlp,execle,需要将参数一个一个列出,并以NULL结尾。
  •     execv,execvp,execve,需要构造一个参数指针数组,然后将数组的地址传入。

(3)以e结尾的函数,可以传入一个指向环境字符串的指针数组的指针。其他未指定环境变量,使用父进程继承过来的。execve 是真正的系统调用
        这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错
则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

(1)execl 函数和 execv 函数

          参数传递方式:

                 1)带 l的函数(如execl)参数逐个列举,以NULL结尾。

                  2)带 v的函数(如execv)参数组织成指针数组的形式。

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	printf("---begin---\n");
	
//  execl("/bin/ls","ls","-l",".",NULL);

	char * const arg[] ={"ls","-l",".",NULL}; 
	execv("/bin/ls",arg);
	
	execl("/home/linux/linux_prog/01-prog/mycp","mycp","1.c","2.c",NULL);

	printf("---end---\n");
	return 0;
}

(2)execvp 函数和execle函数

        文件寻找方式

             1)带p的函数(如execvp)表示可执行文件的寻找方式是从系统的环境变量PATH中的路径下面去找。

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
	printf("---begin---\n");
	
	//if (execlp("mycp","mycp","1.c","2.c",NULL) < 0)

	char *const arg[] = {"mycp","1.c","2.c",NULL};
	if (execvp("mycp",arg) < 0)
	{
		perror("execl fail");
		return -1;
	}

	printf("---end---\n");	
	return 0;
}

              2)带e的函数(如execle)表示可以给要执行的新程序传递需要的环境变量。

#include <stdio.h>
#include <unistd.h>
extern char **environ;

int main(int argc, const char *argv[])
{
	char *menv[] = {"USER=linux","PASSWD=123456",NULL};

	//execle("./myenv","myenv",NULL,environ);
	//execle("./myenv","myenv",NULL,menv);
	execle("./myenv","myenv",NULL,menv);
	
	return 0;
}

在fork函数中的应用

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int execvpe(const char *file, char *const argv[],
		char *const envp[]);
int main(int argc, const char *argv[])
{

	char *env[5] = {NULL};
	char username[20];
	char passwd[20];

	while (1)
	{
		printf("Input username and passwd:");
		fgets(username,sizeof(username),stdin);
		username[strlen(username)-1] = '\0';
		fgets(passwd,sizeof(passwd),stdin);
		passwd[strlen(passwd)-1] = '\0';

		env[0] = username;
		env[1] = passwd;

		pid_t pid = fork();

		if (pid < 0)
		{
			perror("fork fail");
			return -1;
		}

		if (pid == 0)
		{
			char *const arg[] = {"check",NULL};
			if (execvpe("check",arg,env) < 0)
			{
				perror("execvpe fail");
				return -1;
			}
		}

	}
	return 0;
}

进程的终止

        八种情况:

   (1)正常结束:

  1. main 中 return
  2. exit() //库函数 。c库函数,会执行io库的清理工作,关闭所有 的流,以及所有打开的文件注册清理函数(atexit)。
  3. _exit,_Exit 会关闭所有的已经打开的文件,不执行清理函数。 //系统调用   
  4. 主线程退出  
  5. 主线程调用pthread_exit。 

  (2)异常终止:

  1. abort()
  2.  signal   kill pid
  3. 最后一个线程被pthread_cancle开辟堆区空间
  4.  

进程的退出

 

(1)孤儿进程:

子进程还在,但父进程已经结束。此时,避免子进程将来没有人收尸,由init进程来收养子进程。init(1) --- child(xxx)

(2)僵尸进程:

父进程还在,子进程先结束了。父进程没有做收尸操作。此时,子进程进入僵尸态。(wait/waitpid --- 查看子进程的退出状态)进程执行结束但空间未被回收变成僵尸进程。

  (1)exit     库函数

void exit(int status);

  •     退出状态,终止的进程会通知父进程,自己使如何终止的。    
  •     如果是正常结束(终止),则由exit传入的参数。
  •     如果是异常终止,则有内核通知异常终止原因的状态。
  •     任何情况下,父进程都能使用wait,waitpid获得这个状态,以及资源的回收。

            (1)功能:让进程退出,并刷新缓存区

            (2)参数:status:进程退出的状态

            (3)返回值:缺省

 

(2)_exit    系统调用

void _exit(int status);

        (1)功能:让进程退出,不刷新缓存区

        (2)参数:status:进程退出状态

        (3)返回值: 缺省

3b200a031d944170bfa813379905015e.png

 (3)atexit                回调函数

int atexit(void (*function)(void));

        (1)功能:注册进程退出前执行的函数

        (2)参数:function:函数指针,指向void返回值void参数的函数指针

        (3)返回值: 成功返回0,失败返回非0

         当程序调用exit或者由main函数执行return时,所有用atexit。 注册的退出函数,将会由注册时顺序倒序被调用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int *q = NULL;
void cleanup1(void)
{
	free(q);
	printf("----cleanup 1---\n");
}
void cleanup2(void)
{
	printf("----cleanup 2---\n");
}

int main(int argc, const char *argv[])
{
	atexit(cleanup1);
	atexit(cleanup2);
    int *p = malloc(4);
	q = p;
	*p = 4;
	printf("*p = %d\n",*p);
	printf("hello world!\n");

	
	//_exit(0);
	//_exit(0);
	return 0;
}

注意:

  1. exit函数调用时,会调atexit函数 。_exit函数调用时,不会调到atexit

  2. atexit 程序正常结束。(1).main 返回 //exit 。(2).exit()        
  3. atexit函数 可以多次注册 
  4. 最后"退出清理函数"的调用顺序,与注册顺序相反。

    

进程空间的回收

        wait/waitpid函数

pid_t wait(int *wstatus);

pid_t waitpid(pid_t pid, int *wstatus, int options);

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

        (1)功能:该函数可以阻塞等待任意子进程退出并回收该进程的状态。一般用于父进程回收子进程状态。

        (2)参数:status 进程退出时候的状态如果不关心其退出状态一般用NULL表示。如果要回收进程退出状态,则用WEXITSTATUS回收。

        (3)返回值:成功 回收的子进程pid。 失败 -1;

        

    

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值