Linux之进程

在了解了Linux的基础之后,我今天来说一下进程。

1. 进程概念

1.1 进程的主要信息
进程是运行中的程序。
程序:程序员写的高级语言代码->编译后的机器二进制代码
操作系统层面的进程:进程实际就是对运行中程序的描述,称之为pcb-进程控制块,操作系统通过pcb控制管理程序的运行–linux下:struct task_struct{…}
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息,
task_struct内容分类:
进程标识符PID:描述本进程的唯一标识符,用来区别其他进程。
进程状态: 任务状态,退出代码,退出信号等。
进程优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
1.2 查看进程信息
(1)ps -ef / -aux 查看当前操作系统中的所用进程信息。
(2)进程的信息可以通过 /proc 系统文件夹查看。

2. 进程状态

R-运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里(就绪状态)。
S-睡眠状态(sleeping)/可中断休眠状态:意味着进程在等待事件完成
D-磁盘休眠状态(Disk sleep)/不可中断休眠状态:在这个状态的进程通常会等待IO的结束。
T-停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X-死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Z-僵尸进程(zombie):处于僵死状态的进程,子进程先于父进程退出,若父进程没有关注子进程退出状态,则子进程会进入僵死状态成为僵尸进程。僵尸进程会造成内存泄漏。
孤儿进程:父进程先于子进程退出,则子进程会成为孤儿进程,这个孤儿进程运行在后台,并且被1号init进程领养。
守护进程/精灵进程:是一个特殊的孤儿进程,完全独立的运行系统后台,脱离终端的影响。

3.进程地址空间

虚拟地址空间:操作系统为用户虚拟了一个完整的、连续的地址空间。在进程中每个变量使用的地址都是一个虚拟地址,但是通过页表将虚拟地址转换成物理地址进行内存访问,获取到相应数据,使用这种方式可以实现数据的离散式存储,提高内存利用率,并且可以在页表中对内存的访问做出限制。并且每个进程都有独立的虚拟地址空间,访问的都是自己的虚拟地址,因此进程之间具有独立性。
如何将虚拟地址映射转换为物理地址:
(1)分段式内存管理:对程序编译时的内存地址管理更加友好
虚拟内存地址组成:段号+段内偏移;
(2)分页式内存管理:主要用于提高内存利用率
虚拟内存地址组成:页号+页内偏移;
(3)段页式内存管理:将程序地址空间进行分段式管理,但是在每个段内进行分页式管理,具有两者的优点
虚拟内存地址组成:段号+段内页号+页内偏移

4.进程创建

4.1 fork函数
pid_t fork(void); 通过复制父进程创建一个子进程(父进程运行的代码是一样的,并且子进程也是从创建成功的下一句指令开始运行)
返回值:fork函数的返回值对于父子进程是不一样的,在父进程中,fork会返回子进程的pid;在子进程中,fork会返回0。可以通过返回值对父子进程的运行进行分流。
进程调用fork,当控制转移到内核中的fork代码后,内核会发生:
分配新的内存块和内核数据结构给子进程,将父进程部分数据结构内容拷贝至子进程,添加子进程到系统进程列表当中 ,fork返回,开始调度器调度
4.2 vfork函数
pid_t vfork(void); —创建一个子进程,父进程共用同一个虚拟地址空间,使用同一个栈,会造成调用栈混乱,因此父进程调用vfork创建子进程,会被阻塞,直到子进程exit()退出/进行了程序替换,重新创建了自己的虚拟地址空间之后,父进程才会vfork返回继续运行。
4.3 进程创建示例:

int main( void ) 
{   pid_t pid;
  printf("Before: pid is %d\n", getpid());   
  if ( (pid=fork()) == -1 )
  perror("fork()"),exit(1); 
  printf("After:pid is %d, fork return %d\n", getpid(), pid);   
  sleep(1);   
  return 0; 
 }   
5.进程终止

5.1 进程退出场景
(1)正常退出,结果符合预期
(2)正常退出,结果不符合预期
(3)异常退出,结果不能作为判断基准
5.2 进程退出函数
(1)main函数的return:执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做exit的参数。
(2)void _exit(int status);系统调用接口,2号手册
参数:status 定义了进程的终止状态,父进程通过wait来获取该值。
(3)void exit(int status);库函数,3号手册
调用过程:1.执行用户通过 atexit或on_exit定义的清理函数。 2. 关闭所有打开的流,所有的缓存数据均被写入 3. 调用_exit

6.进程等待

进程等待:等待子进程退出,获取子进程的返回值,允许操作系统释放子进程资源,避免产生僵尸进程。
等待方式
(1)pid_t wait(int *status);----阻塞函数
等待任意一个子进程退出,通过status获取返回值,释放子进程资源,返回退出的子进程的pid
(2)pid_t waitpid(int pid,int *status,int options);
参数: pid:pid=-1,等待任意一个子进程。与wait等效;pid>0.等待其进程ID与pid相等的子进程。
status: WIFEXITED(status):判断进程是否正常退出,若为正常终止子进程返回的状态,则为真;WEXITSTATUS(status): 获取退出子进程的返回值,若WIFEXITED非零,提取子进程退出码。
options: 0则默认阻塞等待子进程退出,WNOHANG则waitpid为非阻塞,若没有子进程已经退出,则立即返回
阻塞:为了完成一个功能发起调用,当前若不具备完成功能的条件;则一直挂起等待,直到条件满足完成功能后返回。
非阻塞:为了完成一个功能发起调用,当前若不具备完成功能的条件;则立即报错返回。
返回值获取:
只有进程正常退出的时候,获取进程的返回值,才有意义
判断进程是否正常退出:通过status数据的低7位是否为0,若为0,则进程是正常退出
返回值:返回值存储在status的低16位中的高8位
status & 0x7f 获取程序异常退出信号值
(status >> 8) & 0xff 获取程序的退出返回值
注意事项:
(1)如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
(2)如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
(3)如果不存在该子进程,则立即出错返回。
(4)wait/waitpid不是子进程退出的时候才去回收而是只要有已经退出的子进程,就都可以回收。
代码示例:
进程的阻塞等待方式:

#include <sys/wait.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h>
int main()
{
	pid_t pid;
	pid = fork();
	if (pid < 0){
		printf("%s fork error\n", __FUNCTION__);
		return 1;
	}
	else if (pid == 0)
	{ //child        
		printf("this is child, pid is : %d\n", getpid());        
		sleep(5);        
		exit(257);
	}
	else{
		int status = 0;        
		pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S        
		printf("this is test for wait\n");        
		if( WIFEXITED(status) && ret == pid )
		{            
			printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));       
		}
		else
		{            
			printf("wait child failed, return.\n");           
			return 1;        
		}   
	}    
	return 0; 
}

进程的非阻塞等待方式:

#include <stdio.h>  
#include <unistd.h> 
#include <stdlib.h> 
#include <sys/wait.h>
 
int main() 
{   
	pid_t pid;      
	pid = fork();   
	if(pid < 0)
	{      
		printf("%s fork error\n",__FUNCTION__);     
		return 1;    
	}
	else if( pid == 0 )
	{ //child       
		printf("child is run, pid is : %d\n",getpid());   
		sleep(5);        
		exit(1);    
	} 
	else
	{        
		int status = 0;      
		pid_t ret = 0;       
		do       
		{           
			ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待           
			if( ret == 0 )
			{               
				printf("child is running\n");         
			}            
			sleep(1);       
		}
		while(ret == 0);            
		if( WIFEXITED(status) && ret == pid )
		{           
			printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));    
		}
		else
		{           
			printf("wait child failed, return.\n");      
			return 1;   
		}    
	}    
	return 0; 
} 
7.进程替换

程序替换:替换一个进程正在运行的程序,在一个进程中,可以先将另一端程序加载到内存中,将自己的页表映射到新的程序位置,初始化pcb中的虚拟地址空间中的代码段和数据段信息,这时候pcb(当前进程)则运行另一端程序
替换函数
int execl(const char *path, const char *arg, …);
参数:path:带有路径的新程序名称
arg:新程序得运行参数
int execlp(const char *file, const char *arg, …); 、
参数:file:一个文件名(会在PATH环境变量指定得路径下去找相应得程序)
int execle(const char *path, const char *arg, …,char *const envp[]);
参数:path:带有路径的新程序名称
argv:新程序的运行参数信息
envp:为新程序自定义环境变量信息
int execve(const char *filename, char *const argv[], char *const envp[]);
参数:filename:新的程序代码文件名称(带路径的文件名)
argv:新程序的运行参数信息
envp:新程序的环境变量信息
返回值:失败返回-1,成功则进程运行新程序
int execvp(const char *file, char *const argv[]);
参数:file:一个文件名(会在PATH环境变量指定得路径下去找相应得程序)
argv[]:新程序得运行参数数组
int execv(const char *path, char *const argv[]);
参数:path:带有路径的新程序名称
arg:新程序得运行参数数组
区别:
有没有p:程序名称是否需要带路径
有没有e:是否自定义环境变量
l和v的区别:程序运行参数的赋值方式不同
代码示例

#include <unistd.h>
 
int main() 
{    
   char *const argv[] = {"ps", "-ef", NULL};    
   char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
   execl("/bin/ps", "ps", "-ef", NULL);
   // 带p的,可以使用环境变量PATH,无需写全路径    
   execlp("ps", "ps", "-ef", NULL);
   // 带e的,需要自己组装环境变量    
   execle("ps", "ps", "-ef", NULL, envp);
   execv("/bin/ps", argv);       
   // 带p的,可以使用环境变量PATH,无需写全路径    
   execvp("ps", argv);
   // 带e的,需要自己组装环境变量    
    execve("/bin/ps", argv, envp);
    exit(0); 
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值