文章目录
进程基本概念
a.程序的一个执行实例,正在执行的程序。
b.(内核的角度)担当分配系统资源(CPU时间,内存)的实体。
进程组成
PCB,虚拟地址空间,页表,映射关系。
管理方式:先描述再组织。
先用结构体将进程描述,再使用合适的数据结构将这些结构体描述块组织起来。
描述进程
:
a.进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
b.Linux操作系统下的PCB是task_struct.(它定义在linux-2.6.38.8/include/linux/sched.h文件中) ,task_struct是linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
- task_struct内容分类
a.标识符:描述本进程的唯一标识符,用来区别其他进程
b. 状态:任务状态,退出代码,退出信息
c. 优先级:相对于其他进程的优先级
d. 程序计数器:程序中即将被执行的下一条指令的地址。
e. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
f. 上下文数据:进程执行时处理器的寄存器中的数据
g. I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
h. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
i. 其他信息。
组织进程
可以在内核源代码里找到它,所有运行在系统里面的进程都以task_struct链表的形式存在内核中。
查看进程
进程的信息可以通过/proc系统文件夹查看
a.如:要获取PID为1的进程信息,你需要查看/proc/1这个文件夹。
b.大多数进程信息也可以使用top/ps用户级工具获取
<1>使用ps aux | grep 文件1 | grep -v grep获取文件1的进程信息
ps指针:寄存器当前正在执行指令的下一条指令的地址
时间片一个进程占有CPU资源的基本单位
<2> top:动态查看进程的变化
参数:
-d:后面直接加秒数,表示显示整个进程界面更新到的秒数,如top -d 3每三秒更新一次
-b:以批次的方式执行top
-n:与-b搭配,表示需要几次top输出的结果
-p:指定某个特定的PID进行检测
通过系统调用获取进程标识符号
a.进程id(PID)
b.父进程id(PPID)
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
printf("pid: %d\n",getpid());
printf("ppid: %d\n",getppid());
return 0;
}
通过系统调用创建fork函数
a.fork有两个返回值,一个接受值(一父多子,一子一父)
子进程返回0
父进程返回子进程的PID
失败返回-1
b.父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
c.fork之后通常用if进行分流
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int ret =fork();
if(ret<0){
perror("fork");
return 1;
}
else if(ret == 0){//child
printf("I am child : %d,ret : %d\n",getpid(),ret);
}else{//father
printf("I am parent : %d,ret : %d\n",getpid(),ret);
}
sleep(1);
return 0;
}
父子进程运行具有独立性,由调度算法决定
1.时间片轮转调度算法(RR) : 给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。
先来先服务调度算法(FCFS) :根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。
优先级调度算法 (HPF):在进程等待队列中选择优先级最高的来执行。
多级反馈队列调度算法 :将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。优点是兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。
高响应比优先调度算法 :根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。
进程的状态
1.R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列中。
2.S睡眠状态(sleeping);意味着进程在等待事件完成(这里的睡眠有时候也叫作中断睡眠)(interruptible sleep)(浅度睡眠,可以被kill -9 杀掉)
3.D磁盘休眠状态:不可中断睡眠状态,在这个状态的进程通常等待IO的结束(硬盘访问中可能遇到),不可以被杀掉
4.T停止状态:可以通过发送SIGSTOP信号给进程来停止(T)进程,这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
5.X死亡状态:这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- 进程状态修改
1.kill -9 进程 :杀死进程
2.kill -SIGSTOP 进程:使进程变为T状态
3.kill -SIGCONT 进程:使进程继续运行
4.kill -l 查看系统支持的信号列表
僵尸进程
-
概念:
a.僵尸状态是一个比较特殊的状态,当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回码时就会产生僵尸进程。
b.僵尸进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
c.所以只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。 -
僵尸进程的危害:
(1)退出状态本身就是要用数据去维护的,也属于进程信息,所以保存在task_struct(PCB)中,那么z状态一直不退出,PCB就一直要维护
(2)当一个父进程创建了很多子进程,但却一直没有回收,这样就会造成内存资源的浪费。(因为数据结构本身就要占用内存)
(3)如果父进程不去回收子进程,还会造成内存泄漏 -
怎样避免僵尸进程
1)父进程通过wait和waitpid等函数等待子进程的结束,这样会导致父进程挂起
(2)倘若父进程非常忙,那么可以用singal函数为SIGCHLD安装handler(因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收)
(3) 如果父进程不关心子进 程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
(4)fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还是需要自己做。 -
代码实现
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
return 1;
}else if(pid > 0){
printf("parent : %d\n", getpid());
sleep(30);
}else{
printf("child : %d\n",getpid());
sleep(5);
}
}
打开两个终端,一个终端编译,另一个终端监控。
孤儿进程
- 概念:孤儿进程就是在其父进程执行完成或被终止后仍然继续运行的一类进程。这些孤儿进程将会被init进程(进程号为1号)所收养,并由init进程对它们完成状态收集工作。因为有init进程会循环的wait()已经退出的子进程,所以孤儿进程并不会产生什么危害。
- 实现
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id<0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n",getpid());
sleep(10);
}else{//parent
printf("I am parent, pid : %d\n",getpid());
sleep(3);
exit(0);
}
return 0;
}
打开两个终端,一个终端编译,另一个终端监控。
进程优先级
1.基本概念:
a.cpu资源分配的先后顺序,就是指进程的优先级
b.优先权高的进程优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
c.还可以吧=把进程运行到指定的cpu上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
2.使用命令ps -l会输出下面内容
a.UID :代表执行者的身份
b.PID:代表这个进程的代号
c.PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
d.PRI:代表这个进程可被执行的优先级,其值越小越早被执行
e. NI:代表这个进程的nice值
3.PRI与NI
a.PRI值越小进程的优先级别越高
b.NI也就是要说的nice值,表示进程可被执行的修正数值
c.PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。
d.这样,当nice值为负值时,那么该程序优先级值会变小,即优先级会变高,则其越快被执行。
e.所以,调整进程优先级,在Linux下就是调整nice值
f.nice值的取值范围为-20至19,一共40个级别。
- 修改进程优先级的命令
a.启动进程前调整nice:nice -n -调整值 ./test;调整test进程的nice为调整值
b.调整已经存在进程的nice :renice -调整值 -p 进程1PID;将进程1的nice改为调整值
c.top命令:top,进入top后按“r”,输入进程PID,输入nice值。
杂碎概念
1.竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的,为了高效完成任务,更合理竞争相关资源,便具有优先级
2.独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
3.并行:多个进程在多个CPU下分别,同时进行运行
4.并发:多个进程在一个CPU下采用进程切换的方式,在这一段时间之内,让多个进程都得以推进。。
进程与程序(完成特定任务的一系列指令集合)区别
程序: 数据+代码
进程: 数据+代码+堆栈+PCB
1.进程是动态的,程序是静态的;
2进程的生命周期是短暂的,而程序相对永久;
3.进程有重要的数据结构PCB;
4.一个进程只能对应一个程序
5.而一个程序可以对应多个进程。
环境变量
1.基本概念:
a.环境变量一般是指在OS中用来指定OS运行环境的一些参数
b.当我们编写代码的时候,在执行链接的时候,我们不知道所连接的动态静态库在哪里,但是照样可以链接成功生成可执行程序,原因是相关环境变量帮助编译器进行查找。
c.环境变量在系统中通常具有全局特性
2.常见环境变量
- PATH:指定命令的搜索路径
- HOME:指定用户的主工作目录(即用户登录到Linux系统中,默认的目录)
- HISTSIZE:指保存历史命令记录的条数
- SHELL:当前Shell,它的值通常是/bin/bash。
3.查看环境变量的方法
echo $环境变量名称
4.PATH的使用
我们使用makefile执行文件时,是./执行文件名,使用命令export PATH=¥PATH:执行文件的路径
,就将执行文件的路径放到环境变量PATH中,然后直接输入执行文件名,变可以执行。
5.环境变量的相关路径
- echo:显示某个环境变量值
- export:设置一个新的环境变量
- env:显示所有环境变量
- unset:清除环境变量
- set:显示本地定义的shell变量和环境变量
6.环境变量的组织方式
环境是一个字符指针数组,每个指针是指向环境字符串,最后一个元素是NULL
7.获取环境变量的方法
- 命令行第三个参数
#include<stdio.h>
int main(int argc,char *argv[ ],char *env[ ])
{
int i=0;
for(;env[i];++){
printf("%s\n",env[i]);
}
return 0;
}
- 通过第三方变量environ获取
environ指向环境变量表,没在任何头文件中,使用时,要用extern生命。
#include<stdio.h>
int main(int argc,char *argv[ ])
{
extern char **envision;
int i=0;
for(;environ[i];++){
printf("%s\n",environ[i]);
}
return 0;
}
- 通过getenv获取
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("%s\n",getenv("PATH");
return 0;
}
8.环境变量通常具有全局特性,可以被子进程集成下去。
#include<stdio.h>
#include<stdli.h>
int main()
{
char *env=getenv("MYENY");
if(env){
printf("%s\n",env);
}
return 0;
}
进程地址空间
1.c语言时我们学习的空间分布图,用户使用的不是真正的物理内存,是虚拟的,不难理解,一个程序不可能使用完物理内存。
2.通过代码理解
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int g_val=0;
int main()
{
pid_t id=fork();
if(id<0)
{
perror("use fork");
exit(1);
} else if(id==0){//child
g_val=100;
printf("I am child:[%d],%d,%p\n",getpid(),g_val,&g_val); } else{//parent
printf("I am parent:[%d],%d,%p\n",getpid(),g_val,&g_val)
}
sleep(1);
return 0;
}
输出结果为:
i am parent:[5884],0,0x80497d8
i am child:[5885],100,0x80497d8
我们发现父子进程的地址相同所以这个地址不是物理地址,在Linux下我们称为虚拟地址,真正的物理地址由OS统一管理。用户一概看不到,OS必须负责将虚拟地址转换为物理地址。
3.同一个变量,地址相同,其实就是虚拟地址相同,内容不同就是被页表映射到不同的物理地址。
进程终止
-
进程退出场景
1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.代码异常终止 -
进程常见退出方法
正常退出
1.man函数返回
2.调用exit,
3._exit
异常退出
1.ctrl+c ,信号终止 -
_exit函数
#include<unistd.h>
void _exit(int status);
参数status定义了进程的终止状态,父进程通过wait获取该值(0正常返回,1异常返回),虽然status是int,但是仅有8低8位可以被父进程所用,所以_exit(-1)时,在终端执行$?发现返回值为255.
- exit函数
#include<unistd.h>
void _exit(int status);
- exit与_exit函数的区别:exit刷新缓存区,_exit不刷新。
- return退出
return退出是一种常见的进程退出方法,return m等同于exit(m),因为调用main的运行时函数会把main的返回值当做exit的参数。
进程创建
1.本博客开始已经简单介绍fork函数,现在再加深了解,fork函数从已经存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程。
进程调用fork,当控制转移到内核中的fork代码后内核:
-
分配新的内存块和内核数据结构给子进程
-
将父进程部分数据结构内容拷贝到子进程
-
添加子进程到系统进程列表中
-
fork返回,开始调度器调度
2.fork的写时拷贝
通常父子代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝方式各自一份副本。
3.写时拷贝考虑因素: -
内存资源
-
性能,更合理的使用空间
4.fork调用失败原因 -
系统中有太多的进程
-
实际用户的进程数量超过了限制
5.vfork函数
-
vfork也是用于创建子进程。而vfork之后的父子进程共享地址空间,也就是说他们除了PCB不一样,在都一样,fork的子进程则具有独立的地址空间。
-
vfork的子进程保证让子进程先运行,在调用exec后父进程才可能被调度。
-
使用
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int num=30;
int main()
{
pid_t id=vfork();
if(id<0){
perror("use fork");
exit(1);
}
else if(id==0){//child
sleep(4);
num=80;
printf("child:num=%d\n",num);
exit(0);
}
else{//parent
printf("parent:num=%d\n",num);
}
return 0;
}
运行结果是:先等待4秒,然后先执行子进程,而子进程对数据修改为80,,父进程也是80,子进程直接改变了父进程的变量值,因为子进程在父进程的地址空间运行。
进程等待
- 进程等待的必要性
1.子进程退出,父进程如果不管不顾,就可能造成“僵尸状态”,进而造成内存泄漏
2.进程一旦变成僵尸状态,就刀匠不如,kill -9也无能为力
3.父进程派给子进程的任务完成的如何,我们需要知道,如:子进程运行完成,结果对还是不对,或者是否正常退出
4.父进程通过进程等待的方式,回收子进程,获取子进程退出信息。 - 进程等待的方法
1.wait
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int status);
返回值:
成功返回被等待进程pid,失败返回-1.
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
进程调用wait后会阻塞自己,分析是否存在僵尸子进程,找到就销毁并返回,没找到就一直阻塞。wait只要有一个退出,父进程知道子进程id
2.waitpid
#include<sys/types.h>
#include<sys/wait.>
pid_t waitpid(pid_t pid,int *status,int options);d
3.返回值
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已经退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时error会被设置成相应的值以指示错误所在。
4.参数
-
pid:
pid>0时,等待其进程ID与pid相等的子进程。
pid=-1,等待任一个子进程,与wait等效。a -
status
1.WIFEXITED(status) 判断子进程是否为正常退出的,如果是,它会返回一个非零值
2.WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏 来提取子进程的退出码,如果子进程调用exit(25)退出,WEXITSTATUS(status)就会返回25。如果进程不是正常退出的,WIFEXITED返回0,这个值就无意义。 -
options
WNOHANG(非阻塞 1):若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束,则返回该子进程的ID。
WUNTRACED(阻塞 0)
这是两个常数,可以使用“|”把他们连起来使用,eg:waitpid(-1,NULL,WNOHANG|WUNTRACED);
如果不使用设为0就好,(-1,NULL,0);
阻塞与非阻塞理解:假如梦先生在等烧水,阻塞式表示他一直在等,子进程没有退出,父进程就卡住;非阻塞式表示,他可以在等的过程中干别的比如玩玩手机,聊聊天什么,子进程没退出,父进程可退出,不会卡住。
5.获取子进程的status
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充
- 如果传递NULL,表示不关心子进程的退出状态信息
- 否则,操作系统会根据该参数,将子进程的子进程信息反馈给父进程
- status不能简单的当做整型来看待,可以当做位图来看待,具体细节如下:
正常终止低8位(8-15)是退出码,其余的是0.
被信号杀死,status的低7位退出码。
6.wait使用
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t pid=fork();
if(pid==-1){
perror("use fork");
exit(1);
}
else if(pid==0){//child
sleep(5);
exit(10);
}
else{//parent
int st;//status
int ret=wait(&st);
if(ret>0&&(st&0X7F)==0)
printf("child exit code is [%d]\n",(st>>8)&0XFF);
else
printf("sig code is[%d]\n",st&0X7F);
}
}
开启两个终端,一个执行后,打开另一个终端,杀死子进程,再执行一次。
7.waitpid的使用
- 阻塞式
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
pid_t id=fork();
if(id<0){
perror("use fork");
exit(1);
}
else if(id==0){//child
printf("child is run,pid is [%d]\n",getpid());
sleep(5);
exit(37);
}
else{//parent
int st;//status
pid_t ret=waitpid(-1,&st,0);//0代表阻塞式
printf("this is test for waitpid\n");
if(WIFEXITED(st)&&ret==id)//WIFEXITED判断是否正常返回
printf("wait child 5s success,child return code is [%d]\n",WEXITSTATUS(st));
//WEXITSTATUS宏获取退出码
else{
printf("wait child failed,return.\n");
return 1;
}
}
return 0;
}
- 非阻塞式
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main(){
pid_t id=fork();
if(id<0){
perror("use fork");
exit(1);
}
else if(id==0){//child
printf("child is run,pid is [%d]\n",getpid());
sleep(5);
exit(1);
}
else{//parent
int st;//status
pid_t ret=0;
do{
ret=waitpid(-1,&st,WNOHANG);//WNOHANG是1,代表非阻塞式等待
if(ret==0){
printf("child is running\n");
}
sleep(1);
}while(ret==0);
if(WIFEXITED(st)&&ret==id)
printf("wait child 5s success,child return code is [%d]\n",WEXITSTATUS(st));
else
printf("wait child failed,return.\n");
return 1;
}
return 0;
}
进程的程序替换
- 用fork创建子进程后执行的是和父进程相同的程序,当然可以执行不同的分支(如果我们用fork创建一个子进程之后让子进程做和父进程同样的事,那么这个子进程没有任何意义)。
所以在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[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
- 函数解释
这些函数如果调用成功则加载新的程序从启动端开始执行,不再返回。
如果出错,返回-1. - 命名理解
1.l:表示参数采用列表例如:execl("/bin/ls",“ls”,"-a",“NULL”)
2.v:参数用数组
例如:
char* myargv[]={“ls”,"-a,“NULL”};
execv(“bin/ls”,myargv);
3.p:有p自动搜索环境变量PATH
4.e:表示自己维护环境变量
- 使用
char * const argv[ ] ={ "ps","-ef",NULL);
char * const envp[ ]={"PATH=/bin:/usr/bin","TERM=console",NULL);
execl("/bin/ps","ps","-ef",NULL);
//带P的,可以使用环境变量PATH,无需写全路径
execlp("ps","ps","-ef",NULL");
//带e的自己组装环境变量
execle("ps","ps","-ef",NULL,envp);
execv("/bin/ps",argv);
//带P的,可以使用环境变量PATH,无需写全路径
execvp("ps",argv);
//带e的自己组装环境变量
execvp("/bin/ps",argv,envp);
注:这6个函数只有execve是真正的系统调用,其他5个函数都是在execve上包装的,它们6个函数有以下的关系。
setenv函数
作用:改变或增加环境变量
相关函数getenv,putenv,unsetenv,首先要说明的是,通过此函数并不能添加或修改shell进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止再次运行该程序,上次的设置是无效的,上次设置的环境变量是不能读到的。
参数value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。注释stdlib.h在Linux和windows中略不同,比如setenv函数是用在linux中的,在Windows中没有setenv函数而用putenv来代替。
定义函数:int setenv(const char *name,const char *value,int overwrite);
函数说明setenv函数用来改变环境变量或增加环境变量的内容,参数name为环境变量名称字符串,参数value则为变量的内容,参数overwrite用来决定是否要改变已存在的环境变量。如果没有此环境变量,则无论overwrite为何值均添加此环境变量。若此环境变量存在,overwrite不为0时,原内容会被改为参数value所指的变量内容,当overwrite为0时,则参数会被忽略。返回值执行成功则返回0,有错误发生时,返会-1
说明:通过此函数并不能添加或修改 shell 进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止后再次运行该程序,上次的设置是无效的,上次设置的环境变量是不能读到的。
语法:setenv [变量名称] [变量值]
export
作用:设置或显示环境变量
说明:在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该次登陆操作。
语法:export [-fnp] [变量名称] = [变量设置值]
参数说明:
-f 代表[变量名称]中为函数名称。
-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p 列出所有的shell赋予程序的环境变量。
详细请看【转载】: