42.Linux 进程控制

news/2024/7/5 19:37:58

fork函数

        创建一个子进程。

 头文件:       

 #include <sys/types.h>
 #include <unistd.h>

函数原型:       

pid_t fork(void);

返回值:

RETURN VALUE
       On success, the PID of the child process is returned in the parent, and
       0  is returned in the child.  On failure, -1 is returned in the parent,
       no child process is created, and errno is set appropriately.
返回值: 成功后,子进 程的pid将返回到父级中,并且 在子节点中返回0。当失败时,将在父文件中返回-1, 没有创建任何子进程,并且正确地设置了errno。 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	printf("before fork--1--\n");
	printf("before fork--2--\n");
	printf("before fork--3--\n");
	printf("before fork--4--\n");
	printf("before fork--5--\n");
	
	pid_t pid=fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid==0)
	{
		printf("---child is created\n");
	}
	else if(pid>0)
	{
		printf("---parent process:my child is %d\n",pid);
	}

	printf("=================end of file\n");
	return 0;
}

执行结果如下:

当使用fork函数以后,先执行完父进程,再执行子进程。

使用getpid(),getppid()函数获取进程的本身的pid、和本进程对应的父进程pid

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	printf("before fork--1--\n");
	printf("before fork--2--\n");
	printf("before fork--3--\n");
	printf("before fork--4--\n");
	printf("before fork--5--\n");
	
	pid_t pid=fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid==0)
	{
		printf("---child is created,pid=%d,parent-pid=%d\n",getpid(),getppid());
	}
	else if(pid>0)
	{
		printf("---parent process:my child is %d,my pid:%d,my parent pid:%d\n",pid,getpid(),getppid());
	}

	printf("=================end of file\n");
	return 0;
}

执行结果如下:

题目:

一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎么实现呢?

简单想,for(i=0;i<n;i++){frok()}即可。但这样创建的是N个子进程吗?

例如:for(i=0;i<3 ;i++)

        fork();

 答案:2的3次方=8,因为有一个是父进程,所以产生了七个子进程。

从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n-1)个子进程,而不是N个子进程。需要在循环的过程,保证子进程不再执行fork,因此当(fork()==0)时,子进程来个break才正确。

 实现程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	pid_t pid;
	for(int i=0;i<5;i++)
	{
		pid=fork();
		if(pid==0)
		{
			break;
		}
		if(i==5)
		{
			sleep(1);
			printf("I'm parent\n");
		}
		else
			printf("I'm %dth child\n",i+1);
	}
	return 0;
}

执行结果如下:

进程共享

        父子进程之间在fork后。有哪些相同,哪些相异之处呢?

        刚fork之后:

        父子进程相同之处:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式......

        父子进程不同之处:1、进程ID         2、fork返回值        3、父进程ID        4、进程运行时间        5、闹钟        6.未决信号集

        

  父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。

 练习:编写程序测试,父子进程是否共享全局变量             

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

int var=100;

int main(int argc,char *argv[])
{
	pid_t pid;

	pid=fork();

	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}else if(pid > 0){
		var = 288;
		printf("parent,var=%d\n",var);
		printf("I'm parent pid=%d,getppid = %d\n",getpid(),getppid());
	}else if(pid==0){
		var = 200;
		printf("I'm child pid=%d,ppid=%d\n",getpid(),getppid());	
		printf("child,var = %d\n",var);
	}
	printf("-----------------finish-------------------\n");
	return 0;
}

执行结果如下:

重点注意! 父子进程间共享全局变量的知识误区!

重点:父子进程共享:1、文件描述符(打开文件的结构体)2、mmap建立的映射区(进程间通信详解)

fork函数:

        pid_t fork(void);

        创建子进程。父子进程各自返回。父进程返回子进程pid。子进程返回0.

        getpid();getppid();

        循环创建N个子进程模型。每个子进程标识自己的身份。

 

父子进程gdb调试

        使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟父进程。

        set follow-fork-mode child命令设置gdb在fork之后跟踪子进程。

        set follow-fork-mode parent设置跟踪父进程。

注意,一定要在fork函数调用之前设置才有效。

生成可调试文件的命令:

gcc fork.c -o gdbfork -g

list 1列出调试代码,l列出调试代码

设置断点:b(break)  设置断点的行数

r(run):运行到断点处

打印变量的值:p(print) i
                        $1 = 0


exec函数族(库函数)

        fork创建子进程后执行的是和父进程相同的程序(但有可能

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

六种以exec开头的函数(我们只需要记住两种就可以了),统称exec函数:

int execl(const char *path,const char *arg,....);

int execlp(const char *file,const char *arg,....);

execlp函数 

        加载一个进程,借助PATH环境变量

                int execlp(const char *file,const char *arg,.....);

返回值:

                成功:无返回

                失败:-1

        参数1:(命令)"ls"要加载的程序名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。

        参数2,3....:命令选项        "-l","-d","-h"

        该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。

execlp("ls","-l","-d","-h",NULL);

后面一定要加NULL

例子:用execlp函数实现date命令

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	pid_t pid=fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid==0)
	{
		//execlp("ls","ls","-l","-h" ,NULL);
		execlp("date","date",NULL);
		perror("exec error");
		exit(1);
	}
	else if(pid>0)
	{
		printf("I'm parent:%d\n",getpid());
	}
	return 0;
}

执行结果如下:

execl函数

 函数原型

int execl(const char *path, const char *arg, ...);

execl("/bin/ls","ls","-l",NULL);

注意第一个参数,和第二个参数的区别

例子:使用execl函数在子进程中实现./a.out 的执行

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	pid_t pid=fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid==0)
	{
     	execl("./fork","./fork",NULL);	
	}
	else if(pid>0)
	{
		printf("I'm parent:%d\n",getpid());
	}
	return 0;
}

执行结果如下:

用execl函数执行ls命令:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	pid_t pid=fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid==0)
	{
        execl("/bin/ls","ls","-l",NULL);	
	}
	else if(pid>0)
	{
		printf("I'm parent:%d\n",getpid());
	}
	return 0;
}

执行结果如下:

exec函数族特性

execlp("ls","ls","-l",NULL);      //使用程序名在PATH中搜索

execl("/bin/ls","ls","-l",NULL);  //使用参数1给出的绝对路径搜索

     

练习:将当前系统中的进程信息,打印到文件中,

exec_ps.c

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

int main(void)
{
	int fd;

	fd=open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);
	if(fd<0)
	{
		perror("open ps.out failure");
		exit(1);
	}

	dup2(fd,STDOUT_FILENO);

	execlp("ps","ps","ax",NULL);
        perror("execlp error");
	return 0;
}

l(list)                命令行参数列表

p(path)             搜索file时使用path变量

v(vector)           使用命令行参数数组

e(environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量


回收子进程

孤儿进程

        孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。

僵尸进程

        僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。

wait函数

        一个进程在终止时会关闭所有的文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保留着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。

 pid_t wait(int *wstatus);

成功:清理掉的子进程ID

失败:-1(没有子进程)

当进程终止是,操作系统的隐式回收机制会:1.关闭所有文件描述符 2.释放用户空间分配的内存

内核的PCB仍存在。其中保存该进程的退出状态。(正常终止>退出值 ;异常终止>终止信号)

 

例程1:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
        pid_t pid,wpid;
        pid=fork();

        int status;

        if(pid == -1)
        {
                perror("fork error");
                exit(1);
        }else if(pid > 0){
                wpid=wait(&status);
                if(wpid==-1)
                {
                        perror("wait error");
                        exit(1);
                }
                printf("----------parent wait finish:%d\n",wpid);
        }else if(pid==0){
                printf("---child,my pid=%d,going to sleep 10s\n",getpid());
                sleep(10);
                printf("-----------child die---------------------------------\n");
        }
        return 0;
}

执行结果如下:

 

如果是wait(NULL)不关心子进程结束原因

可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为以下三组:        

这几个宏的使用方法如下:

 (wait if exited)WIFEXITED(wstatus)(为真说明子进程正常终止)

 (wait exit status)WEXITSTATUS(wstatus)(得到子进程的退出值)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
	pid_t pid,wpid;
	pid=fork();

	int status;

	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}else if(pid > 0){
		wpid=wait(&status);
		if(wpid==-1)
		{
			perror("wait error");
			exit(1);
		}
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n",WEXITSTATUS(status));
		}
		printf("----------parent wait finish:%d\n",wpid);
	}else if(pid==0){
		printf("---child,my pid=%d,going to sleep 1s\n",getpid());
		sleep(1);
		printf("-----------child die---------------------------------\n");
		return 73;
	}
	return 0;
}

 执行结果如下: 

            

 (wait if signaled) WIFSIGNALED(wstatus)(为真,说明子进程是被信号终止

 (wait term signal)WTERMSIG(wstatus)

 例程②(获取导致子进程异常终止信号)

得到导致子进程异常终止的信号编号

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
	pid_t pid,wpid;
	pid=fork();

	int status;

	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}else if(pid > 0){
		wpid=wait(&status);
		if(wpid==-1)
		{
			perror("wait error");
			exit(1);
		}
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n",WEXITSTATUS(status));
		}
		if(WIFSIGNALED(status))
		{
			printf("child kill with signal %d\n",WTERMSIG(status));
		}
		printf("----------parent wait finish:%d\n",wpid);
	}else if(pid==0){
		printf("---child,my pid=%d,going to sleep 1s\n",getpid());
		sleep(10);
		printf("-----------child die---------------------------------\n");
		return 73;
	}
	return 0;
}

执行结果如下:     

kill -9 2925

kill -10 2930

   

 

 WCOREDUMP(wstatus)
 WIFSTOPPED(wstatus)

 WSTOPSIG(wstatus)

 WIFCONTINUED(wstatus)

 

一次wait/waitpid函数调用,只能回收一个进程。

waitpid函数

回收指定的进程

作用:同wait,但可以指定pid进程清理,可以不阻塞

函数原型

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

参数:

        pid:指定回收的子进程pid。

        >0:待回收的子进程pid

        -1:任意子进程

        0 :同组的子进程

        status:(传出)回收进程的状态。

        options:WNOHANG指定回收方式为非阻塞。

返回值:

成功:回收的子进程PID;(>0)

        0:函数调用时,参3指定了WNOHANG,并且,没有子进程结束。

        失败:-1(无子进程)。errno

参数pid(无子进程)

        >0  回收指定ID的子进程

        -1   回收任意子进程(相当于wait)

        0     回收和当前调用waitpid一个组的所有子进程

        <-1  回收指定进程组内的任意子进程

返回0:参数3为WNOHANG,且子进程正在运行 

例程:回收第三个子进程 

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

int main(int argc,char *argv[])
{
	int i;
	pid_t pid,wpid,tmpid;
	for( i=0;i<5;i++)
	{
		pid=fork();
		if(pid==0)
		{ 
		        break;
 		}
		if(i==2)
		{
			tmpid=pid;
			printf("----------pid=%d\n",tmpid );
		}
	}
		if(i==5)
		{
			sleep(5);
			printf("-----------in parent,before waitpid,pid=%d\n",tmpid);
			wpid=waitpid(tmpid,NULL,0);
			if(wpid==-1)
			{
				perror("waitpid error");
				exit(1);
			}
			printf("I'm parent,wait a child finish:%d\n ",wpid);
		}
		else
		{
			sleep(i);
			printf("I'm %dth child,pid=%d\n",i+1,getpid());
		}
		return 0;
}

 执行结果如下:

 总结:waitpid一次调用,回收一个子进程。

如果想回收多个子进程。while循环

waitpid回收多个子进程

例程:waitpid回收多个子进程

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

int main(int argc,char *argv[])
{
	int i;
	pid_t pid,wpid;
	for( i=0;i<5;i++)
	{
		pid=fork();
		if(pid==0)
		{ 
		        break;
 		}
	}
		if(i==5)
		{
			while( (wpid = waitpid(-1,NULL,0)) && (wpid>0))
			{
				printf("wait child %d\n",wpid);
			}
		}
		else
		{
			sleep(i);
			printf("I'm %dth child,pid=%d\n",i+1,getpid());
		}
		return 0;
}

执行结果如下:

 

 


http://www.niftyadmin.cn/n/2432906.html

相关文章

Ubuntu下Openfire的安装

Ubuntu下Openfire的安装2014-06-03 20:15:29标签&#xff1a;Openfire原创作品&#xff0c;允许转载&#xff0c;转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://ovcer.blog.51cto.com/1145188/1421665一、安装JDK (1)、创建保存j…

js-yaml简单使用

安装 js-yamlnpm install js-yamlindex.jslet fs require("fs"); let content fs.readFileSync("text.yaml",{encoding:"utf8"});let yaml require("js-yaml"); let result yaml.load(content); console.log(JSON.stringify(resul…

43.Linux 消息队列

msgget&#xff08;message get&#xff09; msgctl &#xff08;message contorl&#xff09; msgsnd (message send) msgrcv &#xff08;message receive&#xff09; &#xff08;1&#xff09;msgget&#xff08;创建消息队列&#xff09; 所需头文件 #includ…

Lombok快速上手(安装、使用与注解参数)

目录 Lombok插件安装与使用说明常见参数lombok的依赖于安装依赖管理IDEA插件的安装Data小例子扩展ToString构造器注解扩展Log及其他日志注解资料链接Lombok插件安装与使用说明 在实习中发现项目中IDE一直报检查错误&#xff0c;原来是使用了Lombok注解的黑科技&#xff0c;这里…

log4j配置每天生成一个日志文件

log4j配置每天生成一个日志文件 2017-02-14 11:52 1712人阅读 评论(0) 收藏 举报分类&#xff1a;java&#xff08;58&#xff09; 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 本文仅记录tomcat下配置成功的记录&#xff0c;不作log4j配置的详…

24.C语言 结构体与链表

结构体变量分为数据域和指针域 结构体变量和数组变量一样都是由大到小开始分配存储单元 0FFFFNode110FFF00FFF0Node220FF000FF00Node33NULL 静态链表 动态链表 ①创建一个表头去表示整个链表 struct Node *creatListHead() { //1.赋值结构体变量//2.动态内存申请struct Node …

LaTeX 常见错误汇总及解决方案

2019独角兽企业重金招聘Python工程师标准>>> fwrite: Broken pipe xelatex.exe: 生成的pdf已打开 XeTeX is required to compile this document 需要XeLaTeX来编译此文档 &#xff01;LaTeX Error: Too many unprocessed floats. 老大&#xff1a;那是有图片出现了浮…

44.Linux 管道

管道的概念&#xff1a; 管道是一种最基本的IPC机制&#xff0c;作用于有血缘关系的进程之间&#xff0c;完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质&#xff1a; 1.其本质是一个伪文件&#xff08;实为内核缓冲区&#xff09;。 2.由两个文件描述符&#…