IT评测·应用市场-qidao123.com技术社区

标题: Linux:历程地点空间、历程控制(一.历程创建、历程终止、历程等待) [打印本页]

作者: 没腿的鸟    时间: 2024-6-22 13:04
标题: Linux:历程地点空间、历程控制(一.历程创建、历程终止、历程等待)
上次介绍了环境变量:Linux:历程概念(四.main函数的参数、环境变量及其相关操作)


  

1.程序地点空间


   牵涉到内存,肯定有事这张图啦。这次我们写段代码验证一下
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int a;
  4. int init_a = 0;
  5. int main()
  6. {
  7.         printf("code:%p\n", main);//代码
  8.         printf("uninit data:%p\n", &a);//未初始化变量
  9.         printf("init data:%p\n", &init_a);//初始化变量
  10.         char* arr = (char*)malloc(10);
  11.         printf("heap:%p\n", arr);//堆
  12.         printf("stack:%p\n", &arr);//栈
  13.         return 0;
  14. }
复制代码

知识点总结

上述空间排布结构是在内存吗?(历程地点空间引入)

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. int main()
  6. {
  7.     pid_t id = fork();
  8.     if (id == 0)
  9.     {
  10.         int cnt = 0;
  11.         while (1)
  12.         {
  13.             printf("child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
  14.             sleep(1);
  15.             cnt++;
  16.             if (cnt == 3)
  17.             {
  18.                 g_val = 200;
  19.                 printf("child change g_val: 100->200\n");
  20.             }
  21.         }
  22.     }
  23.     else
  24.     {
  25.         while (1)
  26.         {
  27.             printf("father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
  28.             sleep(1);
  29.         }
  30.     }
  31.     return 0;
  32. }
复制代码

   我们都知道父子历程共享代码段,那么一开始二者访问的g_vla值和地点均相同
  后体面历程中改变g_val的值后,二者值不同,但是地点照旧一样
  
  
2.历程地点空间


历程地点空间是操作系统中一个告急的概念,它形貌了一个历程在运行时所能访问的捏造地点范围。每个历程都有自己独立的地点空间,使得多个历程可以同时运行而互相不干扰
   地点空间是指一个历程可以使用的内存范围,通常由连续的地点构成。对于32位系统,历程地点空间通常是从0到4GB,这个范围内包含了代码段、数据段、堆、栈等部分,用于存放程序的指令、数据以及动态分配的内存(就是我们上面那个图)
  每个历程都有自己独立的地点空间,使得历程间可以互相隔离,不会相互干扰。在这个地点空间内,操作系统会进行地点映射,将历程的捏造地点映射到物理内存上,以实现对内存的访问和管理。
  当一个历程被创建时,操作系统会为该历程分配一块内存空间,用来存放历程的地点空间。这个地点空间是捏造的,由于它并不直接对应物理内存中的连续空间,而是通过页表和页表项来映射到物理内存中的不同位置。
  页表(Page Table)是操作系统中用于管理捏造内存的告急数据结构之一。它将历程的捏造地点映射到物理内存中的实际地点,实现了捏造内存的地点转换和管理
明确几个点


历程地点空间实质

代码和数据实际上是存储在物理内存中的,而历程空间(或称为捏造地点空间)里存储的是代码和数据的捏造地点。这些捏造地点通过页表等机制映射到物理内存中的实际地点。
每个历程都有自己的捏造地点空间,这个空间是逻辑上连续的,但并不肯定在物理内存中连续。操作系统负责维护页表,将捏造地点转换为物理地点,从而实现历程对内存的访问。
   操作系统肯定也要对历程地点空间进行管理,那就说明也必要:先形貌再组织
  历程地点空间是数据结构,详细到历程中就是特定数据结构的对象。必要注意的是:这个结构体里不生存代码和数据
  

图示过程


2.1历程地点空间意义

地点空间和页表的结合是操作系统中实现捏造内存管理的关键机制,它们的存在有助于解耦历程管理和内存管理,并提供了保护内存安全的告急本事。

3.创建历程

3.1fork()函数创建子历程补充

   我们之前已经讲了在代码里可以使用fork()函数来。创建子历程规则是:子历程与父历程共享代码,写时拷贝
  历程调用fork,当控制转移到内核中的fork代码后,内核做:
  
  当一个历程调用fork之后,就有两个二进制代码相同的历程。而且它们都运行到相同的地方。但每个历程都将可以开始它们自己的路程
写时拷贝

   通常,父子代码共享,父子再不写入时,数据也是共享的,当恣意一方试图写入,便以写时拷贝的方式各自一份副本
  Linux系统中,当使用fork()系统调用创建子历程时,子历程会继承父历程的地点空间的一个副本。但实际上,在fork()之后到子历程开始写数据之前,父历程和子历程所共享的是同一个物理内存页面。只有当其中一个历程实验修改写入时,操作系统才会进行页面复制,确保每个历程都有自己的数据副本,从而避免了不必要的内存复制开销

页表除了有一系列对应的捏造地点和物理地点以外,还有一列代表权限(针对物理地点的内容的权限)
详细来说,权限字段通常包含以下几种权限:
除了读和写权限外,页表的权限字段还可能包含其他类型的权限,例如执行权限(x),它决定了历程是否可以在该页面上执行代码。在某些系统中,还可能存在特殊的权限字段,如用于控制页面共享、缓存计谋等的字段。
   所以上面写时拷贝的过程里:可以看到在修改内容之前,数据段里的权限也都是只读,这不对吧?(由于,全局变量我们是可以修改的啊)这是在创建子历程后,数据段的页表映射权限由rw权限变为r
  为什么要改啊:改后,如果我们实验写入,会发生错误,这时操作系统就会来完成写入拷贝,又发现你是数据段的本该可以写入,就又把必要写入的历程对应的页表映射由r权限改为rw了
  
4.历程终止

4.1历程退出场景

   
  退出码

   main函数的返回值通常被称为历程退出码或返回状态码。在C、C++等编程语言中,main函数是程序的入口点,当程序执行完毕后,main函数会返回一个整数值给操作系统,这个整数值就是历程退出码。操作系统会根据这个退出码来判定程序是正常竣事照旧出现了某种错误。
  我们自己写main函数时,总是写一个return 0
  
    Linux系统中,你可以使用echo $?下令来检察上一个执行的下令或历程的退出码
  

   但是光看一个数字,我们怎么能知道错误的原因呢? 这就必要把错误码转换为错误形貌
  错误码就是函数的
  strerror()函数是一个C库函数,用于将错误代码转换为对应的错误信息字符串。它接受一个整数参数errno,返回一个指向错误信息字符串的指针。strerror函数的在头文件string.h中,
errno是一个全局变量,用于在C语言中表示发生错误时的错误码。当函数或系统调用发生错误时,errno会被设置为相应的错误码,以便程序可以根据错误码进行得当的错误处理。error是近来一次函数进行调用的返回值
  1. char *strerror(int errnum);
复制代码
其中,errnum参数是一个整数,代表特定的错误码。strerror函数会根据错误码在系统的错误码表中查找对应的错误信息,并将其作为字符串返回。
历程出现非常

   历程出现非常说明历程收到了非常信号,每种信号都有自己的编号(宏编号),而不同的信号编号能表明非常的原因
  kill -l 下令在 Unix 和 Linux 系统中用于列出全部可用的信号。执行这个下令将表现系统支持的全部信号的列表以及它们的编号。这对于相识不同信号的含义和用途非常有效,特殊是在处理历程和历程间通讯时。
下面是一个 kill -l 下令的典型输出示例:
  1. $ kill -l  
  2. 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL  
  3. 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE  
  4. 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2  
  5. 13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT  
  6. 17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP  
  7. 21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU  
  8. 25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH  
  9. 29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN  
  10. 35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  ...  
  11. ...  
  12. 63) SIGRTMAX-1  64) SIGRTMAX
复制代码
一些常见的信号及其用途包括:

4.2历程常见退出方法

4.2.1正常退出

4.2.2非常退出

   使用ctrl + c,能使非常信号终止
  历程终极执行情况

Linux系统中,任何历程终极执行完毕后都会返回一个状态码,这个状态码通常被称为“退出码”或“返回码”(exit code)。这个退出码是一个整数,用于表示历程执行的效果或状态。根据惯例,退出码0通常表示乐成,而非零值表示出现了某种错误。
Linux的上下文中,我们通常讨论的是“信号”(signal),这些信号用于在历程之间通报信息或通知历程发生了某种变乱(如停止、终止等)

4.3 OS会做什么

当历程创建和历程终止时,操作系统会执行一系列的操作来确保系统的稳定性和资源管理的有效性。
历程创建时:

历程终止时:


5.历程等待

5.1必要性


5.2历程等待的方法

5.2.1 wait()方法

wait 方法在Linux 编程中是一个告急的系统调用,它主要用于监视先前启动的历程,并根据被等待的历程的运行效果返回相应的 Exit 状态。在父历程中,wait 方法常被用来接纳子历程的资源并获取子历程的退出信息,从而避免产生僵尸历程。
wait 函数允许父历程等待其子历程竣事,并可以获取子历程的退出状态。在C语言中的用法和参数:
函数原型
  1. #include <sys/types.h>  
  2. #include <sys/wait.h>  
  3.   
  4. pid_t wait(int *status);
复制代码
参数status:这是一个指向整数的指针,用于存储子历程的退出状态。如果父历程不关心子历程的退出状态,可以将这个参数设为 NULL。
返回值

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <sys/types.h>  
  5. #include <sys/wait.h>
  6. int main()
  7. {
  8.         pid_t id = fork();//创建子进程
  9.         if (id == 0)
  10.         {
  11.                 //这里面是子进程
  12.                 int count = 5;
  13.                 while (count--)
  14.                 {
  15.                         printf("child is running. pid:%d , ppid:%d\n", getpid(), getppid());
  16.                         sleep(1);
  17.                         //这里循环5秒
  18.                 }
  19.                 printf("子进程将退出,马上就变成僵尸进程\n");
  20.                 exit(0);//子进程退出了
  21.         }
  22.         //这里是父进程
  23.         printf("父进程休眠\n");
  24.         sleep(10);
  25.         printf("父进程开始回收了\n");
  26.         pid_t rid = wait(NULL);//让父进程进程阻塞等待
  27.         if (rid > 0)
  28.         {
  29.                 printf("wait successfully, rid:%d\n", rid);
  30.         }
  31.         printf("父进程回收了\n");
  32.         sleep(5);
  33.         return 0;
  34. }
复制代码
  代码一共15秒
  
  

5.2.2waitpid()方法

waitpid 是 Unix 和 Linux 系统编程中用于等待子历程竣事并获取其状态的系统调用。它的原型如下:
  1. pid_t waitpid(pid_t pid, int *status, int options);
复制代码
返回值

参数
   如果子历程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,而且释放资源,获得子历程退出信息。
  如果在恣意时刻调用wait/waitpid,子历程存在且正常运行,则历程可能阻塞。
  如果不存在该子历程,则立即出错返回。
  获取子历程status

   
  

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. int main()
  7. {
  8.     pid_t id = fork();
  9.     if(id == 0)
  10.     {
  11.         // child
  12.         int cnt = 5;
  13.         while(cnt)
  14.         {
  15.             printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
  16.             sleep(1);
  17.             cnt--;
  18.         }
  19.         exit(1);
  20.     }
  21.     int status = 0;
  22.     pid_t rid = waitpid(id, &status, 0); // 阻塞等待
  23.     if(rid > 0)
  24.     {
  25.         printf("wait successfully, rid: %d, status: %d\n", rid, status);
  26.     }
  27.     return 0;
  28. }
复制代码


那我们怎么直接获得退出码和信号编号呢?
5.3阻塞等待与非阻塞等待

阻塞等待(wait()与waitpid( , , 0)):

非阻塞等待

  1. int main()
  2. {
  3.         pid_t id = fork();
  4.         if (id == 0)
  5.         {
  6.                 int cnt = 5;
  7.                 while (cnt)
  8.                 {
  9.                         printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
  10.                         sleep(1);
  11.                         cnt--;
  12.                 }
  13.                 exit(1);
  14.         }
  15.         int status = 0;
  16.         pid_t rid = waitpid(id, &status, WNOHANG);
  17.         while (1)
  18.         {
  19.                 if (rid > 0)
  20.                 {
  21.                         //子进程结束了
  22.                         printf("wait successfully, rid: %d, status: %d, exit code: %d\n",
  23.                                 rid, status, WEXITSTATUS(status));
  24.                         break;
  25.                 }
  26.                 else if (rid = 0)
  27.                 {
  28.                         //子进程还没结束
  29.                         //这里能写希望在子进程没结束期间希望父进程干什么
  30.                 }
  31.                 else
  32.                 {
  33.                         //到这里就说明出错了
  34.                         perror("waitpid");
  35.                         break;
  36.                 }
  37.         }
  38.         return 0;
  39. }
复制代码

今天的内容也是不少了,累死了。感谢大家支持!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4