Linux系统编程-进程

本文介绍了进程的相关概念,包括进程与程序的区别、如何查看进程以及进程标识符。详细讲解了fork和vfork函数用于创建子进程,以及进程退出、等待子进程的wait和waitpid函数。还探讨了孤儿进程的概念和init进程的角色。此外,文章还涵盖了exec族函数的使用,如execl、execv等,以及system函数和popen函数在执行命令方面的应用。
摘要由CSDN通过智能技术生成

目录

一、进程相关概念

二、创建进程

1、fork函数

 2、vfork函数

三、进程退出

四、进程等待

五、孤儿进程

六、exec族函数的使用

execl函数

execlp函数 

execv函数

 execvp函数

例:exec配合fork使用

七、system函数的使用

八、popen函数


一、进程相关概念

①程序:静态

②进程:程序动态的跑起就叫进程

③如何查看系统中有哪些进程?

   ·使用ps指令,ps -aux显示全部进程,实际工作中使用“ps -aux|grep (需要查看的进程名)”

   ·使用top指令,类似于windows任务管理器

④进程标识符:每个进程都有一个非负整数表示的唯一ID,叫做pid,类似于身份证

        pid=0:交换进程(swapper)作用:进程调度

        pid=1:init进程 作用:系统初始化

编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符     

编程实现:

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

int main()
{
	pid_t pid;
	pid = getpid();
	printf("%d\n",pid);
	return 0;
}



结果:3282

⑤子进程与父进程:进程A创建了进程B,A叫做父进程,B叫做子进程

⑥C程序的存储空间是如何分配的?

 正文:主函数中的执行语句,if...else...等(代码段)

初始化的数据:自定义的已经赋值的变量,包括main函数之内和之外的(数据段)

未初始化的数据:未赋值的变量(bss段)

堆:通常在堆中进行动态存储分配,如malloc申请的空间

栈:比如函数调用,函数局部变量,函数调用产生的信息

二、创建进程

1、fork函数

pid_t fork(void);

fork函数调用成功,返回两次

返回值为0,代表当前进程是子进程

返回值为非负数,代表当前进程是父进程

调用失败,返回-1

例1、fork的用法

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

int main()
{
	pid_t pid;
    //先打印一下目前pid
	printf("father:id=%d\n",getpid());
    
    //创建进程
	pid = fork();

	if(pid>0)//非负整数则是父进程
	{
		printf("this is father,pid=%d\n",getpid());
	}
	else if(pid == 0)//0则是子进程
	{
		printf("this is child,pid=%d\n",getpid());
	}
	return 0;
}


运行结果:
father:id=3678
this is father,pid=3678
this is child,pid=3679

fork之后的代码,子进程与父进程都会执行(选择性)

fork创建子进程的一般目的:

①一个父进程希望复制自己,使父子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

例2、仿真网络服务,请求达到时,data=1,创建子进程进行do net request

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

int main()
{
	pid_t pid;
	int data = 10;

	while(1){
		printf("please input a data\n");
		scanf("%d",&data);//用户输入1模仿服务请求达到
		if(data == 1)
		{
			pid = fork();//服务请求达到,创建子进程去处理
			
			if(pid>0)
			{
			
			}
			else if(pid == 0)//子进程
			{
				while(1){
					printf("do net request,pid=%d\n",getpid());
					sleep(3);
				}
			}
		}
		else//用户输入其他,则没有服务请求
		{
			printf("do nothing\n");
		}	
	}
	return 0;
}

 2、vfork函数

与fork的区别:

  • vfork直接使用父进程存储空间,不拷贝
  • vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行

例3、使用vfork前后对比

fork():

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

int main()
{
	pid_t pid;
	pid = fork();

	if(pid>0)	
	{
		while(1){
			printf("this is father,pid=%d\n",getpid());
			sleep(1);
		}
	}
	else if(pid ==0)
	{
		while(1){
			printf("this is child,pid=%d\n",getpid());
			sleep(1);
		}
	}
	return 0;
}

运行结果:
this is father,pid=4474
this is child,pid=4475
this is father,pid=4474
this is child,pid=4475
this is father,pid=4474
this is child,pid=4475
this is father,pid=4474
this is child,pid=4475

vfork():

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

int main()
{
	pid_t pid;
	pid = vfork();

	if(pid>0)	
	{
		while(1){
			printf("this is father,pid=%d\n",getpid());
			sleep(1);
		}
	}
	else if(pid ==0)
	{
		while(1){
			printf("this is child,pid=%d\n",getpid());
			sleep(1);
		}
	}
	return 0;
}

运行结果:

this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441
this is child,pid=4441

三、进程退出

正常退出:

  • main函数调用return
  • 进程调用exit(),标准C库
  • 进程调用_exit()或者_Exit(),属于系统调用
  • 进程最后一个线程退出(一个进程可以包含多个线程)
  • 最后一个线程调用pthread_exit 

异常退出: 

  • 调用abort
  • 当进程收到某些信号时,如ctrl+C
  • 最后一个线程取消(cancellation)请求做出响应

 父进程要等待子进程退出并收集子进程的退出状态,子进程退出状态不被收集会变成僵尸进程。

四、进程等待

1、wait函数

pid_t wait(int *status);

status:一个整型指针,非空:子进程退出状态放在它所指向的地址中;空:不关心退出状态

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

int main()
{
	pid_t pid;

	int cnt = 0;
	int status = 10;//子进程的退出状态

	pid = fork();//创建子进程

	if(pid>0)	
	{
		wait(&status);//父进程等待子进程退出
		printf("child quit,status:%d\n",WEXITSTATUS(status));//收集子进程的退出状态
		while(1){
			printf("cnt=%d\n",cnt);
			printf("this is father,pid=%d\n",getpid());
			sleep(1);
		}
	}
	else if(pid ==0)
	{
		while(1){
			printf("this is child,pid=%d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 5){
				exit(3);//子进程退出,退出状态为3
			}
		}
	}
	return 0;
}

2、waitpid函数

与wait函数区别:

wait使用者阻塞,子进程不结束,父进程一直阻塞。而waitpid有一个选项可以不阻塞

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

参数说明:

①pid

==-1:等待任一子进程

>0:等待其进程ID与pid相等的子进程

==0:等待其组ID等于其调用进程组ID的任一进程

<-1:等待其组ID等于pid绝对值的任一子进程

②status

③options

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

int main()
{
	pid_t pid;

	int cnt = 0;
	int status = 10;

	pid = fork();

	if(pid>0)	
	{
		waitpid(pid,&status,WNOHANG);//父子一起执行,但是子进程会成为僵尸进程
		printf("child quit,status:%d\n",WEXITSTATUS(status));
		while(1){
			printf("cnt=%d\n",cnt);
			printf("this is father,pid=%d\n",getpid());
			sleep(1);
		}
	}
	else if(pid ==0)
	{
		while(1){
			printf("this is child,pid=%d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 5){
				exit(3);
			}
		}
	}
	return 0;
}

用WNOHANG,不阻塞

五、孤儿进程

1、定义

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程

Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程

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

int main()
{
	pid_t pid;

	int cnt = 0;

	pid = fork();

	if(pid>0)	
	{
		printf("this is father,pid=%d\n",getpid());
	}
	else if(pid ==0)
	{
		while(1){
			printf("this is child,pid=%d,my father pid=%d\n",getpid(),getppid());
			sleep(1);
			cnt++;
			if(cnt == 5){
				exit(3);
			}
		}
	}
	return 0;
}

运行结果:
this is father,pid=3429
this is child,pid=3430,my father pid=3429


this is child,pid=3430,my father pid=1
this is child,pid=3430,my father pid=1
this is child,pid=3430,my father pid=1

六、exec族函数的使用

1、作用

我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。

exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

2、函数原型

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

参数说明:

path:可执行文件的路径

arg:可执行文件的参数,第一个参数为可执行文件的名字,arg必须以NULL结束

file:路径

execl函数

例:demo7中excel(demo8),先运行demo8.-o demo8

demo7.c:

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

int main(void)
{
	printf("before excel\n");
	if(execl("./demo8","demo8",NULL,NULL) == -1)
	{
		printf("execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}

 demo8.c:

#include <stdio.h>

int main()
{
	printf("this is excel!\n");
}

 执行结果:

before excel
this is excel!

举一反三:调用date获取当前时间

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

int main(void)
{
	printf("before excel\n");
	if(execl("/bin/date","date",NULL,NULL) == -1)
	{
		printf("execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}

运行结果:

before excel
Thu Jun 22 14:11:19 CST 2023

execlp函数 

与execl函数的不同:execl函数需要去查看可执行文件的绝对路径,而excelp函数直接写文件名即可,会自动查找文件所在位置

execv函数

与execl函数不同:此函数需要把文件名、参数等提前写成一个数组

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

int main(void)
{
	printf("before excel\n");
    char *argv[] = {"date",NULL,NULL};
	if(execv("/bin/date",argv) == -1)
	{
		printf("execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}

 execvp函数

在execv的基础上,不用写路径,会自动查找路径

例:exec配合fork使用

具体要求:当父进程检测到输入为1时,创建子进程把配置文件的字段值修改掉

①先写一个替换字段的程序生成可执行文件changdata

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

int main()
{
	pid_t pid;
	int data = 10;

	while(1){
		printf("please input a data:\n");
		scanf("%d",&data);
		if(data == 1){
			pid = fork();
			if(pid>0){
				wait(NULL);
			}
			if(pid == 0){
				int fdSrc;
				char *readbuf = NULL;
				fdSrc = open("config.txt",O_RDWR);
				int size = lseek(fdSrc,0,SEEK_END);
				lseek(fdSrc,0,SEEK_SET);

				readbuf=(char *)malloc(sizeof(char)*size +8);
				int n_read = read(fdSrc,readbuf,size);
				char *p = strstr(readbuf,"LENG=");
				if(p==NULL){
					printf("not found\n");
					exit(-1);
				}		
				p=p+strlen("LENG=");
				*p='5';

				lseek(fdSrc,0,SEEK_SET);
				int n_write = write(fdSrc,readbuf,strlen(readbuf)); 		
				close(fdSrc);
				exit(0);	
		}
		}
		else{
			printf("wait,do nothing\n");
		}
	}
}


执行:
gcc demo9.c -o changdata

②先一个程序使用execl调用这个可执行文件

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

int main()
{
	pid_t pid;
	int data = 10;

	while(1){
		printf("please input a data:\n");
		scanf("%d",&data);
		if(data == 1){
			pid = fork();
			if(pid>0){
				wait(NULL);
			}
			if(pid == 0){
                //此处即为子进程调用另一个可执行文件
				execl("./changdata","changdata","config.txt",NULL);
				exit(0);
			}
		}
		else{
			printf("wait,do nothing\n");
		}
	}
}

七、system函数的使用

1、函数原型

int system(const char *command);

参数说明:是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。

返回值:

成功-返回进程的状态值

不能执行sh-返回127

失败-返回-1 

2、作用

与execl作用相同,只不过直接加一个命令即可,不需要后面参数什么的

八、popen函数

1、函数原型

FILE *popen(const char *command, const char *type);

参数说明:

command同system函数

type:读或写的一种

        r:文件指针连接到command的标准输出

        w:文件指针连接到command的标准输入

返回值:

如果调用成功,则返回一个读或打开文件的指针,如果失败,返回null,具体错误根据error判断

int pclose(FILE *system)

参数说明:popen返回的文件指针

返回值:如果调用失败,返回-1

2、比system的好处:可以获取运行的结果

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值