Linux系统编程--进程

1.1、什么是程序,什么是进程,有什么区别?
1.1.1 程序
程序是静态的概念,gcc xxx.c -o pro; 磁盘中生成pro文件,叫做程序;
1.1.2 进程
进程是程序的一次运动活动,通俗点意思是程序跑起来了,系统中就多了一个进程;
1.1.3 区别
程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
程序是静态的观念,进程是动态的观念;
进程具有并发性,而程序没有;
进程是竞争计算机资源的基本单位 ,程序不是。
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创建一个子进程的一般目的
一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这是请求到达时,父进程调用**fork**,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
//一个进程要执行一个不同的程序。这对 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.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 正常退出
Main函数调用return;
进程调用**exit()**,标准c库;
进程调用**_exit()**或者 _Exit(), 属于系统调用;
补充
进程最后一个线程返回了;
最后一个线程调用 pthread_exit ;
3.2 异常退出
调用 abort;
当进程收到某些信号时,如 ctrl + C;
最后一个线程对取消**(cancellation)**请求做出响应;

#include <stdio.h>
void exit(int status);

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

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

 

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

  • 为什么等待,要干活

4.1 wait() /waitpid()

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

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

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

  • 进程状态:

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

    ​ S+ 正在运行;

    ​ Z+ 僵尸进程 (zombie)

  • 4.1.1 检查wait 和waitpid 所返回的终止状态的宏-解析退出码
  • 解析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;
非空:子进程退出状态放在它所指向的地址中;
空: 不关心退出状态;
wait 和waitpid的区别

wait使调用者阻塞;
waitpid 有一个选项,可以使调用者不阻塞;
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 : 等待任一子进程。就这一方面而言,waitpid 与 wait 等效;
pid > 0 : 等待其进程 ID 与 pid 相等的子进程;
pid == 0 :等待其组 ID 等于调用进程组ID的任一子进程;
pid < -1 : 等待其组 ID 等于pid 绝对值的任一子进程;
 

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

 

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

 

  • 34
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值