Linux C 进程

何为进程?

进程是一个应用程序的执行实例,也就是系统中正在运行的应用程序,程序一旦运行就是进程。进程是一个动态过程,它是程序的一次运行过程,而非静态文件。

进程号(process ID)

Linux系统下的每一个进程都有一个进程号(process ID,简称PID),进程号是一个正数,用于唯一标识系统中的某一个进程。

main函数由谁调用?

进程如何终止?

进程和程序是两个完全不同的概念:

我们通常所说的程序指的是可执行程序,也就是可执行文件,说明程序本质上是一个文件,文件是一种静态的概念,文件存储在磁盘中,文件本身并不会对系统产生任何影响。而进程指的是正在运行的程序,它强调的是运行中,所以进程是一种动态的概念,程序运行之后会对系统环境产生一定的影响。

同一个程序可以被运行多次,从而产生多个不同的进程

可以把进程理解为程序的实例化对象,程序被执行一次就表示实例化了一次,被执行多次,也就是表示实例化了多次,产生了多个不同的实例化对象,也就是多个不同的进程。

进程的生命周期:

从程序启动到程序退出这段时间。

到底是谁来调用main函数呢?

其实在运行main函数之前,还会运行一段引导代码,最终由这段引导代码调用main函数

这段引导代码并不需要我们自己编写,而是在编译、链接我们的应用程序的时候由连接器将这段引导代码链接到我们的应用程序中,构成最终的可执行文件,也就是最终的程序。

加载器会将可执行程序加载到内存中。

进程的终止可以分为两种:正常终止和异常终止

正常终止:

  1. 譬如在main函数中通过return返回,终止进程
  2. 调用库函数exit终止进程
  3. 调用系统调用_exit或者_Exit

异常终止:

  1. 调用abort函数终止进程
  2. 被信号终止

Void _exit(int status);终止进程的运行;

       Status 这个参数用来表示进程终止时的状态,通常0表示正常终止,非零值表示非正常终止。

Void exit(int status);

       Status 这个参数用来表示进程终止时的状态,通常0表示正常终止,非零值表示非正常终止。

exit()和_exit()之间的区别:

  1. exit()是库函数,exit()他是一个系统调用,他们所需要的包含的头文件不一样
  2. 这两个函数的最终目的相同,都是终止进程,但是在终止进程之前需要做一些处理,这些处理工作这两个函数是不一样的。

有一些终止进程的情况是不会刷新stdio缓冲的

  1. _exit()或__Exit()
  2. 被信号终止的情况

exit()和return之间有什么区别:

  1. exit()是一个库函数,return 是c语言的语句
  2. exit()函数最终会进入到内核,把控制权交给内核,最终由内核去终止进程。

执行return并不会进入到内核,它只是一个main函数返回,返回到它的上层调用把控制权交给他的上层调用,最终由上层调用终止进程。

测试发现使用return终止进程也会调用终止处理函数,并且会刷新IO缓冲

exit()函数和abort函数之间有什么区别:

1、exit用于正常终止进程,abort用于异常终止进程

正常终止在终止进程之前会执行一些清理工作,异常终止并不会执行这些清理工作,他是直接终止进程 执行SIGABRT信号的系统默认处理操作,终止进程的运行。

创建子进程

  • 所有进程都是由其父进程所创建出来的

Linux系统中的所有进程都是由其父进程所创建出来的,譬如我们在终端下执行某个应用程序;./demo

这个程序启动之后就是一个进程,这个进程就是由它的父进程(也就是这个shell进程)所创建出来的

Shell进程就是shell解析器(shell解析器有很多种,譬如bash、sh等),所谓解析器就是解析用户输入的各种命令,然后做出相应的响应,执行相应的程序。

那既然所有的进程都是由其父进程所创建出来的,那么总会存在一个最原始的父进程(所有进程的“祖先”),否则其它进程是怎么创建出来的呢?这个祖先进程就是init进程。

Init进程的PID是1,它是所有子进程的父进程,所有进程的祖先,一切从init开始

  • 进程空间

在Linux系统中,进程与进程之间,进程与内核之间都是相互隔离的,各自在自己的进程空间中运行(内核就是在自己的内核空间中运行);一个进程不能读取或修改另一个进程或内核的内存数据,这样提高了系统的安全性与稳定性。

新进程被创建出来后,便是一个独立的进程,拥有自己独立的进程空间,拥有自己唯一的进程号(PID),拥有自己独立的PCB(进程控制块),新进程会被内核同等调度执行,参与到系统调用中。

  • fork创建子进程

应用程序中可以调用fork系统调用来创建一个新的进程,被创建出来的新进程称为子进程,调用fork函数的进程称为父进程,这两个进程之间就是父子关系。

子进程与父进程之间的这种关系被称为父子进程关系,父子进程关系相比于普通的进程间关系多多少少存在一些关联与羁绊

  • 如何理解fork系统调用?

一次fork调用会产生两次返回值

也就是说调用一次fork函数,它会产生两次返回值。

为什么会产生两次返回值?因为fork调用会创建一个新的进程,这个新的进程就是子进程,也就是说,在fork调用后,会存在两个进程,一个子进程、一个父进程。

所以会有两次返回值,子进程会返回一次、父进程也会返回一次。

并且这两次返回值是不一样的,分别会返回一个0和大于0的整数,这个0便是子进程的返回值,大于0的整数则是父进程的返回值。所以我们可以通过返回值来判断当前是子进程还是父进程返回。其实这个大于0的整数就是子进程的pid

fork创建了一个与原来进程几乎完全相同的进程

其实子进程是父进程的一个副本,fork函数是以复制的方式创建子进程,子进程几乎完全复制了父进程,譬如子进程会拷贝父进程的数据段、堆、栈,并且拷贝父进程打开的所有文件描述符,父进程与子进程并不共享这些存储空间,这是子进程对父进程相应部分存储空间的完全复制,执行fork之后,子进程和父进程各自在自己的进程空间中运行,每个进程均可修改各自的栈数据以及堆段中的变量,而不会影响另一个进程。

子进程从fork调用返回后的代码开始运行

子进程从fork调用返回后开始运行,虽然子进程和父进程运行在不同的进程空间中,但是他们执行的却是同一个程序。但是需要注意,子进程运行的是fork调用之后的代码,并不会执行fork调用之前的代码。

父子进程间的数据共享

fork之后两个地址空间区数据完全相同

后续各自进行了不同的操作

父进程:num--

子进程:num++

物理地址:i,f_num,z_num

各个进程的地址空间中的数据是完全独立的

对于同一变量,读时共享

写的时候分别在物理地址上拷贝一份变量进行单独读写

父子进程之间可不可以通过全局变量通信?

不能,两个进程内存不能共享

exec函数族

让父子进程来执行不相干的操作

能够替换进程地址空间的代码.text段

执行另外的程序,不需要创建额外的地址空间

当前程序中调用另外一个应用程序

指定执行目录下的程序

Int execl(const char *path,const char *arg,.../*(char *)NULL*/);

Path:要执行程序的路径(最好是绝对路径) 变参arg:要执行的程序需要的参数

第一位arg:占位  后边的arg:命令的参数  参数写完后:null一般执行自己写的程序

执行PATH环境变量能够搜索到的程序

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

file:执行的命令名字  第一个arg:占位 后边的arg:命令的参数 参数写完之后:NULL执行系统自带的程序:/bin/xx */ps aux

执行指定路径,指定环境变量下的程序

int execle(const char *path,const char *arg,.../*,char(*)NULL,char * const envp[]*/);

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

int i=200;

int main(int argc,char *argv[])
{

	pid_t pid;
	pid=fork();

	if(pid>0)
	{
		i+=400;
		printf("this is father process %d\n",getpid());
		printf("i=%d\n",i);
	}
	else if(pid==0)
	{

       //execl(“/bin/ls”,”ls”,”l”,NULL)
		execlp("ps","ps","aux",NULL);
		i+=200;
		printf("this is child process %d,ppid is %d\n",getpid(),getppid());
		printf("i=%d\n",i);
	}

	for(int i=0;i<5;i++)
	{
		printf("i=======%d\n",i);
	}


	return 0;
}

 

execlp后面的代码不进行。

孤儿进程和僵尸进程

孤儿进程:

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作

为了释放子进程的占用的系统资源:

进程结束之后,能够释放用户区空间

释放不了PCB,必须由父进程释放

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

int main(int argc,char *argv[])
{
	pid_t pid;
	pid=fork();
	if(pid==0)
	{
		sleep(1);
		printf("i am child pid=%d ppid=%d\n",getpid(),getppid());

	}
	else if(pid>0)
	{
		printf("i am father pid=%d\n",getpid());

	}

	return 0;
}

僵尸进程:

一个比较特殊的状态,当进程退出父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会在以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。

是一个已近死掉了的进程。

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

int main(int argc,char *argv[])
{
	pid_t pid;
	pid=fork();
	if(pid==0)
	{
		printf("i am child pid=%d ppid=%d\n",getpid(),getppid());

	}
	else if(pid>0)
	{
		while(1)
		{
			sleep(1);
			printf("i am father pid=%d\n",getpid());
		}
	}

	return 0;
}

僵尸进程已经死掉,无法用kill杀死,杀死父进程僵尸进程就没了。

进程回收

wait阻塞函数

函数作用:

阻塞并等待子进程退出

回收子进程残留资源

获取子进程结束状态(退出原因)

Pid_t wait(int *status)

.

返回值:

-1:回收失败,已经没有子进程了

>0:回收子进程对应的pid

参数:

status判断子进程如何退出状态

  1. WIFEXITED(status):为非0,进程正常结束

WEXITSTATUS(status)

如上宏为真,使用此宏,获取进程退出状态的参数

  1. WIFSIGNALED(status):为非0,进程异常退出

WTERMSIG(status):

如上宏为真,使用此宏,取得使进程终止的那个信号的编号

调用一次只能回收一个子进程。

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/wait.h>
int i=200;

int main(int argc,char *argv[])
{

	pid_t pid;
	pid=fork();

	if(pid>0)
	{
		pid_t wpid;
		int status;
		wpid=wait(&status);
		printf("wpid is %d\n",wpid);
		if(WIFEXITED(status))
		{
			printf("exit value is %d\n",WEXITSTATUS(status));
		}
		if(WIFSIGNALED(status))
		{
			printf("exit by signal is%d\n",WTERMSIG(status));
		}
		i+=400;
		printf("this is father process %d\n",getpid());
		printf("i=%d\n",i);
	}
	else if(pid==0)
	{		
		while(1)
		{

			sleep(1);
			i+=200;
			printf("this is child process %d,ppid is %d\n",getpid(),getppid());
			printf("i=%d\n",i);
		}
	}

	for(int i=0;i<5;i++)
	{
		printf("i=======%d\n",i);
	}


	return 9;
}

 

waitpid函数

函数作用:同wait函数

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

参数

  1. pid:指定回收某个子进程

·pid==-1 回收所有子进程

While((wpid=waitpid(-1,&status,0)!=-1));

·pid>0 回收某个pid相等的子进程

·pid==0 回收当前进程组的任一子进程

·pid<0子进程的pid取反(加减号)

  1. status:子进程的退出状态,用法同wait函数
  2. Options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞

返回值:

>0:返回清理掉的子进程ID

-1:回收失败,无子进程

如果为非阻塞

        =0:参数3为WNOHANG,且子进程正在运行

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/wait.h>
int i=200;

int main(int argc,char *argv[])
{

	pid_t pid;
	pid=fork();

	if(pid>0)
	{
		pid_t wpid;
		int status;
		printf("eeeee\n");
		while((wpid=waitpid(-1,&status,WNOHANG))!=-1)
		{
			if(wpid==0)
			{
				continue;
			}
			printf("died wpid is %d\n",wpid);
			if(WIFEXITED(status))
			{
				printf("exit value is %d\n",WEXITSTATUS(status));
			}
			if(WIFSIGNALED(status))
			{
				printf("exit by signal is%d\n",WTERMSIG(status));
			}
			i+=400;
			printf("this is father process %d\n",getpid());
			printf("i=%d\n",i);
		}
	}
	else if(pid==0)
	{		
			printf("ddddddddddddddddddddd\n");
			i+=200;
			printf("this is child process %d,ppid is %d\n",getpid(),getppid());
			printf("i=%d\n",i);
	}

	for(int i=0;i<5;i++)
	{
		printf("i=======%d\n",i);
	}


	return 9;
}

vfork创建进程

vfork也可以创建进程,与fork有什么区别呢?

·区别一:vfork可以直接使用父进程存储空间,不拷贝

·区别二:vfork可以保证子进程先运行,当子进程调用exit退出后,父进程才执行

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

int main()
{
	int count=0;
	pid_t pid;
	pid=vfork();
	if(pid==0)
	{
		while(1)
		{
			printf("i am child ,pid is %d,ppid is %d\n",getpid(),getppid());
			printf("child count=%d\n",count);
			count++;
			if(count==3)
			{
				exit(0);
			}
			sleep(1);
		}
	}
	else if(pid>0)
	{
		while(1)
		{
			printf("i am father pid is %d\n",getpid());
			printf("father countf=%d\n",count);
			sleep(1);
		}
	}

	return 0;
}

进程退出

  1. 正常退出
  1. main函数调用return
  2. 进程调用exit()标准c库
  3. 进程调用 _exit()或者_Exit()属于系统调用

补充:

  1. 进程最后一个线程返回
  2. 最后一个线程调用pthread_exit

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

不管进程如何终止,最后都会执行内核中的同一段代码,这段代码和相应进程关闭所有打开描述符,释放它所使用的存储器等。

对上述任一一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的,对于三个终止函数(exit、_exit、_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数,在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termkination status)。在任意一种情况下,该终止进程的父进程都能用wait或者waitpid函数取得终止状态。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340