Linux系统编程5--*进程【重点】

*1、Linux系统编程–进程相关概念

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

1.1.1 程序

  • 程序是静态的概念,gcc xxx.c -o pro; 磁盘中生成pro文件,叫做程序;

1.1.2 进程

  • 进程是程序的一次运动活动,通俗点意思是程序跑起来了,系统中就多了一个进程;

1.1.3 区别

  1. 程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
  2. 程序是静态的观念,进程是动态的观念;
  3. 进程具有并发性,而程序没有;
  4. 进程是竞争计算机资源的基本单位 ,程序不是。

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

  • 命令 ps -aux 列出所有进程;
  • 命令 ps -aux|grep init 把含**init**的进程筛选出来
  • 命令 **top**查看进程(类似于window任务管理器)

1.3、什么是进程标识符?

1.3.1 进程标识符

  • 每个进程都有一个非负整数表示的唯一 ID,叫做 pid ,类似身份证;
    • Pid = 0:称为交换进程(swapper)
      • 作用:进程调度 //由它来决定,当前某一时刻由谁来跑;
    • Pid = 1:init进程
      • 作用:系统初始化 //刚开始就应该执行的程序

1.3.2 getpid /getppid 函数

pid_t getpid(void);   //获取自身的进程标识符;
pid_t getppid(void);  //获取父进程的标识符;
  • 添加头文件
#include <sys/types.h>
#include <unistd.h>
  • getpid 获取自身的进程标识符;

  • getpid 获取父进程的标识符;


  • 程序演示
//demo.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

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

	printf("my pid is %d\n",pid);
	while(1); //定住

	return 0;
}
// 编译 然后将输出输入到hello文件中
gcc -o hello hello_world.c 
// 执行
./hello
// 显示内容:Hello World
  • 再打开一个终端,输入命令 top ,可见该进程正在执行;

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

  • 进程A创造了进程B (A --> B)
    • 进程A叫做父进程,B叫做子进程;
    • 父子进程是相对的概念没理解为人类中的父子关系;

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

请添加图片描述

  • 代码段: if else 等逻辑语句;
  • 数据段: 初始化的数据 int a = 0;
  • bss 段:未初始化的变量;
  • 栈: calloc申请内存地址;
  • 堆: 函数地址,以及函数中所产生的局部变量;

请添加图片描述

*2、Linux系统编程–创建进程

2.1 fork()

pid_t fork(void);
  • 创造一个子进程;
  • fork 调用成功(返回2下)
    • 给父进程返回 非负数 且 正好为子进程的 ID 号;
    • 给子进程返回 0;

​ 调用失败:返回 -1;

  • 添加头文件
#include <sys/tupes.h>
#include <unistd.h>
  • 程序演示
//demo4.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

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

	pid = fork();  //创造一个子进程

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

2.2 进程创建发生了什么事

  • 子进程不改变 变量a,共享;
  • 子进程改变 变量a,从父进程里面拷贝一份变量a的地址给子进程;

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

  1. 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这是请求到达时,父进程调用**fork**,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
  2. //一个进程要执行一个不同的程序。这对 shell 是很常见的情况。在这种情况下,子进程从fork返回后立即调用exec;
//demo5.c
#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);
		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("wait, do nothing\n");
		}
	}
	return 0;
}
  • 父进程一直检测客服端用户输入,每当用户输入”1“,创建一个子进程,每个子进程都不断输出自己的 ID 号;
  • 父进程和子进程互不影响;

2.4 小总结

请添加图片描述

2.5 vfork()

  • vfork( )fork( ) 的区别
    1. vfork 直接使用父进程存储空间,不拷贝。
    2. vfork 保证子进程先运行,当子进程调用 exit 推出后,父进程才执行。
//demo6.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
	pid_t pid;
	int cnt = 0;
	pid = vfork();

	if(pid > 0)
	{
		while(1)
		{
			printf("this is father print,pid = %d\n",getpid());
			sleep(1);
			printf("cnt = %d\n",cnt);
		}
	}
	else if(pid == 0)
	{
		while(1)
		{
			printf("this is child print,pid = %d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt ==3)
			{
				exit(0); //退出  0 代表这个子进程退出的状态
                //_exit(0);
                //_Exit(0);
                //不可以用 break 推出,会使cnt混乱
			}
		}
	}
	return 0;
}

3、Linux系统编程–进程退出

3.1 正常退出

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

3.2 异常退出

  1. 调用 abort
  2. 当进程收到某些信号时,如 ctrl + C;
  3. 最后一个线程对取消**(cancellation)**请求做出响应;
#include <stdio.h>
void exit(int status);

#include <unistd.h>
void _exit(int status);

#include <stdlib.h>
void _Eixt(int status);

3.3 小总结

请添加图片描述

4、Linux系统编程–父进程等待子进程退出

  • 为什么等待,要干活

请添加图片描述

4.1 wait() /waitpid()

**僵尸进程:**子进程退出状态不被收集,变成僵死进程

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

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

  • 进程状态:

    • 命令:ps -aux|grep a.out

    ​ S+ 正在运行;

    ​ Z+ 僵尸进程 (zombie)

4.1.1 检查waitwaitpid 所返回的终止状态的宏-解析退出码

请添加图片描述

来说明是什么原因退出的;

  • 解析status 退出码,也就是exit(?) 里的参数;
int status;
//子进程
exit(3);//退出码 ? = 3 
//父进程
wait(&status);       //里面的退出码给 *status;
WEXITSTATUS(status)  //== 3 //返回"exit status"//解析退出码
  • 父进程等待子进程退出,并收集子进程的退出状态;

4.1.2 wait 函数和 waitpid 函数介绍

请添加图片描述

  1. 将exit( ? ),里面的退出码给 * status;
  2. 如果其所有子进程都还在运行,则阻塞;
  3. 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
  4. 如果没有任何子进程,则立即出错返回;
  • status参数:(是一个整型数指针)

    • exit(3) status = 3;
    • 非空:子进程退出状态放在它所指向的地址中;
    • 空: 不关心退出状态;
  • waitwaitpid的区别

    1. wait使调用者阻塞;
    2. waitpid 有一个选项,可以使调用者不阻塞;
    3. waitpid 等待一个指定的子进程,而wait 等待所有的子进程,返回任一终止子进程的状态;
#include <stdio.h>
#include <sys/types.h>
#include <unistd.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));//返回的终止状态的宏WEXITSTATUS(*status)
		while(1)
		{
			printf("this is father print,pid = %d\n",getpid());
			sleep(1);
			printf("cnt = %d\n",cnt);
		}
	}
	else if(pid == 0)
	{
		while(1)
		{
			printf("this is child print,pid = %d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt ==3)
			{
				exit(3); //退出
				//_exit(3);
                //_Exit(3);
			}
		}
	}
	return 0;
}

4.1.3 waitpid //用的不多

pid_t waitpid(pid_t pid, int *status, int options);
  • pid 参数

    • pid == -1 : 等待任一子进程。就这一方面而言,waitpidwait 等效;
    • pid > 0 : 等待其进程 IDpid 相等的子进程;
    • pid == 0 :等待其组 ID 等于调用进程组ID的任一子进程;
    • pid < -1 : 等待其组 ID 等于pid 绝对值的任一子进程;
  • options
    请添加图片描述

4.2 孤儿进程

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

    • Linux避免系统存在过多的孤儿进程init 进程收留孤儿进程,变成孤儿进程的父进程
//demo8.c  演示出孤儿进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.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,pid = %d\n",getpid()); //打印一次,父进程就死了,死的比子进程早
	}
	else if(pid == 0)//子进程执行
	{
		while(1)
		{
			printf("this is child print,pid = %d, my father pid = %d\n",getpid(),getppid());
			sleep(1);
			cnt++;
			if(cnt ==3)
			{
				exit(3); //退出
				//
			}
		}
	}
	return 0;
}

请添加图片描述

  • 结果
    • 父进程打印一次
    • 子进程打印一次
    • 父进程死了,子进程被init进程(896) 收留,成为了init进程(896) 的子进程

5、Linux系统编程–exec族函数

  • (execl, execlp, execle, execv, execvp, execvpe)

推荐 blog:https://blog.csdn.net/u014530704/article/details/73848573

  • exec族函数函数的作用:
    • 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变
  • 功能:
      在调用进程内部执行一个可执行文件。既可以是二进制文件,也可以是任何Linux可执行的脚本文件

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

  • 函数原型

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

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
// e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量


建议看推荐 blog



5.1 常用命令和函数

  • 命令 whereis ls : 去找 ls 的绝对路径
  • 打印输出函数 :perror(" ? why ?") : 类似于 printf() ;
    • perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。

5.2 小功能–获取系统时间

  • 命令:date;

请添加图片描述

  • 命令:whereis date //获取date的绝对路径

请添加图片描述

//demo10.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	printf("this pro get system date:\n");
	if(execl("/bin/date","date",NULL,NULL)== -1) //不加参数,所有第三个形参为NULL;
	{
		printf("execlp failed!\n");
		perror("why"); //输出上一个函数发生错误的原因
	}
	printf("after execlp***\n");

	return 0;
}

5.3 更改环境变量PATH

  • 命令pwd //获取当前路径
    请添加图片描述

  • 命令echo $PATH //显示环境变量
    请添加图片描述

  • 命令export PATH =$PATH:/...
    请添加图片描述

*6、Linux系统编程–system / popen

6.1 常用命令和函数

  • cat 查看体积较大的文件

6.2 system()

blog: <linux system函数详解 - 南哥的天下 - 博客园 (cnblogs.com)>

在system源码中,可以看出,system 是 execl 函数的封装,程序员也更愿意去用。

#include <stdio.h>
int system(const char *command)
  • 作用:执行一个 shell 指令
  • 返回值:
    • 成功:返回进程的状态值;
    • 不能执行:返回 127 ;
    • 失败:返回 -1 ;
  • system()execl() 的区别
    • execl 这个函数系列 他会代替调用它的程序 执行完成后 不会回到主调程序中 直接用新的execl createshell替代了 原来的程序
    • system 这个函数不同 他会fork一个子程序中 但他会在主调程序中等待 system的返回。 相当于 for+execl+waitpid 三个函数的合体
//demo11.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	printf("this pro get system date:\n");
   //if(execl("/bin/ps","ps",NULL,NULL)== -1) //不加参数,所有第三个形参为NULL;
    //这就是execl 与system 的区别;
	if(system("ps")== -1)
	{
		printf("execlp failed!\n");
		perror("why");
	}
	printf("after execlp***\n");

	return 0;
}

6.3 popen()

6.3.1 一些小常识

  • 涉及到 FILE ,文件,我们就说 “”; fopen、 fread...

6.3.2 popen() 用法介绍

blog: <linux下popen的使用心得_linux c++ popen 命令执行结束-CSDN博客>

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

6.3.3 popen() 的优势

  • 相较system来说,popen 可以获取运行的输出结果
//demo12.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
	char ret[1024] = {0};
	FILE *fp;

	//system("ps");
	//如何保存输出的数据到file文件呢?
	//不用system利用popen;
	fp = popen("ps","r");

//	size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
	int nread = fread(ret, 1,1024,fp); 
    //nread: fread返回读了几个数;
	printf("read ret %d byte,ret = %s\n",nread,ret);
	return 0;
}
  • 实验结果
    请添加图片描述


欢迎大家一起交流讨论

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值