Linux进程概念

硬件结构决定了软件行为:
在这里插入图片描述

程序运行,会被加载到内存中
因为程序要想被执行,就需要放在内存中被cpu进行读取处理。
例如:
qq聊天实现:键盘采集输入,放到内存,cpu进行处理,网卡发送数据

操作系统:linux

组成:内核+应用
定位:计算机上搞管理的软件,管理计算机上的软硬件资源
管理:

                      用户
                     库函数
                  系统调用接口
  			   	 操作系统  内核
  					硬件驱动
  				 键盘,鼠标....硬件

在这里插入图片描述

系统调用接口:操作系统提供的用户访问系统内核的接口
库函数与系统调用接口的关系:库函数实际上就是对系统调用接口进行的一层封装后的接口

进程:

进程概念

进程:运行中的程序
pcb:进程控制块:程序运行的描述
通过程序的运行描述,操作系统就可以调度哪个程序可以占用cpu去运行指令,要运行哪个程序,则操作系统找到对应程序的pcb,在pcb取出程序的运行所需信息,加载到cpu上,cpu就开始运行这个程序了。

对于操作系统来说,进程就是一个程序运行的描述,通过这个描述,操作系统可以进行程序的调度运行管理。
实际上对于系统来说,进程就是pcb
这个程序运行的动态描述,叫做pcb进程控制块,在Linux下是一个task_struct结构体。

描述信息:内存指针,上下文数据,程序计数器,进程ID-pid,IO信息,进程优先级,进程状态,记账信息…

时间片:cpu调度运行程序的时间段
时间片过后,就该切换调度下一个进程了

并发:一种轮询处理的方式
并行:同时运行

进程操作

进程的简单操作:
创建进程:进程就是一个pcb是一个task_struct结构体(linux中),创建一个进程实际上就是创建了一个task_struct结构体
pid_t fork(void) --创建进程的接口–通过复制调用这个接口的进程(父进程),创建一个新的进程(子进程)

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

//子进程睡眠5秒退出,父进程继续进行,陷入死循环
int mian(int argc, char *argv[])
{
	printf("-------");
	pid_t pi = fork();
	//子进程复制了父进程,因此往后的代码父进程都会运行,但是因为返回值不同因此进入不同的if
	if(pid < 0)
	{
		//出错了
	}
	else if(pid == 0)
	{
		//子进程--对于子进程返回值是0
		sleep(5);
		exit(0);	//退出进程--谁调用就退出谁	
	}
	else
	{
		//父进程--对于父进程返回的是子进程的pid,因此返回值是大于0
	}
	printf("hello:%d\n", getpid());
	while(1)
	sleep(1);
	return 0;
}

ps -ef :查看所有进程信息
ps -ef | grep fork :查找指定字符(fork)的进程信息
ps -aux | grep fork:更详细信息

进程状态

进程状态:用于操作系统对于进程的管理(什么状态对进程进行什么样的操作)
运行态,就绪态,阻塞态
linux中的进程状态:
运行态(R):正在运行或者轮转到时间片能够运行统称运行态
可中断休眠态S:可以被中断的休眠状态(满足唤醒条件,或者休眠被中断则进入运行态)
不可中断休眠态D:不能被中断的休眠状态(满足唤醒条件之后才会进入运行态)
停止态T:程序停止运行的状态(依然会被调度,但是什么都不做)
死亡态X
僵尸态Z:进程已经退出了不再调度了,但是这个进程的资源还没有被完全释放,等待处理的一种状态。
僵尸进程:处于僵尸态的进程,是一种退出,但是资源没有完全被释放的进程
产生:紫禁城先于父进程退出,但是父进程没有关注到子进程的退出,因此资源不会完全释放子进程的资源,这个子进程进入僵尸状态。
子进程退出之后,在进程pcb中保存了自己的退出返回值,在父进程没有关注处理的情况下,pcb资源是不会被释放
危害:资源泄露(一种是pcb所占的内存资源一直无法被回收,一种是一个用户所能创建的进程数量是有限制的)
解决方案:
处理:退出父进程
避免:进程等待
孤儿进程:父进程先于子进程退出,子进程就会成为孤儿进程,运行在后台,父进程称为1号进程(早期名字叫init进程,后期叫systemd)
精灵进程(守护教程):

环境变量

环境变量:保存运行环境参数的变量;使程序运行环境配置更加灵活;以及可以通过环境变量实现进程间的小量通信(父子进程的传递)
命令操作:env(查看环境变量),set(查看所有变量),echo(查看指定变量),export(声明或设置环境变量),unset(删除变量)
典型环境变量:
PATH:程序运行的默认搜索路径—在命令行终端中输入命令名称可以直接执行对应名称的命令程序,实际上是因为shell扑捉到输入的命令名称之后,然后去PATH环境变量指定的路径下去找这个程序,找到了就运行,找不到就报命令没有找到的错误。

环境变量接口:
char *getenv(const char *name);—通过名称获取值;
铺垫:shell中运行的程序,父进程都是shell;或者反过来说,shell中运行的进程都是shell的子进程
代码操作:getenv()、**extern char environ; 、main(int argc, char *argv[], char *env[])

程序地址空间

程序地址空间:
地址:通常所说的地址,都是内存的地址,是内存单元的编号
实际上在进程中,程序访问的这些地址(变量地址…)实际上是一个假地址
将其称之为–虚拟地址;
我们所说的程序地址空间实际上叫做进程的虚拟地址空间
虚拟地址空间实际上是系统给进程所描述的一个假的地址空间,是一个mm_struct结构体
系统会为每一个进程都描述一个假的地址空间,进程访问的都是虚拟地址,访问内存数据的时候,先将虚拟地址转换成物理地址然后访问

系统为每个进程都描述一个完整的,连续的,线性的虚拟地址空间,实际物理内存用的时候再给进程分配。这样的话,对于每个进程自己的感觉,看起来都有一块完整的,连续的内存可以使用。

虚拟地址空间,是系统为每个进程通过mm_struct结构体虚拟的一个地址空间,使用虚拟地址空间的目的是为了让进程能够访问一块连续的,完整的地址,并且经过页表映射到物理内存之后,可以实现数据在物理内存上的离散式存储,提高内存利用率,并且在页表中可以进行内存访问控制

虚拟地址如何通过页表获取物理地址的呢?

内存管理方式

  • 分段式内存管理:将地址空间分为多段(代码段,数据段…)便于编译器进行地址管理

分段式虚拟地址组成:段号+段内的地址偏移量;在系统中有一个段表:一个个段表项(段号,物理内存段起始地址)

  • 分页式内存管理:将地址空间分为多个小块(页),提高内存利用率

分页式虚拟地址组成:页号+页内偏移;在系统中有一个页表(页号,物理内存快起始地址,权限控制,缺页中断位…)
4G(2^32 )内存;内存页大小4k(1k是1024,为2^10 ; 4k为2^12)
所以内存大小除以内存页大小,一共有2^20个内存页
因此32位内存地址,搞20位就是页号,低12位就是页内偏移

  • 段页式内存管理:将虚拟地址进行分段,在每个分段内进行分页式管理,集合了分段分页的优点进行内存管理

进程控制:

创建

pid_t fork();
– 通过赋值父进程创建一个子进程
–父子进程代码共享,数据独有
返回值:错误返回-1,对于父进程 返回值大于0 - 子进程pid,对于子进程返回0

写时拷贝技术:创建子进程后,子进程与父进程各自有自己的虚拟地址空间,但是数据映射的实际上是同一块物理内存,等待内存发生改变的时候为子进程重新开辟一块独立的空间,保存子进程的数据(保持进程独立性)

对比:
pid_vfork(void);(已经很少使用)
通过赋值父进程创建一个子进程,父子进程公用虚拟地址空间
创建子进程后,父进程会阻塞直到子进程exit退出或者(程序替换)之后才会运行。
公用同一个虚拟地址空间,同时运行会造成栈混乱

退出

进程退出:退出一个进程
main函数中调用return;
使用库函数void exit(int status);
在任意位置调用都可以退出进程,退出之前刷新文件缓冲区
使用系统调用接口 void _exit(int status);
在任意位置调用都可以退出进程

进程的退出返回值:
正常退出:通过三种退出方式退出的进程(结果是否符合预期)
异常退出:程序因为某些错误异常崩溃退出
进程的返回值,实际上只是使用了一个字节进行保存
查看上一次系统调用接口使用错误原因的接口:
void perror(consst char *s);

等待

父进程等待子进程退出;获取退出子进程的返回值,释放资源避免产生僵尸进程
wait/waitpid
阻塞:为了完成一个功能,发起一个调用,但是不具备完成条件,一直等待。
非阻塞:为了完成一个功能,发起一个调用,但是不具备完成条件,立即返回金长城返回值的获取。(通常要求循环操作)

替换

替换进程正在调度运行的程序;
加载一个新的程序到内存中,更新当前进程的页表映射信息到新的程序上。
希望创建的子进程能够完成一个新的任务,多进程更加稳定。
execl/execlp/execle/execvp/execve
l和v的区别:参数赋予方式不同;
有没有p的区别:新的此程序是否需要带路径;
有没有e的区别:是否自己设置环境变量;

从零开始编写一个minishell:

shell:命令行解释器
1.捕捉键盘输入;(scanf,fgets,gets)
2.解析输入信息-命令名称,运行参数
3.创建子进程,(fork)让子进程 执行对应命令名称的程序(程序替换)(execvp)
子进程替换失败必须退出,成功运行完命令程序会自己退出
4.等待子进程退出(wait)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
	whlie(1)
	{
		printf("[user@host path]$ ");
		//刷新缓冲区
		fflush(stdout);
		char buf[1024] = {0};
		fgets(buf, 1023, stdin);
		buf[strlen(buf) - 1] = '\0';
		
		int myargc = 0;
		char *ptr = buf, *myargv[32] = {NULL};
		whlie(*ptr != '\0')
		{
			if(*ptr != ' ')
			{
				myargv[myargc] = ptr;
				myargc++;
				while(*ptr != '\0' && *ptr != ' ')
				ptr++;
				*ptr = '\0';
			}
			ptr++;
		}
		myargv[myargc] = NULL;

		//测试打印
		fpr(int i = 0; i < myargc; i++)
		{
			printf("[%s]\n", myargv[i]);
		}
	
		//创建进程
		pid_t pid = fork();
		//如果小于0,创建进程失败
		if(pid < 0)
		{
			perror("fork error");
			continue;
		}
		else if(pid == 0)
		{
			//子进程进来之后进行程序替换
			execvp(myargv[0], myargv);
			//如果运行失败,进行退出
			perror("execvp error");
			exit(-1);	
		}
		wait(NULL);
	}	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值