linux进程

1. 进程的相关概念

1.1 什么是进程,什么是程序,有什么区别?

程序是静态的概念,gcc xxx.c -o pro 磁盘中生成pro文件,叫做程序
进程是程序动态运行的过程,进程是程序的一次运行活动(程序的执行过程),通俗点意思是程序跑起来了,系统就多了一个进程

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

a.使用ps指令查看ps -aux|grep xxx
在实际工作中,配合grep来查找程序中是否存在某一个进程。

ps -aux//查看系统中所有进程

ps -aux|grep xx//查找有关xx的进程方便查找(从所有进程中过滤)

b.使用top指令查看,类似windows任务管理器

1.3 什么是进程标识符

1.3.1 基本概念

每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。

Pid=0:  称为交换进程(swapper)
作用—-进程调度
Pid=1:init进程
作用—-系统初始化

1.3.2 基本格式

查看相关信息:man 2 getpid

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
1.3.2.1 编程查看
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
        pid_t pid;

        //pid_t getpid(void);
        pid = getpid();

        printf("my pid is %d\n",pid);

        while(1);

        return 0;
}
1.3.2.2 编程结果

右边相当于linux的任务管理器,使用指令:top来查看
在这里插入图片描述

1.3.3 函数功能

函数原型pid_t getpid(void)
功能获取当前进程的进程号
返回值成功:获取进程号
函数原型pid_t getppid(void)
功能获取当前进程的父进程的进程号
返回值成功:获取父进程的进程号

pid_t 的实质是int类型的。
getpid()返回当前进程标识码,getppid()返回父进程标识。

1.4 什么叫父进程,什么叫子进程?

进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。

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

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

2. 创建进程函数fork的使用

查看fork相关信息:man 2 fork
在这里插入图片描述

2.1 基本格式

 #include <unistd.h>

pid_t fork(void);
---------------------------------------------------------------------------------------------
fork函数调用成功,返回两次
返回值为0       ,代表当前进程为子进程
返回值为非负数  ,代表当前进程为父进程
调用失败        ,返回-1

2.2 编程实战

2.2.1 初探fork()

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

int main()
{
        pid_t pid;

        //pid_t getpid(void);
        pid = getpid();

        //pid_t fork(void);
        fork();

        printf("my pid is %d\n",pid);

        return 0;
}

在这里插入图片描述
发现printf()执行了两次,这是为啥腻???
其实是fork()的作用,程序是从上到下执行的,当执行到fork()时,此时程序已经由一个进程转变为两个进程在跑,所以导致两个进程程序都执行了prinf()。 搜嘎斯乃~~

2.2.2

那我们可进行相关代码操作,区分父子进程

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

int main()
{
        pid_t pid;

        //pid_t getpid(void);
        pid = getpid();//父进程

        //pid_t fork(void);
        fork();//建立父子进程
		
		/*若下面printf()中的pid相同,则为父进程
		  若下面printf()中的pid不同,则为子进程*/
        printf("my pid is %d,current pro id:%d\n",pid,getpid());

        return 0;
}

在这里插入图片描述

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

int main()
{
        pid_t pid;

        //pid_t getpid(void);
        pid = getpid();//父进程pid

        //pid_t fork(void);
        fork();//创建父子进程pid

        if(pid == getpid()){//进入父进程
                printf("this is father print,father pid = %d\n",getpid());
        }else{				//进入子进程
                printf("this is child print,child pid = %d\n",getpid());
        }

        return 0;
}

在这里插入图片描述

2.2.4

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

int main()
{
	pid_t pid1;
	pid_t pid2;
	
	pid1 = getpid();//getpid():获取当前进程的pid号--父进程
	printf("before fork pid1=%d\n",pid1);
	fork();//fork后会创建两个进程,分别为父进程和子进程,两个进程都会执行fork后的代码程序。
	pid2 = getpid();//获取当前进程的pid
	printf("after fork pid2=%d\n",pid2);
	if(pid1 == pid2){//如果父进程等于当前进程的pid号则该进程为父进程
		printf("this is father print\n");//父进程
	}else{
		printf("this is child print,child pid=%d\n",pid2);//子进程
	}
	return 0;
}
----------------------------------------------------------------------
输出结果:
before fork:pid1 = 40444
//父进程执行
after fork pid2 = 40444
this is father print
//子进程执行
after fork pid2 = 40445
this is child print,child pid = 40445

2.2.5

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

int main()
{
	pid_t pid;
	
	printf("father:id = %d\n",getpid());//父进程pid
	
	pid = fork();//创建父子进程
	
	if( pid > 0 ){//父进程返回值:非负数
		printf("this is father print,father pid = %d\n",getpid());
	}
	if( pid == 0){//子进程返回值:0
		printf("this is child print,child pid = %d\n",getpid());
	}
	return 0;
}
--------------------------------------------------------------------
运行结果:
father:id = 40508
this is father print,father pid = 40508
this is child print,child pid = 40509 

2.2.6

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

int main()
{
	pid_t pid1;
	pid_t pid2;
	pid_t retpid;
	
	pid1 = getpid();//getpid():获取当前进程的pid号--父进程
	printf("before fork pid1=%d\n",pid1);
	
	retpid = fork();//fork后会创建两个进程,分别为父进程和子进程,两个进程都会执行fork后的代码程序。
	printf("retpid = %d\n",retpid);
	pid2 = getpid();
	printf("after fork pid2=%d\n",pid2);
	
	if(pid1 == pid2){//如果父进程等于当前进程的pid号则该进程为父进程
		printf("this is father print,retpid = %d\n",retpid);
	}else{
		printf("this is child print,child pid=%d,retpid = %d\n",pid2,retpid);
	}
	return 0;
}
================================================================
输出结果:
before fork:pid1  = 40723//父进程pid
retpid = 40742//在进程中fork返回的值子进程pid
after fork:pid2 = 40723
this is father print,retpid = 40734//父进程中子进程pid

retpid = 0//进入子进程
after fork pid2 = 40724//子进程Pid与父进程的fork返回值一致
this is child print,child pid = 40724,retpid = 0 

在父进程时fork()返回子进程pid,而在子进程时fork()返回0

3. 进程创建发生了什么事情?

在早期的Linux内核中,新进程会将代码段、数据段、bss段、堆、栈、命令行参数和环境变量等全部拿来拷贝一份,放入新进程中执行,而随着Linux内核的更新,当新进程被创建的时候,采用写时拷贝的方式运行新进程,在新进程中发生改变的变量新进程会将其拷贝一份作为自己的数据,而其他未被触及到的内容则于父进程进行共享(即后面子进程没有对变量数据做改变的话,则采用共享内存空间方式;只有在对变量数据做改变时,子进程的地址空间才会拷贝一份变量数据过来)。

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

int main()
{
	pid_t pid;
	int data = 20;
	
	printf("father:id = %d\n",getpid());

	pid = fork();//创建父子进程
	
	if( pid > 0 ){//父进程返回值:非负数
		printf("this is father print,father pid = %d\n",getpid());
	}
	if( pid == 0){//子进程返回值:0
		printf("this is child print,child pid = %d\n",getpid());
		data = data + 200;
	}
	printf("data = %d\n",data);
	return 0;
}
-------------------------------------------------------------------
运行结果:
father:id = 40774
this is father print,father pid = 40774
data  = 10
this is child print,child pid = 40775
data = 220//新进程写时拷贝了data数据作为自己的变量,与父进程区分开来,而其他的变量则与父进程共享.

4. 创建新进程的实际应用场景及fork总结

4.1 fork创建一个子进程的一般目的

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段,在这个网络服务进程中是常见的。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
2.一个进程要执行一个不同的程序,这对shell常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

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

int main()
{
	pid_t pid;
	int data;
	while(1){
		printf("please input a data:\n");
		scanf("%d",&data);
		if(data == 1){//界面输入1
			pid = fork();//每输入一次1,都会创建一个父子进程,主要用创建的子进程来执行相关操作
			if( pid > 0 ){//父进程返回值:非负数
	
			}
			if( pid == 0){//子进程返回值:0
				while(1){
				printf("do net conncect,pid = %d\n",getpid());
				sleep(3);//休眠3秒,防止刷屏
				}
			}
		}else{
			printf("do nothing!\n");
		}
	}
	return 0;
}

运行结果:
在这里插入图片描述
父子进程两者都是同时运行,父进程一直监测用户输入,当有客户端来时,创建子进程服务于客户端。父进程只检测什么也不干。这里我们可以看出当未创建服务端时,父进程一直在等待输入,当服务端来临时,立刻创建一个子进程,同时父进程也在运行等待新的输入,最后我们输入两个1意味着创建了两个子进程,这两个子进程同时运行。

5. vfork创建进程

5.1 vfork与fork的区别

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

5.2对于fork的使用的解析

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
	pid_t pid;
	
	pid = fork();//创建父子进程
	
	if( pid > 0 ){//父进程返回值:非负数
		while(1){
			printf("this is father print,father pid = %d\n",getpid());
			slepp(1);
			}
	}
	if( pid == 0){//子进程返回值:0
		while(1){
			printf("this is child print,child pid = %d\n",getpid());
			sleep(1);
			}
	}
	return 0;
}

运行结果:
在这里插入图片描述
可见:fork的创建的父子进程同时运行,在争夺CPU的资源 谁先执行取决于进程的调度

5.3 vfork的使用

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
	pid_t pid;
	int cnt = 0;
	
	pid = vfork();//vfork保证子进程先运行,子进程退出后,父进程运行
	
	if( pid > 0 ){//父进程返回值:非负数
		while(1){
			printf("cnt = %d\n",cnt);//会根据子进程的变量变化而变化
			printf("this is father print,father pid = %d\n",getpid());
			slepp(1);
			}
	}
	if( pid == 0){//子进程返回值:0
		while(1){
			printf("this is child print,child pid = %d\n",getpid());
			sleep(1);
			cnt ++;
			if(cnt == 3){
				exit(0);
				}
			}
	}
	return 0;
}

运行结果:
在这里插入图片描述
由此可见,当使用vfork()时,子进程的内容也会影响父进程的内容,注意:当使用fork()时,父子进程互不干涉

6. 进程的退出

6.1 正常退出

1. Main函数调用return
2. 进程调用exit(),标准c库
3. 进程调用_exit()或者_Exit(),属于系统调用
补充:
1. 进程最后一个线程返回
2. 最后一个线程调用pthread_exit

6.2异常退出

1. 调用abort(放弃当前进程)
2. 当进程收到某些信号时,如ctrl+C
3. 最后一个线程对取消(cancellation)请求做出响应

注意:但不管是哪种退出方式,系统最终都会执行内核中的某一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源

6.3 基本格式

查看相关信息:man 2 exit
在这里插入图片描述

#include <stdlib.h>

void exit(int status);

7. 父进程等待子进程退出

7.1 父进程等待子进程退出的目的

进程创建子进程的目的就是能够让子进程去响应某个事件,并且做出相应的处理。如果父进程关心子进程对于事件的处理情况,那么父进程则可收集子进程的退出状态来判断。

7.2 僵尸进程

对于父子进程来说,父进程在忙,子进程结束了,但无人回收,这样就造成了“死亡”的子进程一直占用资源,这个时候的子进程被称为僵尸进程(Z+)。
为了解决这个问题,最初的思路是:让父进程停下,等待子进程执行完,然后回收子进程,清理掉的子进程ID。

7.3 基本格式

查看相关信息:man 2 wait
在这里插入图片描述

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

pid_t wait (int* status);
------------------------------------------------------------------------------

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

7.4 编程实战

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	pid_t pid;
	int cnt = 0;
	
	pid = fork();//创建两个进程
	
	if( pid > 0 ){//父进程返回值:非负数
		while(1){
			wait(NULL);//等待子进程运行完收集后再运行父进程
			printf("cnt = %d\n",cnt);//会根据子进程的变量变化而变化
			printf("this is father print,father pid = %d\n",getpid());
			slepp(1);
			}
	}
	if( pid == 0){//子进程返回值:0 
		while(1){
			printf("this is child print,child pid = %d\n",getpid());
			sleep(1);
			cnt ++;
			if(cnt == 3){
				exit(0);
				}
			}
	}
	return 0;
}

运行结果:
在这里插入图片描述
此时子进程不再是僵尸进程。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	pid_t pid;
	int cnt = 0;
	int status = 10;
	
	pid = fork();//vfork保证子进程先运行,子进程退出后,父进程运行
	
	if( pid > 0 ){//父进程返回值:非负数
		while(1){
			wait(&status);//等待子进程运行完收集后再运行父进程
			printf("child quit.child status = %d\n",WEXITSTATUS(status));//返回子进程结束后的状态码3
			printf("cnt = %d\n",cnt);//会根据子进程的变量变化而变化
			printf("this is father print,father pid = %d\n",getpid());
			slepp(1);
			}
	}
	if( pid == 0){//子进程返回值:0
		while(1){
			printf("this is child print,child pid = %d\n",getpid());
			sleep(1);
			cnt ++;
			if(cnt == 3){
				exit(3);//子进程结束后返回状态码3
				}
			}
	}
	return 0;
}

在这里插入图片描述

7.5 waitpid基本格式

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

pid_ t waitpid(pid_t pid, int *status, int options);
-------------------------------------------------------------------------------------
参数说明:
pid == -1	等待任一子进程。就这一方面而言,waitpid与wait等效
pid  >  0	等带其进程ID与pid相等的子进程
pid == 0	等待其组ID等于调用进程组ID的任一子进程
pid  <  -1	等待其组ID等于pid绝对值的任一子进程

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

options:
WCONTINUED	若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态
WNOHANG(多有)	若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
WUNTRACED	若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程

7.6 wait和waitpid的区别:

wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞。

7.7waitpid编程实战

注意:fork()返回子进程pid(>0),进入父进程程序

#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 )//父进程返回值:子进程pid (非负数)
        {
				//pid--子进程pid
				//$status--退出值
				//WNOHANG--不挂起的方式
                waitpid(pid,&status,WNOHANG);
                printf("child quit.child status = %d\n",WEXITSTATUS(status));//返回子进程结束后的状态码3
                while(1){
                        printf("cnt = %d\n",cnt);
                        printf("this is father print,father pid = %d\n",getpid());//获取父进程pid
                        sleep(1);
                }
        }
 		else if( pid == 0){//进入子进程 返回值0
                while(1){
                        printf("this is child print,child pid = %d\n",getpid());//获取子进程pid
                        sleep(1);
                        cnt ++;
                        if(cnt == 5){
                                exit(3);
                        }
                }
        }
        return 1;
}

在这里插入图片描述
注意:此时子进程被收回,但是仍成为了僵尸进程

7.8 孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

7.8.1 编程实践

#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 )
        {

                        printf("this is father print,father pid = %d\n",getpid());
        }

        else if( pid == 0){
                while(1){
                        printf("this is child print,child pid = %d,my father pid = %d\n",getpid(),getppid());//getppid()获取父进程的!!!
                        sleep(1);
                        cnt ++;
                        if(cnt == 5){
                                exit(3);
                        }
                }
        }
        return 1;
}

在这里插入图片描述
可见,当父进程运行一次时,结束父进程,而子进程需要运行5次才能结束进程,但是当父进程结束时,子进程成为孤儿进程,子进程被父进程1420收留。

8.exec族函数

8.1exec族函数的作用

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

8.2 exec族成员

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

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[]数组,使用新的环境变量代替调用进程的环境变量

8.3 execl编程

主程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("before execl\n");
    //没有带路径且arg必须以NULL结束
    if(execl("./echoarg","echoarg","abc",NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");//当execl成功调用时,该行代码不会执行
    return 0;
}

待执行程序:

//echoarg.c
//编译时gcc echoarg.c -o echoarg
#include <stdio.h>

int main(int argc,char *argv[])
{
    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]);
    }
    return 0;
}

运行结果:
before execl
argv[0]: echoarg
argv[1]: abc

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("before execl\n");
    //没有带路径且arg必须以NULL结束
    if(execl("/bin/ls","ls",NULL,NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");//当execl成功调用时,该行代码不会执行
    return 0;
}

运行结果:
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("before execl\n");
    //没有带路径且arg必须以NULL结束
    if(execl("/bin/ls","ls","-l",NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");//当execl成功调用时,该行代码不会执行
    return 0;
}

运行结果:
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("this pro get system date:\n");
    //没有带路径且arg必须以NULL结束
    if(execl("/bin/date","date",NULL,NULL) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");//当execl成功调用时,该行代码不会执行
    return 0;
}
-----------------------------------------------------------
运行结果:
this pro get system date:
xxxxxxxxxxxx(虚拟机上的时间)

8.4 execlp的使用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("this pro get system date:\n");
    //没有带路径且arg必须以NULL结束
    //execlp第一个参数不需要绝对路径
    //execlp会根据环境变量去寻找第一个参数
    if(execlp("ps","ps",NULL,NULL) == -1)
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");
    return 0;
}

8.5 execvp使用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("this pro get system date:\n");
    char *argv = {"ps",NULL,NULL};
    if(execvp("ps",argv ) == -1)
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");
    return 0;
}

8.6 execv使用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//const char *path:可执行文件名
// const char *arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
int main(void)
{
    printf("this pro get system date:\n");
    char *argv = {"ps",NULL,NULL};
    //第一个参数戴绝对路径
    if(execv("/bin/ps",argv ) == -1)//当execl成功调用时,不会返回值,调用失败返回-1
    {
        printf("execl failed!\n");
    }
    printf("after execl\n");//当execl成功调用时,该行代码不会执行
    return 0;
}

8.7 exec配合fork使用:(在一个进程中调用另一个程序)

实现功能,当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉。

//调用函数
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
        pid_t pid;
        int data;
        while(1){
        printf("please input a data\n");
        scanf("%d",&data);
        if(data == 1){
                pid = fork();

                if(pid == 0){
                        execl("./changeData","changeData","config.txt",NULL);
                }
                }else{
                        printf("wait,do nothing!\n");
                }
        }
        return 0;
}
//被调用函数
#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(int argc, char **argv)
{
        int fdSrc;

        char *readBuf = NULL;

        if(argc != 2){
                printf("param error!\n");
                exit(-1);
        }

        fdSrc = open(argv[1],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=");

		p = p + strlen("LENG=");
        *p = '5';

        lseek(fdSrc, 0, SEEK_SET);

        int n_write = write(fdSrc, readBuf, strlen(readBuf));

        close(fdSrc);

        return 0;
}

运行结果:

原本config.txt内容:
SPEED=3
LENG=1
SCORE=9
LEVEL=5
-----------------------------------------------------------------------------------------------
修改后内容:
SPEED=3
LENG=5
SCORE=9
LEVEL=5

9. system函数

9.1 基本概念

system 函数类似于exec 族函数,我们用fork函数创建新进程后,也能在新进程中调用system 去执行另外一个程序。当进程调用system 函数时,该进程被替换为新程序。与 exec 族函数不同的一点是,system 函数调用完一个程序后,还会返回原来的主程序继续执行完

9.2 基本格式

#include <stdlib.h>

int system(const char *command);
--------------------------------------------------------------------
参数讲解:
const char *command:命令字符串

system()函数的返回值如下: 
成功,则返回进程的状态值; 当sh不能执行时,返回127; 失败返回-1

9.3 system源码

#include
#include
#include
#include
int system(const char * cmdstring)
{
  pid_t pid;
  int status;
  if(cmdstring == NULL){
      return (1);
  }
  
  if((pid = fork())<0){
        status = -1;
  }else if(pid == 0){
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    -exit(127); //子进程正常执行则不会执行此语句
    }
  else{
        while(waitpid(pid, &status, 0) < 0){
          if(errno != EINTER){
            status = -1;
            break;
          }
        }
    }
    return status;
}

当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的 pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring,(char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个 shell进程,这个shell的参数是cmdstring,就是system接受的参数。在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。

9.4 system编程实战

#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(system("ps") == -1)//即使成功调用,仍然会执行if后的代码
    {
        printf("execl failed!\n");
        perror("why");//该函数会再why后加冒号(:)再加原因,方便得知错误之处
    }
    printf("after execl\n");//仍然会运行该程序,与execl的不同之处!!!
    return 0;
}

10. popen函数

10.1 基本概念

popen函数比system函数在应用中的好处就是可以获取到运行的输出结果。(system函数获取不了运行的输出结果,原因是system函数封装的就是exce函数,成功运行的话 不会返回)

10.2 基本格式

#include “stdio.h”

FILE popen( const char *command, const char* mode )
---------------------------------------------------------------
参数讲解:
command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。
这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。
如果 type 是 “r” 则文件指针连接到 command 的标准输出;
如果 type 是 “w” 则文件指针连接到 command 的标准输入。

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

int pclose (FILE* stream)
参数说明:
stream:popen返回的文件指针
返回值:
如果调用失败,返回 -1

10.3 popen编程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
        char ret[1024]  = {0};
        FILE *fp;

        fp = popen("ps","r");
        int nread = fread(ret,1,1024,fp);

        printf("read %d byte,ret=%s\n",nread,ret);

        pclose(fp);

        return 0;
}

运行结果:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小强子!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值