环境变量,fork,守护者进程

来源:互联网 发布:网站关键词优化教程 编辑:程序博客网 时间:2024/04/29 06:58

1.   环境变量

Linux是一个多用户的操作系统。每个用户登录系统后,都会有一个专用的运行环境。通常每个用户默认的环境都是相同的,这个默认环境实际上就是一组环境变量的定义。用户可以对自己的运行环境进行定制,其方法就是修改相应的系统环境变量。

 

使用env命令显示所有的环境变量

$ env

 

使用命令echo显示环境变量

$ echo $HOME

 

使用export设置环境变量

$ export HELLO=”Hello!”

$ echo $HELLO

Hello!

 

使用set命令显示所有本地定义的Shell变量

$ set

 

使用unset命令来清除环境变量

$ unset $TEST #删除环境变量TEST

 

使用readonly命令设置只读变量

$ export TEST=Test…” #增加一个环境变量TEST

$ readonly TEST #将环境变量TEST设为只读

 

C程序来访问和设置环境变量

getenv()访问一个环境变量。

setenv()在程序里面设置某个环境变量的函数。

unsetenv()清除某个特定的环境变量的函数。

指针变量environ,它指向的是包含所有的环境变量的一个列表

#include <stdio.h>

extern char**environ;

int main ()

{

char**var;

for (var =environ;*var !=NULL;++var)

printf (”%s /n “,*var);

return 0;

}

 

通过修改环境变量定义文件来修改环境变量。

需要注意的是,一般情况下,这仅仅对于普通用户适用,避免修改根用户的环境定义文件,因为那样可能会造成潜在的危险。

$cd #到用户根目录下

$ls -a #查看所有文件,包含隐藏的文件

$vi .bash_profile #修改环境变量定义文件

然后编辑你的PATH声明,其格式为:

PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:——:<PATH N>

你可以自己加上指定的路径,中间用冒号隔开。环境变量更改后,在用户下次登陆时生效,如果想立刻生效,则可执行下面的语句:$source .bash_profile

需要注意的是,最好不要把当前路径”./”放到PATH里,这样可能会受到意想不到的攻击。完成后,可以通过$ echo $PATH查看当前的搜索路径。这样定制后,就可以避免频繁的启动位于shell搜索的路径之外的程序了。

 

2.   linux fork函数的精辟解说

#include <unistd.h>;

#include <sys/types.h>;

main ()

{

    pid_t pid;

    pid=fork();

if (pid < 0)

printf("error in fork!");

else if (pid == 0)

printf("i am the child process, my process id is %dn",getpid());

else

printf("i am the parent process, my process id is %dn",getpid());

}

 

结果是

[root@localhost c]# ./a.out

i am the child process, my process id is 4286

i am the parent process, my process id is 4285

 

我就想不到为什么两行都打印出来了,在我想来,不管pid是多少,都应该只有一行才对

 

chg.s 回复于:2004-04-27 21:09:30

 

 

要搞清楚fork的执行过程,就必须先讲清楚操作系统中的“进程(process)”概念。一个进程,主要包含三个元素:

 

o. 一个可以执行的程序;

o. 和该进程相关联的全部数据(包括变量,内存空间,缓冲区等等);

o. 程序的执行上下文(execution context)。

 

不妨简单理解为,一个进程表示的,就是一个可执行程序的一次执行过程中的一个状态。操作系统对进程的管理,典型的情况,是通过进程表完成的。进程表中的每 一个表项,记录的是当前操作系统中一个进程的情况。对于单 CPU的情况而言,每一特定时刻只有一个进程占用 CPU,但是系统中可能同时存在多个活动的(等待执行或继续执行的)进程。

 

一个称为“程序计数器(program counter, pc)”的寄存器,指出当前占用 CPU的进程要执行的下一条指令的位置。

 

当分给某个进程的 CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;把将要接替这个进程占用 CPU的那个进程的上下文,从进程表中读出,并更新相应的寄存器(这个过程称为“上下文交换(process context switch)”,实际的上下文交换需要涉及到更多的数据,那和fork无关,不再多说,主要要记住程序寄存器pc指出程序当前已经执行到哪里,是进程上 下文的重要内容,换出 CPU的进程要保存这个寄存器的值,换入CPU的进程,也要根据进程表中保存的本进程执行上下文信息,更新这个寄存器)。

 

好了,有这些概念打底,可以说fork了。当你的程序执行到下面的语句:

pid=fork();

操作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是 原进程(父进程)的拷贝,但它们是两个相互独立的进程!此时程序寄存器pc,在父、子进程的上下文中都声称,这个进程目前执行到fork调用即将返回(此 时子进程不占有CPU,子进程的pc不是真正保存在寄存器中,而是作为进程上下文保存在进程表中的对应表项内)。问题是怎么返回,在父子进程中就分道扬 镳。

 

父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。所以输出i am the parent process...

 

子进程在之后的某个时候得到调度,它的上下文被换入,占据 CPU,操作系统对fork的实现,使得子进程中fork调用返回0。所以在这个进程(注意这不是父进程了哦,虽然是同一个程序,但是这是同一个程序的另 外一次执行,在操作系统中这次执行是由另外一个进程表示的,从执行的角度说和父进程相互独立)中pid=0。这个进程继续执行的过程中,if语句中 pid<0不满足,但是pid==0true。所以输出i am the child process...

 

我想你比较困惑的就是,为什么看上去程序中互斥的两个分支都被执行了。在一个程序的一次执行中,这当然是不可能的;但是你看到的两行输出是来自两个进程,这两个进程来自同一个程序的两次执行。

3. Linux 守护进程的编程方法

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetdWeb服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。

 

守护进程的编程本身并不复杂,复杂的是各种版本的Unix的实现机制不尽相同,造成不同Unix环境下守护进程的编程规则并不一致。这需要读者注意,照搬某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。下面将全面介绍Linux下守护进程的编程要点并给出详细实例。

 

一. 守护进程及其特性

 

守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。最后,守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。

 

总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果读者对进程有比较深入的认识就更容易理解和编程了。

 

二. 守护进程的编程要点

 

前面讲过,不同Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都一样,区别在于具体的实现细节不同。这个原则就是要满足守护进程的特性。同时,Linux是基于Syetem VSVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点如下;

 

1. 在后台运行。

 

为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。

 

if(pid=fork())

exit(0);//是父进程,结束父进程,子进程继续

 

2. 脱离控制终端,登录会话和进程组

 

有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。

 

控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:

setsid();

 

说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。

 

3. 禁止进程重新打开控制终端

 

现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:

 

if(pid=fork())

exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)

 

4. 关闭打开的文件描述符

 

进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:

for(i=0;i 关闭打开的文件描述符close(i);>

 

5. 改变当前工作目录

 

进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir("/")

 

6. 重设文件创建掩模

 

进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0);

 

7. 处理SIGCHLD信号

 

处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN

 

signal(SIGCHLD,SIG_IGN);

这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。

 

三. 守护进程实例

 

守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。初始化程序中的init_daemon函数负责生成守护进程。读者可以利用init_daemon函数生成自己的守护进程。

 

1 init.c清单

 

#include < unistd.h >

#include < signal.h >

#include < sys/param.h >

#include < sys/types.h >

#include < sys/stat.h >

void init_daemon(void)

{

int pid;

int i;

if(pid=fork())

exit(0);//是父进程,结束父进程

else if(pid< 0)

exit(1);//fork失败,退出

//是第一子进程,后台继续执行

setsid();//第一子进程成为新的会话组长和进程组长

//并与控制终端分离

if(pid=fork())

exit(0);//是第一子进程,结束第一子进程

else if(pid< 0)

exit(1);//fork失败,退出

//是第二子进程,继续

//第二子进程不再是会话组长

 

for(i=0;i< NOFILE;++i)//关闭打开的文件描述符

close(i);

chdir("/tmp");//改变工作目录到/tmp

umask(0);//重设文件创建掩模

return;

}

2 test.c清单

#include < stdio.h >

#include < time.h >

 

void init_daemon(void);//守护进程初始化函数

 

main()

{

FILE *fp;

time_t t;

init_daemon();//初始化为Daemon

 

while(1)//每隔一分钟向test.log报告运行状态

{

sleep(60);//睡眠一分钟

if((fp=fopen("test.log","a")) >=0)

{

t=time(0);

fprintf(fp,"Im here at %s/n",asctime(localtime(&t)) );

fclose(fp);

}

}

}

以上程序在RedHat Linux6.0下编译通过。步骤如下:

编译:gcc -g -o test init.c test.c

执行:./test

查看进程:ps -ef

从输出可以发现test守护进程的各种特性满足上面的要求