Linux--进程

本文深入探讨了操作系统的概念,强调其作为中央控制中心的角色,并详细介绍了进程的管理,包括进程的概念、PCB、状态转换、fork函数、进程优先级以及环境变量。特别讨论了进程的R、S、D、T和Z状态,以及进程的创建、终止和等待机制。此外,还涉及了程序地址空间、虚拟地址与物理地址的关系,以及程序替换的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

🌹操作系统

🎉概念

🎉操作系统理解

🌹进程

🎉概念

 描述进程-PCB

进程和程序的区别

进程控制块

进程控制块成员

 进程查看方法

🎉fork函数

🎉进程状态

 💕R状态

💕S与D状态

    summary:

💕T状态

💕Z状态

进程优先级

基本概念

环境变量

基本概念

常见环境变量

环境变量

概念

常见环境变量

查看环境变量

环境变量组织方式

程序地址空间

虚拟地址空

通过虚拟地址来访问物理地址空间

进程终止

进程等待

进程等待必要性

 进程等待的方法

wait

waitpid

阻塞等待和非阻塞等待

程序替换使用

替换函数


🌹操作系统

🎉概念

任何计算机系统都包含一个基本的程序集和~称为操作系统~而且操作系统包括

  1. 内核(进程管理,内存管理,文件管理,驱动管理)
  2. 其他程序(例如函数库,shell程序等等)

🎉操作系统理解

在我看来既然是操作系统那么就是设计出来为人类服务的系统,我们可以使用它完成某些特定的要求,设想在生活中,总是我们总是有需求~提要求~自己去完成~需求是否可以被实现~我们生活中总是这样~那么操作系统就是一个中央控制中心,当然你可以使用他~但是你不可以随意破化他~那么操作系统怎么判断你是否为非法分子呢~这里就给到了接口形式~!说白了就是函数调用~和c/c++中的函数调用一毛一样~那么这些函数呢就被称之为库函数~

🌹进程

🎉概念

课本概念:程序的一个执行实例,正在执行的程序等

内核观点:担当分配系统资源(CPU时间,内存)的实体。

 描述进程-PCB

      实际上,我们所写的代码在运行起来就是进程,如何管理进程呢?
即先描述,再组织;
      任何进程在形成之时,操作系统要为改进程创建PCB——进程控制模块;简单的将,PCB就是一个结构体( Linux操作系统下的PCB是: struct task_struct ),里面存放了进程相关的属性信息

进程和程序的区别

   首先,我们编写好的程序,在经过编译处理之后,所产生的文件(可执行程序)会放在中,当我们运行程序时,操作系统将磁盘上的文件加载到内存中,同时为它创建PCB(进程控制模块),这两个组合起来才是进程 

 

进程控制块

在Linux中描述进程的结构体叫做task_struct。

task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

进程控制块成员

1.标示符: 描述本进程的唯一标示符,用来区别其他进程。
2.状态: 任务状态,退出代码,退出信号等。
3.优先级: 相对于其他进程的优先级。
4.程序计数器: 程序中即将被执行的下一条指令的地址。
5.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6.上下文数据: 进程执行时处理器的寄存器中的数据。
7.I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8.记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
9.其他信息 

 进程查看方法

使用系统方法:/proc来查看        ls  /proc/

 

🎉fork函数

    先了解一个linux里面的函数~fork

#include<stdio.h>
#include<unistd.h>
int main()
{
 //fork为一个可以创建子进程的函数返回int~
 int id= fork();
 if(id==0)
 {
 while(1)
 {
 
 printf("I am a child,pid:%d  ppid:%d \n",getpid(),getppid());
 sleep(1);
 }
 }
 else if(id>0)
 {
 while(1)
 {
 printf("I am a father,pid:%d  ppid:%d \n",getpid(),getppid());
 sleep(2);
 }
 }
 return 0;
}

 我们知道在ifelse语句只能执行一个但是这个代码却不一样~

 

  fork函数在已存在的进程中创建一个子进程~而子进程由父进程管理并且回收~父进程由bash生成~然后生成自己的pid子进程含有父进程的pid也就是自己的ppid这个好像能理解到就是链表存储下一个节点的指针一样

fork函数创建失败返回<0的值,创建成功分两种情况~第一种情况

给父进程返回子进程的pid.

给子进程返回0; 

fork函数功能: 

分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork 返回,开始调度器调度

  我们可以这样理解fork的返回值~平时一个函数只能返回一个值`但是这里的fork函数创建成功却返回了两个值~父进程返回子进程的pid~子进程返回0;其实就相当于是段程序~第一次程序执行执行了父进程~既然是两个程序那么有两个返回值当然也不为过~所以第一次return 就是父进程返回子进程的pid~第二次return 就是子进程返回0;

🎉进程状态

R:并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里~

S:休眠状态,意味着进程在等待事件完成,也成为可终端睡眠
D:磁盘休眠状态,这个称为不可中断睡眠状态~在这个状态中进程常会等待io结束
T: 停滞状态,可以通过发送single信号进行停止或者激活~
x:死亡状态,死亡状态为返回状态,在进程列表里是看不到的~
Z:僵尸状态,一种等待死亡的状态~

 💕R状态

R状态就是run状态~在排队等待进cpu的状态也会是R状态~

 以上三种状态都被从唤醒进队列显示的是R状态~

 

 

上面这段程序是一直在运行着的~可是他为什么显示的是休眠状态呢?这就和cpu有关了 我们计算机的cpu执行是非常快的~几乎在纳秒级别~所以对于我们的程序来说~程序还没准备好cpu这边已经执行完了~所以这个程序处在休眠状态~

💕S与D状态

    进程需要使用磁盘进行读写的时候,需要磁盘为其分配资源再由cpu对其进行读写~如果磁盘没有就绪的话,此时操作系统就会将这个想要进行资源调用的进程放到一个新的队列里面,进行等待~这个时候进程的状态就为S状态~S状态在等待结束后进入cpu变为R状态~

S状态和D状态的区别~

如果我们把一个正在运行的状态的进程放到一个等待队列~称为等待进程的挂起等待~

唤醒进程:我们把一个等待队列中的进程放到运行队列称之为唤醒~

 S状态和D状态虽然都没有在被执行~他们的区别就在~S状态是已经准备好的~随时要进入运行状态~很明显的就是我们在设置一个延时函数~然后我们可以使用ctrl+c结束这个状态~

而D状态属于深度睡眠状态,.这个状态是不可以被ctrl+c掉的~

    我们可以理解s状态就是一个在等待队列里面的进程~我们知道cpu一次只能调用一个进程~那么这个进程如何保证随时被cpu快速的调用呢~我们把它放在一个等待队列当中~当cpu执行的一个进程结束后直接调用等待队列里面的进程~

    summary:

    从运行状态的task_struct(run_queue),放到等待队列中的状态称为挂起状态`~

    从等待队列,放到运行队列的我们称之为唤醒~~

💕T状态

    T状态我们称之为暂停状态~比如我们在程序调试中打的断点~这个进程到断点的时候停止了~就称之为暂停状态~

 我们可以观察到i这个进程就是一直在运行的状态~被杀掉之后就成为T暂停状态~我们也可以使用continue使他继续~再继续这个进程就会进入到后台运行,这时候再ctrl+c是结束不了这个进程的~

使用kill -9 +pid可以结束这个继承~

💕Z状态

    Z状态我们称之为僵尸状态,也就是这个进程已经停止执行了~但是却没有被回收~这样的进程可能会造成内存泄露的~

int main()
{
 int id=fork();
 if(id==0)
 {
  while(1)
	{
	 cout<<"i am a child"<<endl;
	 sleep(2);
	}
 }
 else
 {
  cout<<"father did nothing"<<endl;
	sleep(20);
 }
 return 0;
}

定义一个fork函数生成两个进程~我们在杀掉子进程之后子进程就会变为僵尸进程~

 如果杀掉父进程那么子进程就会被托管给另外一个进程,那么这个进程就被称为孤儿进程~

观察脚本

while :; do ps axj | head -1 && ps axj | grep test1 |grep -v grep; sleep 1 ; echo "#######################################";done

进程优先级

基本概念

  1.         cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  2.         优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  3.         还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能

查看系统进程指令:ps -l

        之后显示PRI表示优先级~NI表示nice值~

环境变量

基本概念

        环境变量(environment variables) 一般是指在操作系统中用来指定操作系统运行环境的一些参数如:我们在编写C/C++ 代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性~
比如我们已经生成了一个可执行程序,在我们运行它之前总是要加上./,不加的话系统就会报错,那么为什么系统命令不需要加上./再执行呢?比如pwd~ll~ls~之类的~

常见环境变量

PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录 ( 即用户登陆到 Linux 系统中时 , 默认的目录 )
SHELL : 当前 Shell, 它的值通常是 /bin/bash
查看环境变量的方法为echo $NAME:

       

我们也可以使用第三方变量来获取当前环境变量

  

从此可以看出主函数行里面的命令行起到了作用~而且环境变量具有全局属性,可以被子进程继承下去~

环境变量

概念

        环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
        如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
        环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

常见环境变量

PATH : 指定命令的搜索路径

HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)

SHELL : 当前Shell,它的值通常是/bin/bash。

查看环境变量

echo $PATH   用来查看环境变量

环境变量组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以\0结尾的环境字符串

 

程序地址空间

 我们平时说的内存其实不是真正计算机上面的物理内存~这个我之前也有注意到过~因为在写c++代码的时候总忘记释放空间,我就想这样日积月累我的电脑会不会以后么有内存了~然而学了linux才知道~我们平时写的代码全部都是虚拟地址空间~根本是真正意义上的物理地址~

 

我们来证明一下~


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> 
int g_unval;
int g_val = 100;
int main()
{                    
    int a = 10;                                        
    int b = 20;                        
    const char *s = "hello world";       
    printf("code addr:%p\n", main);       //代码区            
    printf("string rdonly addr:%p\n", s); //字符常量区
    printf("uninit addr:%p\n", &g_unval); //未初始化
    printf("init addr:%p\n", &g_val);     //已初始化
    char *heap = (char*)malloc(10);
    printf("heap addr:%p\n", heap);       //堆区
    printf("stack addr:%p\n", &s);        //栈区                         
    printf("stack addr:%p\n", &heap);
    printf("stack addr:%p\n", &a);
    printf("stack addr:%p\n", &b);
    return 0;                           
}

程序地址空间就是这样分配的

虚拟地址空间


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int g_val = 100;
 
int main()
{
    if(fork() == 0){
        int ret = 5;
        while(ret){                                                                     
            printf("hello--- %d g_val = %d &g_val = %p\n", ret, g_val, &g_val);
            ret--;                         
            sleep(1);
            if(ret == 3){
                printf("################child更改数据###############\n");                              
                g_val = 200;
                printf("#############child更改数据完成##############\n");
            }   
        }                                          
    }                                  
    else{                  
        while(1){                                              
            printf("I am father:g_val = %d &g_val = %p\n", g_val, &g_val);                                                     
        }                                                              
    }                                                              
    return 0;                                               
}

 

看这段代码输入什么~

 

 我们发现一块地址里面却放着两个变量~

可以这么理解父进程和子进程不一样都有着不一样的页父进程的虚拟地址通过页表(可以先简单理解为哈希映射表)找到属于自己的物理空间~子进程也是~

所以可以得到的一个结论就是操作系统通过虚拟地址来操控物理地址~

通过虚拟地址来访问物理地址空间

虚拟地址映射物理地址

 

notice :这里需要注意的就是写实拷贝,在对数据进行更改的时候才会真正的给分配空间,

进程终止

每个程序跑完都会自己退出,那么有时候里面的结果会运算不正确那么这个程序还会正常退出么?

那么进程退出就分为3种场景:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程退出的方式:

exit函数

_exit

return

进程等待

进程等待必要性

通过父进程创建好子进程之后,子进程是一定要替父进程做点什么的.那么子进程运行完就需要被检测本身的状态,

父进程通过进程等待的方式来回收子进程,获取子进程的退出信息` 

 进程等待的方法

wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status)

返回值:
    等待成功返回进程pid,失败返回-1;
参数:
    输出型参数,获取子进程退出状态,不关系就设为NULL;

waitpid

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
 当正常返回的时候waitpid返回收集到的子进程的进程ID;
 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
 pid:
 Pid=-1,等待任一个子进程。与wait等效。
 Pid>0.等待其进程ID与pid相等的子进程。
 status:
 WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
 WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
 options:
 WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。

阻塞等待和非阻塞等待

        父进程在等待一个子进程的时候有可能是一直在等,一直在检测着子进程状态,什么时候子进程结束了退出了父进程就等待结束

        这里的等待方式就分为了阻塞等待和非阻塞等待,阻塞等待就是父进程一直在检测子进程状态,并没有取创建其他的子进程, 或者领其他的子进程

        非阻塞等待就是父进程一会来检测一次子进程状态,父进程可以自己去做自己做的事~

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
int main()
{
	pid_t id=fork();
	if(id==0)
	{
		int cnt=5;
		while(cnt--)
		{
			printf("child[%d]is  running : cnt is :%d\n",getpid(),cnt);
			sleep(1);
		}
	//	int a=1;
	//	a/=0;
		exit(11);
	}
	sleep(5);
	printf("father wait begin:\n");
	int status=0;
	//非阻塞等待
	while(1)
	{
		pid_t ret=waitpid(id,&status,WNOHANG);
		if(ret==0)
		{
			//子进程没有退出,但是waitpid等待是成功的,需要父进程重复进行等待,
			printf("DO myself things\n");
		}
		else if(ret>0)
		{
			//子进程退出了也等待成功了获取到相应的结果
			printf("father wait:%d,success,status exit code:%d ,status exit signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);
		}
		else
		{
			//等待失败
			perror("waitpid");
			break;
		}
		sleep(1);
	}
	//阻塞等待代码
	
//	pid_t ret=waitpid(id,&status,0);//默认行为阻塞等待,WNOHANG;设置等待方式为非阻!
//	if(ret>0)
//	{
//		if(WIFEXITED(status))
//		{
//			printf("exit code: %d\n",WEXITSTATUS(status));
//		}
//		else
//		{
//			printf("error,get a signal\n");	
//		}
//	}
	//pid_t ret =wait(NULL);
	//获取父进程接管僵尸进程状态
	//#########################################
	//if(ret>0)
	//{
	//	printf("father wait:%d,success,status exit code:%d ,status exit signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);
	//}
	//else
	//{
	//	printf("father wait failed\n");
	//}
	return 0;
}

 

程序替换使用

在创建好一个程序之后,可以生成它的子进程,如果想要子进程做一点别的事情我们这里使用到程序替换.想要子进程替换当前程序,也只需执行一些函数接口就可以

替换函数

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[]);

 程序替换的原理就是使用上面exec函数去执行另一个程序,那么exec函数知识替换进程的代码和数据,而不是再创建一个新的进程.

替换使用:

        

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
	if(fork()==0)
	{
		printf("command begin\n");
		char* argv[]=
		{
			"ls",
			"-a",
			"-l",
			"-i",
			"-n",
			NULL
		};
		execv("/usr/bin/ls",argv);
		//execl("/usr/bin/ls","ls","-a","-l","-i",NULL);
		printf("command end\n");
		exit(1);
	}
	waitpid(-1,NULL,0);
	printf("wait child success\n");
	return 0;
}

从上面我们可以看到子进程被替换为了一个系统调用命令,而这个进程并未被改变~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值