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函数取得终止状态。

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...