进程概念(二)

进程概念(二)

1. 进程状态

进程运行时,进程会被CPU调度,但是我运行了很多个进程,凭什么你这个进程就被先运行,这里就是被操作系统管理,这里就要说说进程状态,实际上这里的凭什么就因为进程有状态。

1.1 阻塞和挂起状态

那么,这里我们就要提起两个概念(阻塞/挂起):

阻塞:进程因为等待某种条件就绪,而导致的一种不推进的状态。什么意思呢?首先进程=内核关于进程的数据结构+当前进程的代码和数据,首先数据都是经过CPU调度来运算的,那么进程也是,假如进程已经创建和运行起来了,但是没有被CPU调度,那么这里就是阻塞。再设想,我们在Windows下启动了太多的软件,会出现卡的请况,实际上这里就是启动了太多的进程,CPU调度不过来,当前正在调度的在运行,没有被调度的就是没运行,所谓的阻塞就是卡住了,这里的卡住了就是在等待某种软硬件资源,这里的资源可以指网卡、显卡、磁盘等。**那么进程为什么阻塞呢?进程要通过等待的方式,等具体的资源被别的进程用完后,再被自己使用。那么阻塞就是进程等待某种资源就绪的过程。**从数据结构来理解阻塞:首先进程在被操作系统管理的时候会被描述成一个结构体,硬件在被操作系统管理的时候同样是一个结构体,当我们在官网上下载软件的时候,突然断网了,这里这个进程就不会再被CPU调度,那么怎么解释呢?实际上当我们正在官网上下载的时候这个结构体是指向的CPU(抽象理解),突然断网了就要等待网卡资源,实际上这个进程的结构体就指向了网卡这个结构体,排在网卡这个结构体的后面。

再说一个例子,我们在写C语言的 scanf 时,当我们运行起来,光标会在控制台上一直闪烁,实际上这个进程就是阻塞的,它是在等待键盘输入,也就是在等待键盘的资源。

挂起:首先进程是内核相关进程的数据结构+代码和数据,当我们把进程运行起来,那么就是加载到内存中,那么当我们在进程被CPU调度的时候(也就是在官网上下载的时候),突然断网了,就会阻塞,这里进程暂时不会被运行,那么资源放在内存中就会被浪费,这里存放在内存中的代码和数据就会被释放掉这部分空间,然后把代码和数据暂时存放在磁盘中,需要用的时候就会从磁盘中拿取。那么,挂起状态就是操作系统把数据和代码管理放在磁盘中

1.2 进程状态

上面我们谈及到了阻塞和挂起状态,这里面呢我们谈及到了数据结构来理解进程阻塞和挂起,那么进程是什么状态一般是看进程在那个队列排队,也就是在那个硬件资源排队

kernel源代码定义:

/*
* 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): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

1.2.1 进程查看+S状态+R状态

ps axj | head -n1 && ps axj | grep mytest | grep -v grep

上面当我们执行这个test.c程序时,加载到内存中就相当于一个进程了,我们是正在运行吗?这里的状态显示的是S+,也就是休眠状态吗,我们不是已经运行起来了吗,为什么这个进程是休眠状态呢?再来看一个样例:

上面再执行test.c程序时,这里的进程状态显示的时R+,也就是运行状态,我们所观察到的代码不都差不多吗,为什么上面那个程序中while循环中有printf库函数,下面这个进程中没有所显示的状态不一洋呢?这是为什么呢?printf库函数本质就是向外设打印信息,当CPU执行printf这库函数代码的时候,这里就要访问外设,但是这里如果时频繁打印的时候,CPU会很快速的去运行,外设和CPU的速度差太大,导致外设就不一定是就绪的,那么这里就会产生阻塞,这个进程就会被放在对应的外设的结构体队列中。实际上是运行的状态,只不过CPU的速度太快了,执行这一行代码的速度非常快,但是外设没有那么快,导致CPU请求外设资源很慢,这里CPU是不会等外设资源到来再去进行下一个任务,这里就会让这个进程放在外设队列中等待资源到来,所以有很多时间这个进程都是在等待,导致效果上是休眠状态。下面这没有printf库函数的代码是R+状态,是因为没有访问外设资源的动作,不会有阻塞的发生。R状态直接代表进程在运行,实际上是指进程在运行队列中就算做R状态。

1.2.2 D状态

故事:首先OS是管理者身份,进程是被管理者,磁盘是一个进程的小弟。当我们电脑中有很多进程的时候,不防止出现了很多进程都没有运行,也就是下属很多都没有干活,没有干活的话此时就被挂起,一个进程对应的数据和代码都放在了磁盘中,此时OS管理者看到了很多进程下属没有干活就直接给它开除了,但是我们用数据的时候呢,此时数据找不到对用的进程下属了,此时OS管理者和进程和磁盘就开始扯皮,最终要怪的还是OS的设计者,此时设计者就想到了一个处理办法,就是让这里的进程不可中断,就是这里的进程下属很重要,不能被开除,所以就出现了D状态,那么此时操作系统都无法删除这个进程,只有当这个进程自己进入CPU中运行的时候才会中断。系统不能出现D状态,出现D状态说明磁盘被占用空间很大,此时这个进程不能删除,一直等到数据被CPU处理完后这个进程才可中断

1.2.3 T状态

查看kill掉进程的操作:

运行起来程序后使用kill -19 PID来kill掉程序:

此时我们发现当我们kill掉进程后,状态就变成了T状态。也可以让当前就前进程继续运行,这里使用到kill -18 PID.

当我们在运行起来进程时,我们发现状态变成了S状态,之前没有kill掉程序时还是S+状态,同时我们ctrl+c杀掉程序是不可以的。

为什么这里S状态少了个+字符,原因:首先要清楚不带+字符表示是在后台运行,带上+字符表明是在前台运行的,所谓后台运行就好比是又开了个进程,这个进程一直在运行,但是你kill不掉;前台的程序可以直接用ctrl+c终止掉程序。那么怎么来解决问题呢?使用kill -9 PID来kill掉后台或者前台的程序。

1.2.4 t状态

实例:就是当我们打断点后执行到断点处时就会有这个追踪停止状态:

1.2.5 Z状态(僵尸状态)

那么为什么需要创建进程?因为我们需要创建进程来做完相对应的任务,那么这里我们就有两种情况:关心结果和不关心结果。

例子:我们在写C或者C++代码的时候main(){return 0;}这里的return 0是什么呢?其实就是进程的退出码。

这么查看进程的退出码呢?echo $?

如果一个进程退出了,立马X状态了,bash作为父进程,有没有机会拿到退出结果呢?Linux当进程退出的时候,进程不会立即彻底退出,而是维持一个Z状态,目的时方便父进程读取子进程的退出结果!

那么如何看到Z状态呢?子进程退出,但是不要回收子进程。

Z状态的危害:进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!内存泄漏?是的!如何避免请看后续!

1.3 孤儿进程

僵尸进程是父子进程同时运行时,当我们子进程结束后,此时的子进程并不是直接结束,而是处于僵尸状态,方便父进程来读取子进程的结果!那么,当我们父子进程同时运行时,假设父进程先结束了,子进程是个什么状态呢?

这里Makefile文件中的 @ 表示的是目标文件, @表示的是目标文件, @表示的是目标文件,^表示的是依赖文件列表

上面现象中,我们观察到当我们的父子进程同时运行的时候,父进程先结束了,子进程就在后台运行,父进程是直接结束了,不是说当进程结束后是维持在Z状态吗,那么这里的父进程为什么是直接结束了?原因是这里的父进程的父进程是bash,而当这个父进程结束后,bash会自己回收掉这个父进程,所以这个父进程就直接结束了。这里的子进程为什么父进程变成了1?父进程退出后,子进程要被PPID为1的这个父进程领养,这个PPID为1的父进程就是操作系统。结论:父进程退出,子进程会被OS自动领养(PPID为1的进程就是OS),那么这个被领养的进程就是孤儿进程

为什么子进程会被OS领养?原因就是:如果不领养,子进程后续再退出就没有被回收了,会造成内存泄漏。

这里kill掉后台进程的操作:kill -9 PID 或者 killall 进程名

2. 环境变量

2.1 背景

我们所常见的学java的时候我们需要配置环境变量,这里的环境变量就是这里的所谓的环境变量。

**概念:环境变量一般是指在OS中用来指定OS运行环境的一些参数。**例子:我们在编写C/C++程序的时候,在链接的时候,从来不知道我们的所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是相关环境变量帮助编译器进行查找。此外,环境变量在系统中通常具有全局性!

那么这里有一个问题来进行切入:我们在Linux上写的代码,编译之后,运行的时候为什么要带上./呢?

这里的可执行程序和系统的ls、pwd等等指令都是可执行程序,那么自己写的可执行程序和系统自带的可执行程序有区别吗?没有区别

那么都是可执行程序,那么这里就是上面的问题,为什么自己写的可执行程序需要带上./运行,而系统的指令的可执行程序带上./运行不行呢?

原因就在于这里的通过该环境变量在系统当中在特定的条件下没有找到ls指令。执行一条命令的前提一定是找到它。所以系统存在环境变量用来存放对应的路径的、用户、登录用户等等。

查看环境变量:

echo $PATH

在我的Linux distribution下运行的结果是:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin

怎么证明,系统指令ls、pwd等指令是存在变量中呢?

[jyh@VM-12-12-centos study5]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
[jyh@VM-12-12-centos study5]$ which ls
alias ls='ls --color=auto'
	/usr/bin/ls
[jyh@VM-12-12-centos study5]$ 

那么怎么让自己的可执行程序在运行前不同加上./运行呢?

[jyh@VM-12-12-centos study5]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   75 Feb 20 22:58 Makefile
-rwxrwxr-x 1 jyh jyh 8408 Feb 21 20:47 myproc
-rw-rw-r-- 1 jyh jyh  554 Feb 20 23:37 myproc.c
[jyh@VM-12-12-centos study5]$ pwd
/home/jyh/linux_-stu/study5
[jyh@VM-12-12-centos study5]$ export PATH=/home/jyh/linux_-stu/study5
[jyh@VM-12-12-centos study5]$ echo $PATH
/home/jyh/linux_-stu/study5
[jyh@VM-12-12-centos study5]$ myproc 
hello 3
hello 2
hello 1
[jyh@VM-12-12-centos study5]$ cat myproc
-bash: cat: command not found
[jyh@VM-12-12-centos study5]$ cat myproc.c
-bash: cat: command not found
[jyh@VM-12-12-centos study5]$ 

这里有个问题,我把我们的可执行程序路径放进了环境变量,此时可执行程序可以不带./执行,但是系统指令找不到了,这时不要慌,可以把我们的图形化界面程序关掉重新打开就会恢复PATH。那么这里怎么解决既可以不带./执行自己写的可执行程序又能执行系统自带指令程序呢?

[jyh@VM-12-12-centos study5]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   75 Feb 20 22:58 Makefile
-rwxrwxr-x 1 jyh jyh 8408 Feb 21 20:47 myproc
-rw-rw-r-- 1 jyh jyh  554 Feb 20 23:37 myproc.c
[jyh@VM-12-12-centos study5]$ pwd
/home/jyh/linux_-stu/study5
[jyh@VM-12-12-centos study5]$ export PATH=$PATH:/home/jyh/linux_-stu/study5 //: 表示追加
[jyh@VM-12-12-centos study5]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin:/home/jyh/linux_-stu/study5
[jyh@VM-12-12-centos study5]$ myproc 
hello 3
hello 2
hello 1
[jyh@VM-12-12-centos study5]$ ./myproc 
hello 3
hello 2
hello 1
[jyh@VM-12-12-centos study5]$ cat myproc.c
#include <stdio.h>
#include <unistd.h>

int main()
{
  int count = 3;
  while(count){
    printf("hello %d\n", count--);
    sleep(1);
  }
}
[jyh@VM-12-12-centos study5]$ 

另外一种方法就是把对应的路径直接拷贝到/usr/bin目录下。

所以在Linux中,把可执行程序拷贝到系统默认路径下让我们可以直接访问的方式就相当于Linux下软件的安装!

针对什么环境变量,我们再来谈一谈权限管理>我们奇怪的是这个文件的拥有者是一个私人用户,默认情况下,其他人是无法进程写入的。那么这一现象用环境变量来解释就是:正因为有环境变量的存在,当我们登陆的时候,系统就已经知道了我是谁,通过这里的我是谁以及其他权限配置来禁止其他用户写入东西到我们的文件中。查找自己的身份时可以使用指令:echo $USER

2.2 认识环境变量

查看本用户对应的环境变量配置:env ;以及查看本地变量和环境变量的指令:set(用的并不是很多);取消本地变量:unset + 本地变量名

[jyh@VM-12-12-centos study5]$ env
XDG_SESSION_ID=12568
HOSTNAME=VM-12-12-centos //(机器名)
TERM=xterm
SHELL=/bin/bash //(shell)
HISTSIZE=3000 //(历史命令数量最大值)
SSH_CLIENT=171.43.199.212 2950 22
OLDPWD=/home/jyh
SSH_TTY=/dev/pts/0
USER=jyh //(用户)
LD_LIBRARY_PATH=:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: //(配色方案)
MAIL=/var/spool/mail/jyh
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin (可执行程序搜索路径)
PWD=/home/jyh/linux_-stu/study5 //(当前文件路径)
LANG=en_US.utf8
SHLVL=1
HOME=/home/jyh //(家目录)
LOGNAME=jyh //(登录用户)
SSH_CONNECTION=171.43.199.212 2950 10.0.12.12 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1002
HISTTIMEFORMAT=%F %T 
_=/usr/bin/env
[jyh@VM-12-12-centos study5]$ 

2.3 获取环境变量

铺垫:main函数最多可以带几个参数呢?int argc、char* argv[]、char* envp[],其中最后一个envp是环境变量表。

其实这里环境变量表就是一个指针数组,这个数组中的每一个元素都存放的是字符串,以’\0’为结束标志的字符串,这个数组中最后的一个无效元素肯定是NULL,用来记录大小。

这个环境变量具体是存的什么呢?那么这里去访问这个环境变量表:

[jyh@VM-12-12-centos study5]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  161 Feb 21 21:39 env.c
-rw-rw-r-- 1 jyh jyh  150 Feb 21 21:40 Makefile
-rwxrwxr-x 1 jyh jyh 8360 Feb 21 21:40 myenv
-rw-rw-r-- 1 jyh jyh  568 Feb 21 21:29 myproc.c
[jyh@VM-12-12-centos study5]$ cat env.c
#include <stdio.h>

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;
}
[jyh@VM-12-12-centos study5]$ ./myenv 
envp[0]->XDG_SESSION_ID=12568
envp[1]->HOSTNAME=VM-12-12-centos
envp[2]->TERM=xterm
envp[3]->SHELL=/bin/bash
envp[4]->HISTSIZE=3000
envp[5]->SSH_CLIENT=171.43.199.212 2950 22
envp[6]->OLDPWD=/home/jyh
envp[7]->SSH_TTY=/dev/pts/0
envp[8]->USER=jyh
envp[9]->LD_LIBRARY_PATH=:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
envp[10]->LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
envp[11]->MAIL=/var/spool/mail/jyh
envp[12]->PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin:/home/jyh/linux_-stu/study5
envp[13]->PWD=/home/jyh/linux_-stu/study5
envp[14]->LANG=en_US.utf8
envp[15]->SHLVL=1
envp[16]->HOME=/home/jyh
envp[17]->LOGNAME=jyh
envp[18]->SSH_CONNECTION=171.43.199.212 2950 10.0.12.12 22
envp[19]->LESSOPEN=||/usr/bin/lesspipe.sh %s
envp[20]->PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
envp[21]->XDG_RUNTIME_DIR=/run/user/1002
envp[22]->HISTTIMEFORMAT=%F %T 
envp[23]->_=./myenv
[jyh@VM-12-12-centos study5]$ 

我们所看到的就差不多是我们直接执行系统指令env看到的结果,这里的指针数组的每个元素所对应的就是一个个字符串。

那么我们不要main函数参数可以访问环境变量吗?可以

这里我们需要使用到extern char** environ这个二级字符指针,它是指向环境字符串数组的,也就是指向envp字符串指针数组的。操作如下:

[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myenv  myproc.c
[jyh@VM-12-12-centos study5]$ cat env.c 
#include <stdio.h>
#include <unistd.h>

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

[jyh@VM-12-12-centos study5]$ make
make: `myenv' is up to date.
[jyh@VM-12-12-centos study5]$ ./myenv 
environ[0]->XDG_SESSION_ID=12568
environ[1]->HOSTNAME=VM-12-12-centos
environ[2]->TERM=xterm
environ[3]->SHELL=/bin/bash
environ[4]->HISTSIZE=3000
environ[5]->SSH_CLIENT=171.43.199.212 2950 22
environ[6]->OLDPWD=/home/jyh
environ[7]->SSH_TTY=/dev/pts/0
environ[8]->USER=jyh
environ[9]->LD_LIBRARY_PATH=:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
environ[10]->LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
environ[11]->MAIL=/var/spool/mail/jyh
environ[12]->PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin:/home/jyh/linux_-stu/study5
environ[13]->PWD=/home/jyh/linux_-stu/study5
environ[14]->LANG=en_US.utf8
environ[15]->SHLVL=1
environ[16]->HOME=/home/jyh
environ[17]->LOGNAME=jyh
environ[18]->SSH_CONNECTION=171.43.199.212 2950 10.0.12.12 22
environ[19]->LESSOPEN=||/usr/bin/lesspipe.sh %s
environ[20]->PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
environ[21]->XDG_RUNTIME_DIR=/run/user/1002
environ[22]->HISTTIMEFORMAT=%F %T 
environ[23]->_=./myenv
[jyh@VM-12-12-centos study5]$ 

我们这里可以来直接遍历得到需要的环境变量,但是这样就很麻烦,所以系统提供了一个接口:char* getenv(char* name);获取成功就返回环境变量内容,失败就返回NULL,操作如下:

[jyh@VM-12-12-centos study5]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  552 Feb 21 22:09 env.c
-rw-rw-r-- 1 jyh jyh  151 Feb 21 22:03 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Feb 21 22:09 myenv
-rw-rw-r-- 1 jyh jyh  568 Feb 21 21:29 myproc.c
[jyh@VM-12-12-centos study5]$ ./myenv 
该环境变量的内容是:jyh
[jyh@VM-12-12-centos study5]$ cat env.c 
#include <stdio.h>
#include <stdlib.h>

int main()
{
  char* user = getenv("USER");
  if(user == NULL){
    perror("没有此环境变量!\n");
    exit(-1);
  }
  else{
    printf("该环境变量的内容是:%s\n", user);
  }
  return 0;
}

[jyh@VM-12-12-centos study5]$ 

当然这就是ls、pwd指令怎么执行后的结果是我们所看到的结果的原因,原因就是从环境变量中直接过间接的获取。

2.4 环境变量是什么

环境变量本质就是字符串,这里的多个环境变量就形成了一张内存级的环境变量表(字符串指针数组),这张表由用户登录系统的时候就会形成给用户,环境变量中的每一个环境变量都有自己的用途,例如:可执行程序搜索查找、身份认证等等。

**那么环境变量是从哪里来的呢?环境变量都是从系统的相关配置文件中读取的!**这里的文件就比如:etc/bashrc/

这里怎么来理解环境变量表是内存级的呢?

[jyh@VM-12-12-centos study5]$ myval=100
[jyh@VM-12-12-centos study5]$ echo $myval
100
[jyh@VM-12-12-centos study5]$ 

这里我们可以在命令行上来定义一个变量myval,我们说shell实际上是一个进程,它是用来读取命令和命令行的,其实当shell启动时,就会去维护一个环境变量表,然后这个变量就是在shell内部定义的,当我们执行命令的时候,这里就是创建了一个子进程,然后shell进程会把环境变量表再拷贝给子进程。所以,这个环境变量是在shell中被维护的!怎么证明呢?

[jyh@VM-12-12-centos study5]$ export mypassword='12345678'
[jyh@VM-12-12-centos study5]$ env
XDG_SESSION_ID=13984
HOSTNAME=VM-12-12-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=171.43.199.212 1479 22
OLDPWD=/home/jyh/linux_-stu
SSH_TTY=/dev/pts/0
USER=jyh
LD_LIBRARY_PATH=:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
mypassword=12345678
MAIL=/var/spool/mail/jyh
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
PWD=/home/jyh/linux_-stu/study5
LANG=en_US.utf8
SHLVL=1
HOME=/home/jyh
LOGNAME=jyh
SSH_CONNECTION=171.43.199.212 1479 10.0.12.12 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1002
HISTTIMEFORMAT=%F %T 
_=/usr/bin/env
[jyh@VM-12-12-centos study5]$ 

上面我们观察到,当我们输入指令export mypassword='12345678’后,这个变量就被导入到了环境变量中,说明了这个环境变量表是被shell来维护的!那么怎么证明创建新的子进程后shell进程会拷贝环境变量表到子进程呢?

[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  test.c
[jyh@VM-12-12-centos study5]$ make
gcc -o mytest test.c
[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  mytest  test.c
[jyh@VM-12-12-centos study5]$ ./mytest 
12345678
[jyh@VM-12-12-centos study5]$ env
XDG_SESSION_ID=13984
HOSTNAME=VM-12-12-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=171.43.199.212 1479 22
OLDPWD=/home/jyh/linux_-stu/study5/test.c
SSH_TTY=/dev/pts/0
USER=jyh
LD_LIBRARY_PATH=:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
mypassword=12345678
MAIL=/var/spool/mail/jyh
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
PWD=/home/jyh/linux_-stu/study5
LANG=en_US.utf8
SHLVL=1
HOME=/home/jyh
LOGNAME=jyh
SSH_CONNECTION=171.43.199.212 1479 10.0.12.12 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1002
HISTTIMEFORMAT=%F %T 
_=/usr/bin/env
[jyh@VM-12-12-centos study5]$ 

所以,环境变量是可以被相关的子进程继承下去的,环境变量具有全局属性!

上述中我们导入环境变量表中要使用到export指令,但是我们不使用export指令会出现什么现象呢?

[jyh@VM-12-12-centos study5]$ myenviron=12345
[jyh@VM-12-12-centos study5]$ echo $myenviron
12345
[jyh@VM-12-12-centos study5]$ vim test.c
[jyh@VM-12-12-centos study5]$ cat test.c
#include <stdio.h>
#include <stdlib.h>


int main()
{
  printf("%s\n", getenv("myenviron"));
  return 0;
}
[jyh@VM-12-12-centos study5]$ make
gcc -o mytest test.c
[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  mytest  test.c
[jyh@VM-12-12-centos study5]$ ./mytest 
Segmentation fault
[jyh@VM-12-12-centos study5]$ env
XDG_SESSION_ID=13984
HOSTNAME=VM-12-12-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=171.43.199.212 1479 22
OLDPWD=/home/jyh/linux_-stu/study5/test.c
SSH_TTY=/dev/pts/0
USER=jyh
LD_LIBRARY_PATH=:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
mypassword=12345678
MAIL=/var/spool/mail/jyh
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
PWD=/home/jyh/linux_-stu/study5
LANG=en_US.utf8
SHLVL=1
HOME=/home/jyh
LOGNAME=jyh
SSH_CONNECTION=171.43.199.212 1479 10.0.12.12 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1002
HISTTIMEFORMAT=%F %T 
_=/usr/bin/env
[jyh@VM-12-12-centos study5]$ 

上述中现象告诉我们如果我们不加export来定义变量,它就不是导入进环境变量,这种变量叫做本地变量,只能在shell内部有效,如果想再导入到环境变量表中让子进程继承,那么直接可以使用指令:export 变量名。所以export的作用是把本地变量导入到环境变量表中!

题外话:再来说一个命令:. 。这个命令其实就是一个点,这个点命令其实就等于source,用法就是source 配置文件名,作用就是让配置文件立马生效。

2.5 认识命令行参数argc和argv

上面我们在2.3获取环境变量中说到了main函数的三个参数,其中我们只是解决了一个envp这个参数是什么的问题,下面我们再来谈一谈argc和argv是什么。

同样的argv也是一个字符串指针数组,每个元素对应的一个字符串,同样字符串也是以’\0’为结束标志的,也是以NULL为标识结束数组的。这里的argc指的是argv指针数组中的元素个数。

那么我们打印来看看argv中到底有什么?

[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  test.c
[jyh@VM-12-12-centos study5]$ cat test.c
#include <stdio.h>
#include <stdlib.h>


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

[jyh@VM-12-12-centos study5]$ make
gcc -o mytest test.c -std=c99
[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  mytest  test.c
[jyh@VM-12-12-centos study5]$ ./mytest 
argv[0]->./mytest
[jyh@VM-12-12-centos study5]$ 

通过上述的现象,我们观察到的是argv这个字符串数组中就只有./mytest这个可执行程序。但是我们可以再其可执行程序后加上参数,对应的效果就是这样的:

[jyh@VM-12-12-centos study5]$ ./mytest -a -b -c
argv[0]->./mytest
argv[1]->-a
argv[2]->-b
argv[3]->-c
[jyh@VM-12-12-centos study5]$ 

那么这里的参数列表也就随后保存在了argv这个字符串指针数组中了,这样我们就可以理解为什么ls指令后可以跟上参数列表了,比如:ls -a -l,这里的ls就是一个可执行程序,后面就是参数列表。

那么这里的参数列表怎么来用呢?

[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  test.c
[jyh@VM-12-12-centos study5]$ mae
-bash: mae: command not found
[jyh@VM-12-12-centos study5]$ make
gcc -o mytest test.c -std=c99
[jyh@VM-12-12-centos study5]$ ls
env.c  Makefile  myproc.c  mytest  test.c
[jyh@VM-12-12-centos study5]$ cat test.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

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

int main(int argc, char* argv[])
{
  if(argc != 2) 
  {
    argument(argv[0]);
  }
  if(strcmp(argv[1], "-a") == 0)
  {
    printf("print current dirrectory!\n");
  }
  else if(strcmp(argv[1], "-b") == 0)
  {
    printf("print current dirname\n");
  }
  else if(strcmp(argv[1], "-c") == 0)
  {
    printf("print pwd\n");
  }
  return 0;
}

[jyh@VM-12-12-centos study5]$ ./mytest 
argument:./mytest [-a|-b|-c]
[jyh@VM-12-12-centos study5]$ ./mytest -a
print current dirrectory!
[jyh@VM-12-12-centos study5]$ ./mytest -b
print current dirname
[jyh@VM-12-12-centos study5]$ ./mytest -c
print pwd
[jyh@VM-12-12-centos study5]$ 

我们发现,每个参数列表对应的就是不同的操作,还可以对参数列表个数进程设置等等操作。通过这样类比一下:ls -a -l等等指令!

3. 进程优先级

3.1 概念

进程优先级就是指的是进程的优先权,也就是指CPU资源分配的先后顺序!优先权高的进程先执行,优先级低的后执行!这样的目的是为了改善系统性能!

这里权限和优先级怎么来理解呢?权限是能不能的问题,优先级是能但是谁先谁后的问题。

3.2 查看系统进程

指令:ps -l / ps -al

[jyh@VM-12-12-centos study5]$ ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1002  4197  4196  0  80   0 - 29280 do_wai pts/0    00:00:00 bash
0 R  1002  5831  4197  0  80   0 - 38332 -      pts/0    00:00:00 ps
[jyh@VM-12-12-centos study5]$ 

这里的UID代表的是执行者身份;PID是此进程的代号;PPID是此进程对应的父进程的代号;PRI知道是优先级别,值越小优先级越高;NI是进程的nice值,也就是优先级对应的修正值,其NI的范围是[-20, 19]。其中有一个PRI的求法:PIR(new) = PRI(old) + nice。另外我们的进程会通过CPU来做计算,CPU中有调度器,这个调度器的作用就是来对进程的优先级一碗水端平,不会过度的使得其进程优先级很高,也不会过度的使得其进程优先级很低,所以这里的NI值也不会很高,这样就可以达到一碗水端平的目的。

那么这里如何修改优先级呢?使用top命令–>按r–>输入进成PID–>输入nice值,进程重新开始后其PRI和NI都恢复原值。另外这里的PRI求法中式子里面PRI(old)始终都是80。

4. 其他概念

竞争性:系统进程数目很多,但是CPU资源有限,所以进程之前具有竞争。

独立性:多进程运行需要独享资源,多线程期间互补干扰

并行:多个进程在多个CPU下分别同时进程运行

并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程都得以推进

5. 程序地址空间

5.1 C语言程序地址空间分布图

我们这时地址空间分布图,地址不是和内存有关系吗,这里的问题是这个程序地址空间分布图是内存吗?其实并不是内存。

那么这里就有很多问题:不是内存它是什么呢?为什么要有它呢?内存在哪呢?

5.2 切入

通过现象来切入话题:

[jyh@VM-12-12-centos study6]$ ls
address.c  Makefile
[jyh@VM-12-12-centos study6]$ make
gcc -o myaddress address.c
[jyh@VM-12-12-centos study6]$ ls
address.c  Makefile  myaddress
[jyh@VM-12-12-centos study6]$ ./myaddress 
我是父进程, id:20977, ppid:15064, global:100, &global:0x60105c
我是子进程, id:20978, ppid:20977, global:100, &global:0x60105c
我是父进程, id:20977, ppid:15064, global:100, &global:0x60105c
我是子进程, id:20978, ppid:20977, global:101, &global:0x60105c
我是子进程, id:20978, ppid:20977, global:102, &global:0x60105c
我是父进程, id:20977, ppid:15064, global:100, &global:0x60105c
我是父进程, id:20977, ppid:15064, global:100, &global:0x60105c
我是子进程, id:20978, ppid:20977, global:103, &global:0x60105c
我是父进程, id:20977, ppid:15064, global:100, &global:0x60105c
我是子进程, id:20978, ppid:20977, global:104, &global:0x60105c
我是子进程, id:20978, ppid:20977, global:105, &global:0x60105c
我是父进程, id:20977, ppid:15064, global:100, &global:0x60105c
^C
[jyh@VM-12-12-centos study6]$ cat address.c 
#include <stdio.h>
#include <unistd.h>
#include <assert.h>

int global = 100;

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

  }

  return 0;
}

[jyh@VM-12-12-centos study6]$ 

上述现象:我们发现定义的一个全局变量,在子进程对其++操作,但是父进程没有对其进程操作,我们发现的一个奇怪现象就是:这里子进程的global是变化的,但是父进程的global是不变的,这就有意思了,我们不是说全局变量做更改,这个程序都是输出更改后的值吗?这里我们就会想到进程的独立性,这里子进程对全局数据修改了,但是并没有影响到父进程,这里我们再提到:进程=内核数据结构+代码和数据,既然进程具有独立性,那么这里的数据也具有独立性,那么这里怎么使得一个子进程修改了数据的但是不影响父进程的数据的呢,这里就用到了写时拷贝。这里还有一个很重要的现象那就是我们的global变量的地址并没有发生改变,这里居然有写时拷贝又是怎么回事呢,如果有写时拷贝那不就是有两个变量吗,一个是global,另外一个是做逐字节拷贝了global数据的变量,但是你发现这里的地址是完全一样的。假设这里是物理地址,那么可不可能同一块空间上有两份不同的数据呢?答案是不可能的,因为物理地址上是绝对的,也就是一个空间只能存储一个数据。上面我们提到了C语言程序空间分布它不是地址,这里也说明了这个问题,它绝对不是物理地址,它是虚拟地址或者是线性地址

5.3 正式认识进程地址空间

故事:在学校中,我们通常在初三和高三作业繁忙,初三的时候我们是受家长管制的,但是高中大部分都是住读生,这里就不被管制,初中的时候家里管的严的,一般都是学校作业+回家后妈妈或者爸爸布置的另外的作业,这里一天一天的过去,也面临着此学期的期末考试了,于是妈妈说:你如果考进年级前十,下学期就不给你另外布置作业。此时的你很高兴,心里也想这次肯定考进年级前十,然后过了几天考试,考试后你的成绩下来了,你的妈妈问了问成绩,知道了你考进了年级前十,此时你也很高兴,但是马上一个暑假过去了,又开学了,你很开心,然后去报名上学了,但是到了周末后,你妈妈又给你布置了作业,你又不得不按着照做。这个故事里面妈妈给自己的孩子画的饼就是进程地址空间,而妈妈就是操作系统,这里的妈妈布置的另外的作业就是内存,而孩子就是进程。这里就有个问题:妈妈给孩子画了一张饼,此时妈妈用不用管理这张饼呢?是要管理的。所以对应的操作系统要管理进程地址空间,那么又有问题来了,我们说操作系统做管理是先描述再组织,这里操作系统如何对进程地址空间先描述再组织呢?实际上进程地址空间就是操作系统中的一个内核数据结构,这个数据结构是mm_struct。那么问题又来了:地址空间上的区域的区域划分如何去理解?故事:学校里,有个小美和小黑两个对象,小黑是个很不爱卫生的男生,而小美是个很爱干净的女生,开学第一天就他两就被老师安排在一起做,这时小美很嫌弃小黑,就对小黑画清了界限,说我们两个桌子的三分之二都是我的,你只能在三分之一内活动,这时就对区域进行了划分。在程序中模拟区域划分:struct area{int start; int end;};,那么小美的区域就是struct area xiaomei{0, 4/3 * lenght}; 小黑的区域就是struct area xiaohei{4/3 * lenght, lenght}; 所以区域划分的本质就是对线性区域进行指定start和end来完成区域划分。 如何理解这个地址空间是线性连续的呢?我们在使用visual studio 2019或者2022时,我们调试后打开内存会发现我们的地址空间中的地址是用16进制表示的,32位计算机下范围是[0x00000000, 0xFFFFFFFF],有4G的空间大小,这里的地址说明一下:地址是多少不重要,关键是地址具有唯一性,不能发生冲突,每个地址值对应的就是一个字节。所以因为地址数字是连续的,所以这里说地址空间是线性连续的。那么操作系统知道地址空间是多大吗?是知道的,这个进程地址空间是一个内核的数据结构,进程的结构体PCB中有mm_struct,操作系统有一套64位和一套32位计算机的对应的代码。那么了解上述的区域划分问题后,我们很容易知道地址空间中的各个区怎么来的了:struct mm_struct{long code_start; long code_end; long init_start; long init_end; … };这样就可以对地址空间做划分。这里限定了区域,但是区域内的数据是什么呢?也就是虚拟地址和线性地址。这里调整区域大小也就是对应的调整对象的start和end的指向。了解了地址空间是什么,我们再谈谈进程和进程地址空间以及物理内存的关系,下面我们要了解一下进程地址空间是我们直接用到是虚拟地址,我们找到地址并不是目的,而是手段,我们真正的目的是通过地址找到空间中对应的内容,而这里的内容一定是存在物理内存中的,但是这里地址空间必须和物理内存有联系才能把内容放进对应的地址的物理内存空间中,所以Linux下存在一种技术:页表,它的作用就是把虚拟地址转化为物理地址。除了转换外,还有一个硬件:MMU(memory management unit),集成在CPU中的。对应的关系是:

我们了解到了关系后,再回过头来看5.2切入中的那段代码,对其做出解释:父进程fork一个子进程,这个子进程就对父进程的task_struct进行了复制,但是子进程中对global进程了++操作,此时我们对应的task_struct和父进程是一摸一样的,mm_struct也是一摸一样的,但是在物理内存上,不允许子进程global++操作后影响父进程,所以此时在物理内存上又开辟了一块空间,拷贝global原有的值到新空间,子进程中的页表对应的物理内存存放的就是这个新空间的地址,简单来说就是父子进程的mm_struct并不改变,改变的是物理内存空间,也就是父子进程互不影响,进程具有独立性的表现之一。那么上述切入中的代码我们所观察到的是父子进程可以同时运行,但是id是一个变量,那么为什么会有大于0和等于0的两种情况同时出现,也就是因为:fork在返回的时候,父子进程都有了,return了两次,返回的本质就是写入,那么谁先返回,谁都会让操作系统发生写时拷贝。

5.4 扩展

  1. 虚拟地址空间为什么要存在?

如果没有地址空间,操作系统是如何工作的?没有地址空间的话,当我们多个进程加载到内存时,其中有个进程时访问地址的操作,然后给到CPU,CPU返回给这个进程一个不是本进程而是其他进程的地址,这样假如我们本进程要进行写入或者删除的操作,这样就会影响到了其他进程,这样就无法保证进程的独立性。所以就引入了页表和虚拟地址空间这样的东西,这样就会保证进程的独立性和安全。小结:地址空间的存在的意义:1. 防止地址随意访问,保护物理内存和其他进程 2. 将进程管理和内存管理进行解耦合 3. 让进程以统一的视角看待代码和数据

  1. malloc的本质是什么?

我们向操作系统申请空间,操作系统是你在需要的时候才会给你,而不是立马给你。为什么呢?因为操作系统是不允许任何的浪费或者不高效的行为,也就是说在你申请成功之后和在你使用之前,这块空间有一段的闲置状态。操作系统怎么样不让空间浪费呢?这里的首先是在虚拟地址空间申请空间,然后对应的虚拟地址放进页表,但是没有映射处物理地址,物理内存上也没有申请空间,这就是缺页中断。

  1. 重新理解虚拟地址空间

首先抛出一个问题:程序在被编译的时候,没有被加载到内存中,那么程序内部有没有地址呢?是有的。那么这里就有点奇怪,源代码被编译的时候,就是按照虚拟地址空间的方式进行对代码和数据就已经编好了对应的编制。不要认为虚拟地址这样的策略只会影响OS,它也会让编译器遵守对用的规则。CPU中读取数据对应的地址,这个地址是虚拟地址还是物理地址?还是虚拟地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

脚踏车(crush)

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

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

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

打赏作者

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

抵扣说明:

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

余额充值