第四章:Linux进程概念与环境变量

本文详细介绍了冯诺依曼体系结构,强调了内存对计算机性能的影响。接着,探讨了操作系统的概念、目的和特点,以及系统调用和库函数的角色。接着,深入讨论了进程的管理,包括进程的基本概念、状态(如阻塞、挂起、运行/就绪)、创建和终止,以及进程的优先级。此外,还讲解了环境变量和命令行参数在进程中的应用,以及进程地址空间的分布和管理。
摘要由CSDN通过智能技术生成

系列文章目录



前言

进程是操作系统分配资源的最小单位,linux系统上的进程是如何表示和操作的呢?


一、冯诺依曼体系结构

大部分的计算机硬件体系都是 冯诺依曼体系
在这里插入图片描述

  • 输入设备:键盘、话筒、摄像头、网卡 、磁盘等
  • 输出设备:显示器、磁盘、网卡、声卡、音响
  • 存储器:这里就是指 内存
  • 中央处理器:运算器和控制器

1、内存的意义

  • 输入设备的数据读取速度是最慢的
  • CPU的寄存器的数据读取速度是最快的
  • 所有设备都只能直接和内存打交道(由硬件体系所决定),但不代表除了内存外设备之间不会传递信息
  • 有了内存,我们可以将数据预加载进内存,从而提升了计算机整体的运行速度

2、数据流

数据传输即使是些简单操作,都用到每一部分。

在这里插入图片描述

QQ中传递文件

在这里插入图片描述

QQ中聊天

在这里插入图片描述

二、操作系统

1、管理的概念

在这里插入图片描述

管理就是对被管理者先抽象成对象,再组织对象

2、操作系统的概念

  • 任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)
  • 操作系统给用户提供良好的接口,但操作系统并不信任用户,所以提供了一系列系统调用接口
  • 操作系统通过对下管理好软硬件资源(手段),对上给用户提供良好、安全、高效的执行环境(目的)
  • 操作系统包括:内核(进程管理,内存管理,文件管理,驱动管理)+ 其他程序(例如函数库,shell程序等等)

在这里插入图片描述

3、操作系统的目的

  • 与硬件交互,管理所有的软硬件资源
  • 为用户程序(应用程序)提供一个良好的运行环境
    在这里插入图片描述

4、操作系统的特点

  • 系统调用:OS提供的接口
  • 硬件部分遵守冯诺依曼体系
  • OS不信任任何用户,任何对系统硬件或者软件访问,都必须通过OS的手
  • 计算机体系是一个层状结构,任何访问硬件或者软件的行为,都必须通过OS接口,贯穿OS进行访问
  • 库函数:语言或者第三方库(第一方:系统的、第二方:自己的,其余是第三方的)给我们提供的接口

5、系统调用和库函数概念

  • 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
  • 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

6、深度理解系统调用

在这里插入图片描述

操作系统控制CPU执行程序
CPU执行程序的指令
可能会使得操作系统会去执行系统调用
操作系统会将系统调用的指令发给CPU
CPU来完成操作系统提供的功能

在执行系统调用时,CPU会执行一系列指令,包括将请求参数压入栈中、执行中断指令、将控制权转移到中断服务例程等。这些指令是由CPU执行的,但它们不属于用户程序的指令集,而是属于操作系统内核的指令集。因此,虽然系统调用需要使用指令来实现,但它本身不是指令,而是一个操作系统提供给用户程序的接口。

系统调用和CPU执行指令也有密切的关系。系统调用是操作系统提供给用户程序的一组接口,用户程序可以通过这些接口来请求操作系统提供的服务,例如读写文件、进行网络通信等。系统调用通常需要通过CPU执行指令来完成,因为CPU是计算机系统中的核心硬件,它负责执行指令,完成各种计算和操作。
在这里插入图片描述
当用户程序需要执行系统调用时,它会通过一些特殊的指令(例如中断指令)来请求操作系统提供相应的服务。CPU会响应这些特殊指令,执行操作系统提供的中断服务例程。中断服务例程是操作系统中的一段代码,它负责处理用户程序请求的服务,并将结果返回给用户程序。在执行中断服务例程时,CPU会切换到操作系统的特权模式,以便访问操作系统的各种资源和服务。
在这里插入图片描述

比如我们看C语言程序加载时会有一个对应的C程序布局模型(进程虚拟地址空间),实际上在操作系统中是不存在的,这就是系统调用的作用

因此,系统调用和CPU执行指令是密不可分的,它们共同构成了计算机系统中的核心功能。

7、总结

计算机管理硬件:描述起来,用struct结构体组织起来,用链表或其他高效的数据结构
操作系统是进行软硬件资源管理的软件(其中管理的本质是先描述在组织(是对数据的管理)
管理分为三种:管理者、执行者、被管理者(eg管理者为OS、执行者为驱动程序、被管理者为底层硬件)

三、进程

1、基本概念

任何启动并运行程序的行为——操作系统都会帮助我们把程序转换成为进程!

课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
操作系统:内核关于进程的数据结构(PCB) + 当前进程的代码和数据

在这里插入图片描述

描述进程-PCB

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。称之为PCB(process control block),Linux操作系统下的PCB是: task_struct 。task_struct是PCB的一种
  • 在Linux中描述进程的结构体叫做task_struct。
  • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

task_ struct内容分类

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

PCB的内容与文件的属性有一定联系,但二者完全不同

2、查看进程

ps ajx
//查看进程
ps ajx | head -1 && ps axj | grep "test" | grep -v grep
//带标题栏和过滤带有“test"的进程
top
//查看进程占资源情况
ls /proc
ls /porc/xxx -al
//这些目录保存了当前系统中运行的所有进程的信息,创建一个进程就会有一个对应的目录(PID)产生

3、获取进程id——getpid/getppid

GETPID(2)    Linux Programmer's Manual    GETPID(2)

NAME
       getpid, getppid - get process identification

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       pid_t getpid(void);
       pid_t getppid(void);

DESCRIPTION
       getpid() returns the process ID of the calling process.  
       (This is often used by routines that generate unique temporary filenames.)

       getppid() returns the process ID of the parent of the calling process.

ERRORS
       These functions are always successful.

4、bash

  • bash命令行解释器,本质上它也是一个进程!
  • 命令行启动的所有程序,最终都会变成进程,而该进程对应的父进程一般都是bash

5、终止进程

kill -9 21314
//终止pid为21314的进程

6、通过系统调用创建进程-fork

FORK(2)    Linux Programmer's Manual    FORK(2)

NAME
       fork - create a child process

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);

DESCRIPTION
       fork()  creates a new process by duplicating the calling process.  
       The new process, referred to as the child, is an exact duplicate of the calling
       process, referred to as the parent, except for the following points:

       *  The child has its own unique process ID, and this PID does not match the ID 
       of any existing process group (setpgid(2)).

       *  The child's parent process ID is the same as the parent's process ID.

       *  The child does not inherit its parent's memory locks (mlock(2), mlockall(2)).
       
       *  Process resource utilizations (getrusage(2)) and CPU time counters (times(2)) are 
       reset to zero in the child.

       *  The child's set of pending signals is initially empty (sigpending(2)).

       *  The child does not inherit semaphore adjustments from its parent (semop(2)).

       *  The child does not inherit record locks from its parent (fcntl(2)).

       *  The child does not inherit timers from its parent (setitimer(2), alarm(2), timer_create(2)).

       *  The child does not inherit outstanding asynchronous I/O operations f
       rom its parent (aio_read(3), aio_write(3)), nor does it inherit  any  asyn‐
       chronous I/O contexts from its parent (see io_setup(2)).

       The  process  attributes  in the preceding list are all specified in POSIX.1-2001.  
       The parent and child also differ with respect to the following
       Linux-specific process attributes:

       *  The child does not inherit directory change notifications (dnotify) from its 
       parent (see the description of F_NOTIFY in fcntl(2)).

       *  The prctl(2) PR_SET_PDEATHSIG setting is reset so that the child does not 
       receive a signal when its parent terminates.
       
       *  The default timer slack value is set to the parent's current timer slack value.  
       See the description of PR_SET_TIMERSLACK in prctl(2).
       
       *  Memory mappings that have been marked with the madvise(2) MADV_DONTFORK flag 
       are not inherited across a fork().

       *  The termination signal of the child is always SIGCHLD (see clone(2)).

       *  The port access permission bits set by ioperm(2) are not inherited by the child; 
       the child must turn on any bits that it  requires  using  iop‐erm(2).

       Note the following further points:
       
       *  The  child process is created with a single thread—the one that called fork().  
       The entire virtual address space of the parent is replicated in the child, including 
       the states of mutexes, condition variables, and other pthreads objects; 
       the use of pthread_atfork(3) may  be  helpful  for dealing with problems that this can cause.

       *  The  child  inherits  copies  of  the  parent's  set  of open file descriptors.  
       Each file descriptor in the child refers to the same open file
       description (see open(2)) as the corresponding file descriptor in the parent.  
       This means that the  two  descriptors  share  open  file  status  flags, current file offset, 
       and signal-driven I/O attributes (see the description of F_SETOWN and F_SETSIG in fcntl(2)).

       *  The  child  inherits copies of the parent's set of open message queue descriptors (see 
       mq_overview(7)).  
       Each descriptor in the child refers tothe same open message queue description as the 
       corresponding descriptor in the parent.  
       This means that the  two  descriptors  share  the  same  flags (mq_flags).

       *  The  child  inherits copies of the parent's set of open directory streams (see opendir(3)). 
       POSIX.1-2001 says that the corresponding directory
       streams in the parent and child may share the directory stream positioning; 
       on Linux/glibc they do not.

RETURN VALUE
       On success, the PID of the child process is returned in the parent, 
       and 0 is returned in the child.  
       On failure, -1 is returned in the parent,  
       no child process is created, and errno is set appropriately.
ERRORS
       EAGAIN fork() cannot allocate sufficient memory to copy the 
       parent's page tables and allocate a task structure for the child.

       EAGAIN It  was  not  possible to create a new process because the
       caller's RLIMIT_NPROC resource limit was encountered.  To exceed this limit, the
       process must have either the CAP_SYS_ADMIN or the CAP_SYS_RESOURCE capability.

       ENOMEM fork() failed to allocate the necessary kernel structures because memory is tight.

       ENOSYS fork() is not supported on this platform (for example, hardware without a 
       Memory-Management Unit).

7、创建子进程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
  printf("AAAAAAAAAAAAAAAA: pid: %d, ppid: %d\n", getpid(), getppid());
  pid_t ret = fork();
  printf("BBBBBBBBBBBBBBBB: pid: %d, ppid: %d, ret: %d, &ret: %p\n", getpid(), getppid(), ret, &ret);

//  while(1)
//  {
//    printf("我是一个进程,我的pid是%d\n, 我的父进程pid是%d\n",getpid(), getppid());
//    sleep(1);
//  }
  return 0;

}

//结果
AAAAAAAAAAAAAAAA: pid: 30472, ppid: 2742
BBBBBBBBBBBBBBBB: pid: 30472, ppid: 2742, ret: 30473, &ret: 0x7ffda2f7d78c
BBBBBBBBBBBBBBBB: pid: 30473, ppid: 30472, ret: 0, &ret: 0x7ffda2f7d78c

从结果可见,fork函数创建了子进程30473,并且执行了fork后的代码,并且fork函数在父进程返回子进程pid,在子进程中返回0。并且在两个进程中变量的地址是一样,这和进程地址空间有关。

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h> 

int main()
{
  pid_t ret = fork();

  if(ret == 0)
  {
    //子进程
    while(1)
    {
      printf("我是子进程, 我的pid是:%d, 我的父进程是:%d\n", getpid(), getppid());
      sleep(1);
    }
  }
  else if(ret > 0)
  {
    //父进程 
    while(1)
    {
      printf("我是父进程, 我的pid是:%d, 我的父进程进程是:%d\n", getpid(), getppid());
      sleep(1);
    }
  }

  return 0;
}


//结果
是父进程, 我的pid是:1113, 我的父进程进程是:2742
我是子进程, 我的pid是:1114, 我的父进程是:1113
我是父进程, 我的pid是:1113, 我的父进程进程是:2742
我是子进程, 我的pid是:1114, 我的父进程是:1113
我是父进程, 我的pid是:1113, 我的父进程进程是:2742
我是子进程, 我的pid是:1114, 我的父进程是:1113
我是父进程, 我的pid是:1113, 我的父进程进程是:2742
我是子进程, 我的pid是:1114, 我的父进程是:1113
我是父进程, 我的pid是:1113, 我的父进程进程是:2742
我是子进程, 我的pid是:1114, 我的父进程是:1113

fork产出了子进程1114,同时在父进程中返回子进程pid,在子进程中返回0,执行了各自进程里的if-else分流

8、fork原理

在这里插入图片描述

在这里插入图片描述

当我们函数内部准备执行return的时候,我们的主题功能已经完成
return语句也是代码,父子进程共享代码,所以被执行了两次

fork之后哪个进程先运行由调度器决定
fork之后,fork之后的代码共享,通常我们通过if和else来进行执行流分流
fork有两个返回值的原因是在创建子进程成功之后,子进程和父进程共享代码
fork:子进程的返回值是0,父进程返回值是子进程的pid,因为子进程只有一个父进程,而父进程有多个子进程,需要对每个子进程进行标识(pid),并且记住他们

父子进程代码共享(从fork中的return开始),数据各自开辟空间,私有一份(采用写时拷贝)
进程在运行的时候具有独立性,不会影响其他进程

四、进程状态

1、阻塞状态

  • 阻塞:进程因为等待某种条件就绪,而导致一种不推进的状态——进程卡住了

阻塞一定是在等待某种资源,进程要通过等待的方式,等具体的资源被别人完成之后,再被自己使用,阻塞:进程等待某种资源就绪的过程
资源:磁盘、网卡、显卡等各种外设

在这里插入图片描述

进程等待某种资源的过程,进程不被CPU调度,即进程暂停运行。
阻塞:阻塞就是不被调度。一定时因为当前进程需要等待某种资源,进程task_truct结构体需要在某种被OS管理的资源下排队

2、挂起状态

  • 挂起:进程在内存中但不被CPU调度,此时将进程的代码与数据暂时性由操作系统交换到磁盘
    在这里插入图片描述

3、运行/就绪状态

  • 运行/就绪:进程在运行队列中,处于等待被运行或正在被运行

在这里插入图片描述

pcb在被操作系统维护在什么队列,进程就会处于什么状态
进程只要是运行/就绪状态,并不直接代表该进程只在被CPU调度运行,而代表该进程在运行队列中排队

4、linux内核源码

//task_struct是一个结构体,内部会包含各种属性,就有状态属性
struct task_struct
{
	int status;
	...
}


/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束,操作系统不可以终止该进程。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
  • Z(zombie)-僵尸进程:

在这里插入图片描述

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

int main()
{
  while(1)
  {
    printf("我在运行吗\n");
  }
  return 0;
} 

在这里插入图片描述

printf会申请显示器资源,如果申请不到显示器资源则进程会进入阻塞状态,如果注释掉printf,那么该进程就不会等待资源,会一直处于就绪执行状态。

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

int main()
{
    int count = 1;
    while(1)
    {

        printf("我在运行吗? %d\n", count++);
        sleep(1);
    }
    return 0;
} 
kill -19 23333 
//暂停pid为23333的进程

kill -18 23333
//继续运行pid为23333的进程

killall test
//终止进程名为test的进程

在这里插入图片描述

可以暂停进程和继续执行进程,但STAT中少了+号,代表该进程从前台运行转换成后台运行,这是不能再用ctrl + c终止该进程了
调试程序时,运行中的程序在断点处停下来,本质就是让进程暂停

5、僵尸进程

我们让进程帮我们办事,我们只关心结果,那么进程退出码就可以帮助我们知道进程是否正常运行
所以linux当进程退出时,一般进程不会立即彻底退出,而是维护一个Z状态,即僵尸状态——方便后序父进程/OS读取该子进程退出结果

  1. 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
  2. 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  3. 只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h> 

int main()
{
  pid_t ret = fork();

  if(ret == 0)
  {
    //子进程
    while(1)
    {
      printf("我是子进程, 我的pid是:%d, 我的父进程是:%d\n", getpid(), getppid());
      sleep(1);
    }
  }
  else if(ret > 0)
  {
    //父进程
    while(1)
    {
      printf("我是父进程, 我的pid是:%d, 我的父进程是:%d\n", getpid(), getppid());
      sleep(1);
    }
  }
  return 0;
}

在这里插入图片描述

维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护
那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!会造成内存泄漏

6、孤儿进程

  1. 父进程先退出,子进程就称之为“孤儿进程”
  2. 孤儿进程被1号init进程领养,当然要有init进程回收喽
  3. 如果没有领养,子进程后序再退出,就没有进程来回收了

在这里插入图片描述

终止子进程的父进程,子进程的父进程就变成init(pid = 1),且子进程变成后台进程

五、进程优先级

进程的优先级:

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

1、优先级VS权限

  • 优先级是在一定能得到某种资源,只是先后的问题
  • 权限是决定你能还是不能得到某种资源
  • 优先级是得到某种资源(CPU)的先后顺序,其本质是因为资源有限(CPU)

2、查看进程优先级

在这里插入图片描述

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

Linux的优先级由pri和nice值共同确定(优先级的数值越小,优先级越高;优先级的数值越大,优先级越低)
nice值就是优先级的修正数据,范围是[-20,19]
优先级不可能一味的高,也不可能一味的低(操作系统的调度器要适度地考虑平衡问题,避免“饥饿问题”)

3、设置进程优先级

  • PRI是进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  • NI就是nice值了,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,PRI(new)=PRI(old)+nice
  • 当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是-20至19,一共40个级别。
  • 进程的nice值不是进程的优先级,但是进程nice值会影响到进程的优先级变化。
    在这里插入图片描述

4、进程的其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

六、环境变量与命令行参数

1、基本概念

在bash中要执行某个可执行程序,就是要输入可执行程序的绝对路径或相对路径,环境变量就是可以使bash指令在环境变量中的目录寻找可执行程序

环境变量本质就是一个内存级的一张表,这张表由用户在登录系统的时候,进行给特定用户形成属于自己的环境变量表
环境变量中的每一个变量都有自己的用途:有的是进行路径查找的,有的是进行身份认证的,有的是进行动态库查找的,有的是用来进行确认当前路径等等,每个环境变量都有自己的特点应用场景,有变量名和变量内容。
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
在linux中把可执行程序拷贝到系统默认路径下,让我们可以直接访问的方式,相当于Linux下软件的安装!

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前Shell,它的值通常是/bin/bash。

环境变量的数据是从系统相关配置文件中读取的

/etc/bashrc
.bash_profile
.bashrc

source .bashrc 
. .bashrc
//上面两条指针实现同样的功能:是配置文件.bashrc生效

2、查看环境变量

在这里插入图片描述

查看PATH环境变量,查看环境变量方法:echo $NAME //NAME:你的环境变量名称

在这里插入图片描述

查看所有环境变量

3、修改环境变量

  • echo: 显示某个环境变量值
  • export: 设置一个新的环境变量(本地变量添加到环境变量)
  • exort:给环境变量添加路径
  • env: 显示所有环境变量
  • unset: 清除环境变量
  • set: 显示本地定义的shell变量和环境变量
  • unset: 取消环境变量和本地变量

在这里插入图片描述

3、环境变量通常是具有全局属性的

环境变量是由shell进程维护的

命令行中一般有两个变量:本地变量、环境变量
本地变量:只能够在当前shell命令行解释器内被访问,不可以被子进程继承
环境变量:具有”全局属性“ 可以被子进程继承
在这里插入图片描述

int main()
{
	printf("hello = %s\n", getenv("hello"));
	return 0;
}

在这里插入图片描述

可以看出bash子进程继承了bash的环境变量

4、命令行参数

命令行参数可以帮助我们设计出,在同一个程序中可以设计出不同的业务功能

argc

  • int argc:命令行参数的个数,他是个整数。

当没有任何参数存储的时候,他的值为1

argv[ ]

  • char* argv[]:字符串指针数组,存的是命令行参数的内容的首地址

这个表也是由shell制作的,子进程使用这个表

在这里插入图片描述

int main()
{
	printf("%d\n", argv);
	for(int i = 0;i < argc;i++)
   {
     printf("argv[%d]->%s \n", i, argv[i]);
   }
}

//[admin1@VM-4-17-centos lesson12]$ ./myPATH 
//结果
1
argv[0]->./myPATH 


//[admin1@VM-4-17-centos lesson12]$ ./myPATH -a -b
//结果
3
argv[0]->./myPATH 
argv[1]->-a 
argv[2]->-b 

在这里插入图片描述

总结

void Usage(const char* name)
{
  printf("\nUsage:%s -[a][b][c]\n\n",name);
  exit(0);
}

int main(int argc, char *argv[], char* envp[])
{
  if(argc != 2) Usage(argv[0]);

  if(strcmp(argv[1], "-a") == 0)printf("打印当前目录下的文件名\n");
  else if(strcmp(argv[1], "-b") == 0)printf("打印当前目录下的文件的详细信息\n");
  else if(strcmp(argv[1], "-c") == 0)printf("打印当前目录下的文件名(包含隐藏文件)\n");
  else printf("其他功能在开发中");
  return 0;

}

在这里插入图片描述

通过命令行参数,可以将不同的参数传给程序,实现不同的功能

5、进程中的环境变量

envp[ ]

  • char* envp[]:字符串指针数组,指向环境变量表
    在这里插入图片描述
int main(int argc, char *argv[], char* envp[])
{
	for(int i = 0;envp[i];i++)
	{
	  printf("envp[%d]:%s\n", i, envp[i]);
	}
  	return 0;
}
//结果
envp[0]:XDG_SESSION_ID=247850
envp[1]:TERM_PROGRAM=vscode
envp[2]:HOSTNAME=VM-4-17-centos
envp[3]:TERM=xterm-256color
envp[4]:SHELL=/bin/bash
envp[5]:HISTSIZE=3000
envp[6]:SSH_CLIENT=27.8.233.221 49832 22
envp[7]:TERM_PROGRAM_VERSION=1.80.1
envp[8]:USER=admin1
envp[9]:LS_COLORS=rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:
envp[10]:LD_LIBRARY_PATH=:/home/admin1/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/admin1/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/admin1/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
envp[11]:PATH=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/bin/remote-cli:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/admin1/.local/bin:/home/admin1/bin
envp[12]:MAIL=/var/spool/mail/admin1
envp[13]:PWD=/home/admin1/linux_code/lesson12
envp[14]:LANG=en_US.utf8
envp[15]:VSCODE_GIT_ASKPASS_EXTRA_ARGS=
envp[16]:HOME=/home/admin1
envp[17]:SHLVL=5
envp[18]:VSCODE_GIT_ASKPASS_MAIN=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/extensions/git/dist/askpass-main.js
envp[19]:LOGNAME=admin1
envp[20]:VSCODE_GIT_IPC_HANDLE=/run/user/1001/vscode-git-f531bb27c4.sock
envp[21]:SSH_CONNECTION=27.8.233.221 49832 10.0.4.17 22
envp[22]:VSCODE_IPC_HOOK_CLI=/run/user/1001/vscode-ipc-fb92df34-64dc-482c-b2cc-2239ba8c5e97.sock
envp[23]:LESSOPEN=||/usr/bin/lesspipe.sh %s
envp[24]:BROWSER=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/bin/helpers/browser.sh
envp[25]:PROMPT_COMMAND=__vsc_prompt_cmd_original
envp[26]:VSCODE_GIT_ASKPASS_NODE=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/node
envp[27]:GIT_ASKPASS=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/extensions/git/dist/askpass.sh
envp[28]:XDG_RUNTIME_DIR=/run/user/1001
envp[29]:HISTTIMEFORMAT=%F %T 
envp[30]:COLORTERM=truecolor
envp[31]:_=./myPATH
envp[32]:OLDPWD=/home/admin1/linux_code

我们的进程内部本身就是有环境变量

environ

  • extern char** environ:二级指针指向envp字符串指针数组

在这里插入图片描述

int main()
{
   extern char **environ;
   for(int i = 0;environ[i];i++)
   {
     printf("environ[%d]:%s\n", i, environ[i]);
   }
  return 0;

}
//结果
environ[0]:XDG_SESSION_ID=247850
environ[1]:TERM_PROGRAM=vscode
environ[2]:HOSTNAME=VM-4-17-centos
environ[3]:TERM=xterm-256color
environ[4]:SHELL=/bin/bash
environ[5]:HISTSIZE=3000
environ[6]:SSH_CLIENT=27.8.233.221 49832 22
environ[7]:TERM_PROGRAM_VERSION=1.80.1
environ[8]:USER=admin1
environ[9]:LS_COLORS=rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:
environ[10]:LD_LIBRARY_PATH=:/home/admin1/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/admin1/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/admin1/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
environ[11]:PATH=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/bin/remote-cli:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/admin1/.local/bin:/home/admin1/bin
environ[12]:MAIL=/var/spool/mail/admin1
environ[13]:PWD=/home/admin1/linux_code/lesson12
environ[14]:LANG=en_US.utf8
environ[15]:VSCODE_GIT_ASKPASS_EXTRA_ARGS=
environ[16]:HOME=/home/admin1
environ[17]:SHLVL=5
environ[18]:VSCODE_GIT_ASKPASS_MAIN=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/extensions/git/dist/askpass-main.js
environ[19]:LOGNAME=admin1
environ[20]:VSCODE_GIT_IPC_HANDLE=/run/user/1001/vscode-git-f531bb27c4.sock
environ[21]:SSH_CONNECTION=27.8.233.221 49832 10.0.4.17 22
environ[22]:VSCODE_IPC_HOOK_CLI=/run/user/1001/vscode-ipc-fb92df34-64dc-482c-b2cc-2239ba8c5e97.sock
environ[23]:LESSOPEN=||/usr/bin/lesspipe.sh %s
environ[24]:BROWSER=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/bin/helpers/browser.sh
environ[25]:PROMPT_COMMAND=__vsc_prompt_cmd_original
environ[26]:VSCODE_GIT_ASKPASS_NODE=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/node
environ[27]:GIT_ASKPASS=/home/admin1/.vscode-server/bin/74f6148eb9ea00507ec113ec51c489d6ffb4b771/extensions/git/dist/askpass.sh
environ[28]:XDG_RUNTIME_DIR=/run/user/1001
environ[29]:HISTTIMEFORMAT=%F %T 
environ[30]:COLORTERM=truecolor
environ[31]:_=./myPATH
environ[32]:OLDPWD=/home/admin1/linux_code

getenv

GETENV(3)    Linux Programmer's Manual    GETENV(3)
NAM
       getenv, secure_getenv - get an environment variable

SYNOPSIS
       #include <stdlib.h>

       char *getenv(const char *name);

       char *secure_getenv(const char *name);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       secure_getenv(): _GNU_SOURCE


DESCRIPTION
	The  getenv()  function  searches  the  environment  
	list  to find the environment variable name, and 
	returns a pointer to the corresponding value string.

       The GNU-specific secure_getenv() function is just like getenv() except 
       that it returns NULL in cases where "secure execution" is required.  
       Secure execution is required if one of the following conditions 
       was true when the program run by the calling process was loaded:

       *  the  process's  effective user ID did not match its real user ID or 
       the process's effective group ID did not match its real group ID 
       (typically this is the result of executing a set-user-ID or 
       set-group-ID program);

       *  the effective capability bit was set on the executable file; or
       *  the process has a nonempty permitted capability set.

       Secure execution may also required if triggered by some Linux security 
       modules.

       The secure_getenv() function is intended for use in general-purpose 
       libraries to avoid vulnerabilities that could occur  if  set-user-ID  
       or
       set-group-ID programs accidentally trusted the environment.

RETURN VALUE
       The getenv() function returns a pointer to the value in the environment, 
       or NULL if there is no match.
int main()
{
     char* pwd = getenv("PWD");
     if(pwd == NULL)perror("getenv");
     else 
     {
       printf("PWD: %s\n", pwd);
     }
       return 0;
}
//结果
PWD: /home/admin1/linux_code/lesson12

七、进程地址空间

1、进程地址空间的分布

在这里插入图片描述
在这里插入图片描述

这个并不是物理内存分布,即进程地址空间不是内存地址空间
进程地址空间,会在进程的整个生命周期内一直存在,直到进程退出

2、操作系统的内存管理

在这里插入图片描述

操作系统为每个进程分配内存,又用结构体描述分配的内存,进行管理
进程地址空间:本质是一个内核数据结构,struct mm_struct{}

3、虚拟内存地址空间

在这里插入图片描述

虚拟内存地址空间就是地址的集合(0x000…0 ~ 0xFFF…F)
操作系统通过虚拟内存地址空间(结构体)管理分配内存,该地址空间是线性的
地址空间本质是进程看待内存的方式,是抽象出来的一个概念,内核struct mm_struct,这样的每一个进程,都认为自己独占系统内存资源
区域划分本质:将线性地址空间划分成为一个一个的area,[start,end]
虚拟地址本质:在[start,end] 之间的各个地址叫做虚拟地址

4、虚拟内存与物理内存

在这里插入图片描述

虚拟地址是操作系统提供的,数据和代码一定在物理内存上(冯诺依曼规定),因此需要将虚拟内存转化成物理内存(由OS自动完成)
虚拟内存地址可以通过页表转换成物理内存地址,从而管理内存

  1. 虚拟内存与页表就是通过软件的方式来防止物理内存直接/随意/越权访问,保护了其他进程和物理内存
  2. 缺页中断:进程向OS申请内存,地址空间立刻发生改变,但操作系统在进程真正需要的时候才给,操作系统一般不允许任何的浪费或不高效即进程申请内存不一定立马给使用
  3. 解耦合:进程看到的内存就是虚拟地址空间,进程不会关心物理内存的管理
  4. 编译好了的可执行程序,在没加载到内存时,程序内部已经有虚拟地址了。所以源代码就是按照虚拟地址空间的方式进行对代码和数据编好了对应的编制(ELF),编译器也要遵守虚拟地址
  5. 文件中包含了虚拟地址,文件加载到内存时会通过页表将虚拟地址转换成物理地址,这样程序执行时,CPU是可以读取物理内存上的代码,开始执行程序
  6. 虚拟地址空间可以让进程以统一的视角看待自己的代码和数据

在这里插入图片描述

ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。

6、检验进程地址空间

#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h> 

int g_value = 100; //全局变量

int main()
{
  pid_t id = fork();
  assert(id >= 0);
  if(id == 0)
  {
    //child
    while(1)
    {
      printf("我是子进程,我的id是:%d, 我的父进程是:%d, g_value: %d, &g_value: %p\n", getpid(), 
          getppid(), g_value, &g_value);
      sleep(1);
      g_value++;
    }
  }
  else
  {
    //father 
    while(1)
    {
      printf("我是父进程,我的id是:%d, 我的父进程是:%d, g_value: %d, &g_value: %p\n", getpid(), 
          getppid(), g_value, &g_value);
      sleep(2);
    }
  }
}

//结果
我是父进程,我的id是:3194, 我的父进程是:2392, g_value: 100, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 100, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 101, &g_value: 0x60105c
我是父进程,我的id是:3194, 我的父进程是:2392, g_value: 100, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 102, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 103, &g_value: 0x60105c
我是父进程,我的id是:3194, 我的父进程是:2392, g_value: 100, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 104, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 105, &g_value: 0x60105c
我是父进程,我的id是:3194, 我的父进程是:2392, g_value: 100, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 106, &g_value: 0x60105c
我是子进程,我的id是:3195, 我的父进程是:3194, g_value: 107, &g_value: 0x60105c

父子进程中的g_val的地址竟然是一样的
进程对全局数据进行修改,并不影响父进程!——进程具有独立性
进程的代码和数据必须是独立的(写时拷贝)
语言层面用的地址是虚拟地址/线性地址|

在这里插入图片描述

  • 任何的编程语言里面的地址,绝对不是物理地址,而是虚拟地址(C++/C语言中的&得到的是虚拟地址不是物理地址)
  • 虚拟地址是操作系统提供的,数据和代码一定在物理内存上(冯诺依曼规定),因此需要将虚拟内存转化成物理内存(由OS自动完成)
  • 父子进程代码共享,而数据是各自私有一份的(写时拷贝)
  • 当所有程序运行起来之后,该程序立即变成进程

在这里插入图片描述


总结

进程管理与内存管理是操作系统的非常重要作用。
强烈的信仰会赢取坚强的人,然后又使他们更坚强。——华特贝基霍

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值