Linux——进程

文章讨论了进程的地址空间,强调了写时拷贝技术在进程独立性中的作用。进程控制涉及进程的创建、退出以及退出状态。程序替换通过exec系列函数实现,允许进程在不创建新进程的情况下执行新程序。页表在虚拟地址到物理地址映射中起到关键作用,保证了每个进程的独立内存视图。
摘要由CSDN通过智能技术生成

进程地址空间

进程在对于代码的实现时会发生写时拷贝,因为进程是独立。

int main()
{
    int ret=fork();
    int g_val=10;
    if(ret==0)
    {
        //child
        int cnt=10;
         while(cnt--)
         {
            printf("我是子进程,还在运行中:%d。我的pid:%d。g_val:%p\n",cnt,getpid(),&g_val);
         }
         exit(0);
    }
    int ret_id=wait(NULL);
    if(ret>0)
    {
       printf("我是父进程,子进程已经结束了。我的pid:%d。g_val:%p\n",getpid(),&g_val);
    }
    return 0;
}

在这里插入图片描述

进程的开辟需要空间,同一个变量进行不同值传递,取地址。然后发现地址相同但是输出值不同。这里便可以推断进程中数据读取的地址不是物理空间的地址。同一物理空间的值是唯一的。所可以就此推断出在语言层面上的地址不是物理地址,而是虚拟地址(线性地址)。但是对于进程我们在创建是不会只有一个进程,所以每次开辟进程就需要消耗空间。对于线性地址来说也有一定限制。就以32位机器下来说,对于32位机器的内存是以32根总线,每一根总线都会有0、1俩种输入可能。所以就会有232种排列。所以对于32位机器的最大内存就是4G。进程是内核数据结构和代码、数据的一个结构体。会对于空间有占有,进程具有独立性、竞争性、并行性和并发性。所以操作系统就会对于内存的空间进行管理。进程结构体(PCB)中有就会有个变量用来指向地址的地址空间。对于代码区、数据区……分区可以使用建模方式来看待内存。内存就是一个就结构体,结构体内成员是指针。指针是成对出现。用来标记每一个区的开始后结束位置。在指针之间的数据就是线性地址。
在这里插入图片描述
进程独立性和写时拷贝。就是通过页表完成的。页表实现了虚拟地址和物理地址之间的映射。在子进程对于数据进行写入时,会对于内存中的数据和代码进行内存中空间的开辟然后拷贝。页表的虚拟地址不会发生改变,会改变它的物理空间指向。最后实现数据写入。这就是写时拷贝。
在这里插入图片描述
假设没有页表:
进程中的代码和数据是预加载在内存的。对于多个进程访问同一份代码和数据时,CPU通过对于pcb的调度会发生写时拷贝。如果就是直接访问内存中的代码和数据,使用指针访问容易越界
在这里插入图片描述

进程1如果使用指针发生越界行为,就有可能对于进程2的代码和数据进行修改。随意访问地址,会破坏物理内存和影响进程独立性。

在程序中使用动态内存开辟使用时,os还会对空间管理比较严苛,CPU运行速度决定了对于进程都是进行片段的使用然后频繁调度不同的进程,os十分注重效率。当我们使用函数申请空间是os会在进程真正需要时进行空间开辟给进程。os通过缺页中断来实现前面的操作。所以os还可以通过页表来实现1(进程管理)和2(内存管理)的解耦操作。

进程控制

进程结束退出时,os内少个进程,便会释放进程对应的内核数据结构和对应的代码和数据。在退出时OS会对进程退出的状态进行检查。检查是否正常运行完,运行结果是否正确。我们在写一个程序是,常用的main函数就会有return 0.这样的返回值。也就是退出码。退出码通常在0~256之间。进程退出时可以是自己退出,也可以是人为退出。

exit()//1
_exit()//2

上面函数都可以退出进程。

   int main()
   {
     int ret=fork();
    if(ret==0)
    {
      printf("已近执行完该语句");
      sleep(3);
      //_exit(0);
      exit(0);                                                                                                                                                                       
    } 
   return 0;
  }


都可以退出进程但是进程数据_exit不会刷新缓冲区,exit会刷新缓冲区。exit底层还是_exit。

1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit

在这里插入图片描述

进程中程序替换

在子进程运行时,会出现一个一件事是做到一般后突然就变化了。就像在看书时,走神了……。
在OS中用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

通过函数可以使进程执行内容发生改变。就是函数通过改变页表指向的代码来替换。但是不会创建新的进程就是修改页表指向。
在这里插入图片描述
常见函数调用

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

对于函数参数函数名后面带“p”就是已经包含路径。会在环境变量中查找。带“e”的就是需要自己带环境变量。对于函数如果使用多个参数,就必须在参数结尾放上NULL。函数execl和函数execv。末尾字母l表示链表,v表示数组形式。

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

char* argv[]={指令,NULL}
int execv(const char *path, char *const argv[]);

在这里插入图片描述
上面函数的底层调用都是函数

int execve(const char *path, char *const argv[], char *const envp[])

可以说上面函数是下面函数的一个封装。
在这里插入图片描述
以上便是有关进程空间地址、进程控制、程序替换的相关内容。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鱼君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值