linux系统编程之进程(全)

一、进程的启动

1.1 getpid获取当前进程标识符

进程标识符(PID),通常的范围为2~32768,0~1的取值有系统固定的意义,可参考
上篇文章对其的简述 

在这里插入图片描述

由上图我门,可以看出获取进程标识符(以下都称为PID)的函数有两个
 pid_t getpid(void);
 pid_t getppid(void);
 所需头文件:
 	#include<sys/types.h>
 	#include<unistd.h>
 pid_t 是返回值类型,类型为进程标识符类型
 通过程序测试,
 我们知道了 getpid()返回的是当前进程的PID,而getppid()返回的是父进程PID
 注意:
 	此函数不会失败,也就是说只要调用这个两个函数中的一个,
 	都会成功且返回一个PID

1.1.1代码演示将在子进程处 演示

1.2启动新进程

       在一个程序内部启动另一个程序,从而创建一个新进程。这个工作一个通过库函数system完成

在这里插入图片描述

int system(const char *string);
所需头文件:
	#include<stdlib.h>
返回值:
	成功:返回值为该命令的退出码
	失败:
		若comand(即string形参) 为空 返回0
		若子进程不能被创建或者它的状态不能被检测 返回-1
		若无法启动shell启动命令  返回127
		其他错误 都返回-1

例1 system()使用

在这里插入图片描述

在这里插入图片描述

1.2.1 system的局限性

       system很有用,它也有很大的局限性,因为程序必须等待由system函数启动的进程结束后才能继续,也就是说,不能立刻执行其他任务。
       一般来说,使用system函数并非启动其他进程的理想手段,因为它必须使用一个shell来启动需要的程序,所以这对shell的安装情况和使用环境也有很大的以来,所以system函数的使用效率并不高,接下来我们介绍一中更好的调用程序的方法。

1.3 exec系列函数

功能:
  在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
  
函数族:
  exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

函数原型:

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., 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[]);

返回值:
       exec函数族的函数执行成功后不会返回,调用失败时, 会设置errno并返回-1,然后从原程序的调用点接着往下执行。
  
  
参数说明:
       path:可执行文件的路径名字
       arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束 ,可以为系统自带的命令
       file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数有很多并且相似,可以根据函数中某些字符区分:
       l : 使用参数列表
       p:使用文件名,并从PATH环境进行寻找可执行文件
       v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
       e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

下面将exac函数归为带l、带p、带v、带e 四类来说明参数特点。

       一、带l的一类exac函数(l表示list),如execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。
以execl函数为例子来说明:

例2 execl()使用

//文件exec1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// int excel(const char *path,const char *arg,...);
int main()
{
	printf("before excel\n");
	if(execl("./file1","file1",NULL)==-1)//file1为可运行程序(xx.exe)
	{
		printf("execle failed\n");
		perror("why:");
	}
	printf("after execl\n");
	return 0;
}
//文件file1.c
#include<stdio.h>

int main()
{
	printf("Hello world\n");
	return 0;
}

运行结果:
	gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec1.c
	gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out 
	before excel
	Hello world

实验说明:
       在exec1.c文件中使用execl()函数,file1.c作为被调用文件,在执行exec1可执行文件后,程序在此函数调用处的后面内容将被覆盖,所以”after execl” 没有在终端被打印出来。

       二、带p的一类exac函数,如execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子,PATH=/bin:/usr/bin

例3 execlp()使用

//文件execl_no_path.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
    printf("before execl****\n");
    if(execl("ps","ps","-l",NULL) == -1)
    {
        printf("execl failed!\n");
    }   
    printf("after execl*****\n");
    return 0;
}
运行结果:
	gec@ubuntu:/mnt/hgfs/vrshare$ gcc execl_no_path.c 
	gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out
	before execl****
	execl failed!
	after execl*****
//文件exec3.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

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

int main()
{
	printf("before execlp\n");
	if(execlp("date","date",NULL)==-1)//调用的是Linux系统自带的命令
	{

		printf("execle failed\n");
		perror("why:");
	}
	printf("after execlp\n");
	return 0;
}
运行结果:
	gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec3.c
	gec@ubuntu:/mnt/hgfs/vrshare$./a.out 
	before execlp
	Fri Oct  9 23:34:25 PDT 2020
从上面的实验结果可以看出,上面的exaclp函数带p,
所以能通过环境变量PATH查找到可执行文件ps,这样就看清了两者的不同

       三、带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。

例4 excvp()使用

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

// int execvp(const char *file,const char *argv[]);

int main()
{
	printf("before execvp\n");
	char *argv[]={"ps",NULL};
	if(execvp("ps",argv)==-1)
	{

		printf("execvp failed\n");
		perror("why:");
	}
	printf("after execvp\n");
	return 0;
}

运行结果:
	gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec4.c 
	gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out 
	before execvp
  	 PID TTY          TIME CMD
 	 2343 pts/6    00:00:00 bash
 	70519 pts/6    00:00:00 ps

       四、带e的一类exac函数,包括execle、execvpe,可以传递一个指向环境字符串指针数组的指针。 参数例如char *env_init[] = {“AA=aa”,”BB=bb”,NULL}; 带e表示该函数取envp[]数组,而不使用当前环境。

例5 execle()使用

//文件exec5.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execle(const char *path, const char *arg,..., char * const envp[]);

char *buf[] = {"AA=aa","BB=bb",NULL};
int main(void)
{
    printf("before execle****\n");
        if(execle("./bin/exec_test03","exec_test03",NULL,buf) == -1)
        {
                printf("execle failed!\n");
        }       
    printf("after execle*****\n");
    return 0;
}
//文件exec_test03.c
#include <stdio.h>
#include <unistd.h>
extern char** buf;
int main(int argc , char *argv[])
{
    int i;
    char **ptr;
    for(ptr = buf;*ptr != 0; ptr++)
        printf("%s\n",*ptr);
    return 0;
}
运行结果:
	gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec5.c 
	gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out 
	before execle****
	AA=aa
	BB=bb

二、创建子进程

2.1 fork创建子进程

在这里插入图片描述

 pid_t fork(void);
 所需头文件:
 	#include<unistd.h>
 返回值:
 	成功:返回值为子进程PID
 	失败:返回值为-1
 作用:
 	创建一个和原进程内容一样的进程,从此处开始执行新进程。
 注意:
 	两个进程是同时进行的!!

例6 getpid()和fork()使用

在这里插入图片描述
在这里插入图片描述

       通过加之限定条件,我们清楚了父进程和子进程!知道了在同时有父进程和子进程时,父进程PID为非负数,子进程PID为0,fork()调用成功返回两次!

2.2等待一个进程

       当fork创建一个子进程时,子进程有了它自己的生命周期并且独立于父进程运行,有时我们希望知道什么时候一个子进程结束。例如,有时我们想让输出不乱,那么我们需要父进程等待子进程结束,再结束父进程。通过查库我们得到函数:
在这里插入图片描述

pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options);
status:
	用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。通常
设为NULL,原因自查。

pid:进程标识符,进程号
  1.pid>0 只等待进程ID等于pid的子进程,只要指定的子进程未结束waitpid就会一直等下去。
  2.pid=-1 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
  3.pid=0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
  4.pid<-1 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
  
options:
options提供了一些额外的选项来控制waitpid,具体请查看手册
	
所需有文件:
	#include<sys/types.h>
	#include<sys/wati.h>

返回值:
因为篇幅原因,(这里仅说wait(NULL)),对于wait其他状态宏和waitpid的返回值
具体请参考:
https://blog.csdn.net/wyhh_0101/article/details/83933308

	成功:
	wait(NULL),返回最终结束的子进程的PID
	失败:
	wait(NULL),返回 -1
函数说明:
	1.wait() 
	父进程阻塞直到子进程结束或者终止。如果该父进程没有子进程
或者它的子进程已经结束,则wait()函数就会立即返回。
	2.waitpid()
	它并不一定要等待第一个终止的子进程(可以指定需要等待终止的子进程),
它还有若干选项(options),如可提供一个非阻塞版本的 wait()功能,
也能支持作业控制。可以说wait()函数只是 waitpid()函数的一个特例,
在Linux 内部实现 wait()函数时直接调用的就是waitpid()函数。

例7 wait()使用

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
	pid_t pid;
	int cnt=0;
	int status=10;
	pid=fork();
	if(pid>0)
	{
		wait(&status);
		printf("child quit,child status = %d\n",WEXITSTATUS(status));
		while(1)
		{
			printf("cnt=%d\n",cnt);
			printf("This is father process  %d\n",getpid());
			sleep(1);

		}

	}
	else if(pid==0)
	{
		while(1)
		{
			printf("This is child process  %d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt==3)
			{
				exit(3);
			}
		}
	}
	return 0;

}

运行结果:
	gec@ubuntu:/mnt/hgfs/vrshare/eng/linux_Sys/day4$ ./a.out 
	This is child process  70951
	This is child process  70951
	This is child process  70951
	child quit,child status = 3
	cnt=0
	This is father process  70950
	cnt=0
	This is father process  70950
	cnt=0
	This is father process  70950
	cnt=0
	This is father process  70950
	cnt=0
	This is father process  70950
//程序设置子进程执行三次后,退出,父进程接到终止信号后运行

2.3 popen函数

#include <stdio.h>
FILE * popen(const char *command , const char *type );
int pclose(FILE *stream);
//pclose与popen是一对,打开了就要有关闭,但这里仅对popen做详细说明。

函数作用:
        popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭。popen函数允许一个程序将另外一个程序作为新进程来启动,并可以传递数据或者通过它接受数据。

其内部实现为调用 fork 产生一个子进程,执行一个 shell, 以运行命令来开启一个进程,这个进程必须由 pclose() 函数关闭。

参数说明:
        ①type参数只能是读或者写中的一种,如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。
        ②command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令
返回值
成功:
        返回标准I/O流。(FILE*)
失败:
        如果调用fork()或pipe()失败,或者不能分配内存将返回NULL。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。

三、总结

       1.由于篇幅原因,将在下一篇文章补充僵尸进程和孤儿进程相关概念

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux系统编程是指在Linux操作系统下进行程序开发的过程和技术。它包括了使用系统调用、编写应用程序、处理文件和目录、进程管理等一系列操作。 在Linux系统编程中,可以使用系统调用来获取系统相关的信息,比如时间和日期。你可以使用time函数来获取当前的系统时间,date函数来获取当前的日期。这些函数可以帮助你获取到你所需要的系统信息。 此外,你可以使用Linux系统下的/proc虚拟文件系统来获取更详细的系统信息。在/proc目录下,每个进程都有一个对应的子目录,其中包含了该进程的相关信息。你可以读取这些文件来获取进程的状态、文件描述符等详细信息。 在Linux系统编程中,你可以使用system函数来执行shell命令。这个函数可以让你在程序中方便地执行任意的shell命令。比如,你可以使用system("ls -la")来执行ls命令来列出当前目录下的所有文件和文件夹。你也可以使用system("echo HelloWorld")来执行echo命令打印出HelloWorld。 除了上述内容,Linux系统编程还涉及到进程管理、文件和目录操作、进程通信等方面的内容。你可以使用fork函数创建子进程,使用exec函数执行新的程序,在程序之间进行进程通信等等。这些都是Linux系统编程中常见的任务和技术。 总结起来,Linux系统编程是在Linux操作系统下进行程序开发的过程和技术,包括使用系统调用、处理文件和目录、进程管理、进程通信等一系列操作。通过使用系统调用和/proc虚拟文件系统,你可以获取到系统的相关信息。同时,使用system函数可以方便地执行shell命令。这些都是Linux系统编程中常见的内容和技巧。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉迷学习的拿铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值