首页 > 学技术 > 技术网文 > C/C++ > 正文

[原创] UNIX系统编程(10月11号更新 1楼,2楼,11楼,20楼,53楼,54楼)


来源 chinaunix.net kuqin整理

前言:
前一阵子在网上闲逛,偶然发现了CU这个地方感觉还不错。所以就注册了个ID有事没事来这里看看。慢慢发现这里编程高手云集,但同时也有一些刚学习了一些C,UNIX编程入门级选手。我想起我上大学学习UNIX系统编程时有过一本笔记,我花了两天时间从我书箱里的最下面把它给翻了出来。决定把这本“出土文物”拿出来进行一下“保护性修补”,然后贴出来,如果能给UNIX编程的初学者带来一些帮助的话,也就算这本给这本笔记找到了新的利用价值,反正总比压箱底强吧。
说道UNIX编程的入门书籍,凡是学过的应该很少有人不知道W.Richard Stevens的那本《UNIX环境高级编程》的吧。就像中国学习C语言的人80%以上都知道谭浩强教授一样。W.Richard Steven是国际著名的计算机专家,计算机图书作家。他所写的网络协议和UNIX编程等几部书部部都堪称圣经级读本。可惜的是他英年早逝,实是令人惋惜呀。说远了,我想说的是W.Richard Stevens的那本《UNIX环境高级编程》虽然好,但内容体系对一个刚刚起步的人来说也过于庞大,即使你看它一两百页也很难对UNIX编程有一个整体把握,我的帖子不追求面面俱到,希望能用最简洁的篇幅来让初学者对UNIX编程的主要内容有一个大体的把握,方便以后进阶学习。甚至不用刻意通读那本《UNIX环境高级编程》,用到什么地方在仔细读某一章节,边用边学效果会更好一些,我就是这么干的。呵呵,似乎有点老王卖瓜的意思,嘻嘻。
最后介绍一下我认为不错的学习工具,一是刚才说的那本《UNIX环境高级编程》,还有一本Kay A.Robbins, Steve Robbins的《UNIX系统编程》,里面例题较多,容易理解。在有就是man命令了,一套庞大的,快速的,免费的,权威的,方便的在线帮助系统。你有什么不会的就输入
$man 你想查询的内容

如果man你也不会用也不要紧,那你就
$man man
一下,呵呵。只可惜好像还没有翻译成中文,对于像我这样英文水平还有待提高的朋友来说读起来有点麻烦。最后还有一个好工具就是网络,有什么不会的google一下好多问题都能解决。
好,啰嗦的半天,说归正传,免得给大家造成光说不练的印象。

    第一章:什么是系统编程
UNIX系统编程,简单的说就是“C语言+系统调用(system call)”,学会了C语言再知道一些系统调用的方法,其实就可以进行UNIX系统编程了。那什么又是系统调用呢,其实初学者就把它看当成是函数用就可以了。这些“函数”是干什么用的呢,大家知道操作系统内核管理着我们的计算机资源,比如CPU,内存,硬盘等等。应用程序是无法直接访问到它们的。那我们想利用这些资源怎么办呢,内核就给我们提供了一个接口,我们可以利用这个接口来进行计算机资源的使用。内核也通过接口来判断我们的使用请求是否合法,合法的的提供资源,不合法的给与干掉。就好比是金库,银行和储户。金库里有要多地人民币,这就是资源。然而我们储户却无法直接接触到这些可爱的人民币,因为它们是通过银行来管理的,银行就好比是内核。但我们怎么样才能从金库里取出钱来呢,我们可以去银行窗口办理存款取款手续,这就是系统调用。当然,每个人的取款限额都不一样,大款的存款多,他可以取几百万甚至更多,而我存款少,取出一万块就已经不错了。这就是用户的系统调用权限不同。还有就是银行行长,他对这个金库的权限更大(root),当然他的责任也更大,他的一个错误决定有可能导致银行破产。这事可就大了。还有一种情况,一个在银行里没有什么存款的人,却通过一些技术手段,得到了一个大款的密码甚至是伪装成银行行长,把金库里的钱全提走了,这就是黑客。想想这种感觉你就知道为什么世界上有这么多黑客乐此不疲了。还有一些人没有什么“技术含量”直接“抢银行”,把你的计算机都抱走了。那你只有哭了,金库里的钱丢了,好在“房子”还在呀,这回连“房子”也丢了。依照这个比喻,那木马是什么呢?对,就是你的银行职员里出了内奸了。哈哈。好了不胡扯了,我了这么多例子就是想告诉大家,银行(内核)本身来说还是十分坚固稳定的,问题出在如何通过窗口(系统调用)安全地使用它。这也是学习UNIX系统编程是应该注意的问题。从下一个帖子开始说说说进程(process)和如何生成一个进程。

[ 本帖最后由 naker 于 2006-11-11 18:59 编辑 ]



 naker 回复于:2006-11-05 17:18:58

第二章 进程的生成(1)
    先说说什么是进程?假设你编好了一个程序,在它没有被调用之前,它只是乖乖地躺在你的硬盘上,什么事情都不干。好不容易编出来的不干活,这是我们不能容忍的。所以我们要把它调到内存里,然后通过CPU去执行它。所以说,进程就是一个在执行状态下的程序。
我们可以通过
$ps –e

命令来查看一下,计算机里所运行的进程有哪些。
那我们计算机里这么多的进程的又是从哪里来的呢,我们可以通过
$ps –axwf

来看到关于进程的一张家谱。其实,系统中的所有进程都是通过另一个进程生成的(除了0号进程以外),如果,A进程生成了B进程,那么可以把A进程叫做父进程,把B进程叫做A进程的子进程。也就是说,UNIX系统所有的进程都与其它进程保持着父子关系。

下面我们来看一个简单的例子
#include<stdio.h>

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

int main( int argc , char *argv[])
{
        int time;
        time = atoi( argv[1] )*60 ;       //将参数中的分钟换成秒数
        if( fork()==0 )       //生成子进程,括号里是子进程的代码
        {
                sleep( time );
                fprintf( stderr , "it is time to alarm!\n");
        }
        return 0;
}

执行时输入
$./a.out 2

这是一个简单的闹钟程序。你把它执行后,看似系统没有什么反映,其实不是,在后台你已经生成了一个进程,来监视时间。如果你用ps命令查看就能看到它。这时你可以接着干你自己的事情。等到了你设定的时间之后这个进程会提示你时间已经到了。这个程序虽不完善(没有进行输入参数的检查),但可以简单的告诉大家如何生成一个进程。
    为了生成一个新的进程,这里使用了 fork() 这个系统调用。它的作用是将父进程的各个变量的值复制给子进程,也就是说当你调用了fork()的那一刻,系统就为你生成了一个和父进程完全一样的进程。当然我们不想要一个父亲一样的孩子,孩子要有自己的个性,那我们如何来赋予孩子自己的个性呢?让我们先来看看fork()这个系统调用的概要。
头文件       #include <sys/types.h>

                    #include <unistd.h>
形式       pid_t fork(void);
返回值          成功时:        父进程中:子进程的进程号(>0)
                                子进程中:=0
                   失败时:        -1
                          
根据上面fork()的特性,我们可以通过fork()的返回值区分父进程要做的事和子进程要做的事。例如,
pid_t pid ;

pid=fork();
if( !pid)
{
        //子进程要做的事
}else if(pid >0)
{
       //子进程生成失败时,父进程要做的事
}else  //pid<0
{
//子进程生成失败时,父进程要做的事
}


好,我们现在已经学会了生成一个子进程了。但它还是遗传了许多父进程的特性,有可能大家会想,能不能用我们生成的子进程来执行另一个与父进程没有任何关系的程序呢。当然是可以的,比如我们常说的shell就是就是这个样子。shell本身也是一个进程当你输入命令回车以后,shell会生成一个子进程来执行你的命令,这条命令可以和shell没有丝毫关系。为了更好的说明问题我们先来做一个简单的shell。当然是最简单的那种。

#include<stdio.h>

#include<sys/types.h>
#include<unistd.h>
int main()
{
        static      char prompt[64]="> ";
        char    command[256];
        int   st;
        fprintf(stderr,"%s",prompt);    //  屏幕上的输出提示符
        while(gets(command)!=NULL)  //  取得键盘输入
        {
                if(fork()==0)     //  生成子进程
                {         //  子进程要做的事
                       execl(command,command,(char *)0)==-1 //执行所输入的命令
                }
                else
                {         //  父进程要做的事
                        wait(&st);  //  等待子进程结束
                        fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令
                 }
        }
        return 0;
}

好了我们保存,编译,执行以下看看
$./a.out

>/bin/ls  //这里必须输入命令的完全路径
        当前目录下文件名
>Ctrl+D 退出程序
$

这样我们的一个最初级shell就做好了。虽然它还很弱,还有着安全上的漏洞(使用了gets()),甚至连自己退出都不能,但起码可以让我们看到一个shell是如何执行的了。其实,一个复杂的shell最基本的东西也就使这些。大家要是有兴趣的话可以将gets()换掉,再加上退出功能。

我们再说说程序中出现的一个新的函数execl()。其实它是exec函数组中的一个。这组函数有:
int   execl( path , arg0 , arg1 , ... , argn , (char *)0 );

int   execv( path , argv );
int   execle( path , arg0 , arg1 , ... , argn , (char *)0 , envp );
int   execve( path , argv , envp );
int   execlp( file , arg0 , arg1 , ... , argn , (char *)0 );
int   execvp( file , argv );

参数定义如下:

char *path;
char *file;
char *arg0 , *arg1 , ... , *argn;
char *argv[];
char *envp[];
返回值:        成功时:所执行的命令将会覆盖现有的进程,所以无返回值
                 失败时:-1

比如说我们在shell里执行
$ /bin/ls –l

这个命令,实际上shell调用的是
execl( "/bin/ls" , "/bin/ls" , "-l"  , (char *)0 );

的一个系统调用
这个函数组函数有6个,用法就不一一说明了,大家可以参看一下其它资料。这里只告诉大家它们的用处,exec函数组就是用来调用一个可执行程序。还有一点很重要,一但进程调用了exec函数那么写在exec函数后面的进程代码将会被覆盖,变成无效的代码了。
例如下面一段代码,我们想在execl执行后输出一段文字列,这是办不到的。
 if(fork()==0)     //  生成子进程

                {         //  子进程要做的事
                       execl(command,command,(char *)0)==-1 //执行所输入的命令
        fprinf(stderr,"lalalalalalalalala!");  //
                }
                else
                {         //  父进程要做的事
                        wait(&st);  //  等待子进程结束
                        fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令
                 }

还有一个函数wait(),它的概要是
#include <sys/types.h>

pid_t wait(int *status);

返回值就是子进程的进程号
它的参数是个指针。C语言里讲过,一个函数想有一个以上的返回值时,你可以将想返回的变量的地址作为函数的参数。比如说将数组地址作为函数的参数等等。其实这里的status就是这个道理,它的值与子进程的结束方式有关系。当你的子进程以exit()方式结束的话,status所指向的地址的前8位将会是exit()的参数的后8位,而status所指向的地址的后8位是0。例如子进程是exit(1);那status所指向的地址的内容应该是0000 0001 0000 0000。还有如果子进程是通过信号(signal)终止的(信号我们以后再讲),那么我们也可以通过status的值来判断是哪一个信号终止了这个子进程。(详见man)
我们为什么还要在父进程中调用wait(),这涉及到进程状态的概念,我们稍候再说。

[ 本帖最后由 naker 于 2006-11-7 01:08 编辑 ]


 guhan010 回复于:2006-11-06 08:59:57

这个好,谢谢了


 zw2002 回复于:2006-11-06 09:19:51

适当温习一下,不错,继续!


 susesuse 回复于:2006-11-06 09:23:16

支持楼主,继续完善哈。


 longshort 回复于:2006-11-06 09:23:56

描述得挺细致,支持了!


 rhinux 回复于:2006-11-06 16:27:09

怎么不加精呢


 克里丝多 回复于:2006-11-06 17:35:57

好,支持!~


 王紫豪 回复于:2006-11-06 22:13:05

支持,顶


 langue 回复于:2006-11-06 22:23:22

似乎 0 号进程是内核“生”出来的,1 号 2 号什么的要看操作系统的具体实现了。特别的,在 OpenBSD 上,pid 是伪随机的,几乎不可预测。


 naker 回复于:2006-11-07 01:06:26

明天本来我要把这段期间做的一个小东西,给我们部门的人展示一下,结果晚上8点钟测试机OVER了,恢复它到了晚上11点才回来,太累了。本来今天不想写了,没想到有这么多人支持,眼泪哗哗的,再累也不觉得了!呵呵,废话少说,捞干的!

接着上次那个shell,我们再改进一下它,作个“强化版”。其实是打肿脸充胖子,如果说bash是宝马7系(我今生奋斗的目标)的话,前面我们那个也就是台破自行车,速度不行,还没闸(无法自己退出),还容易出车祸(有安全隐患)。但好歹也是辆车,它能上路也能跑。今天我们给前后胎打点气,让它再提提速。(火车服务那么差,还天天提速呢,咱自行车咋了?)

我们知道,每个UNIX用户都有自己的环境变量。比如$HOME,$SHELL,$PAHT啦等等。这里不多说这个了,不太了解的朋友可以看看别的关于shell的东西。我只说一点如果你是bash或csh的话输入
$env

就可以看到你的所有环境变量了。有人可能要问,这不是全部吧,怎么没看到PS1,PS2什么的呀。注意环境变量和shell变量是两码事,它们是shell变量。你也可以输入
$set

的话可以看到你所有的环境变量和shell变量。这里面就有它们了吧。

接下来我们先做一个读取环境变量并打印出来的程序
#include<stdio.h>

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

编译,执行
$./a.out

TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=192.168.1.10 2115 22
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
SSH_TTY=/dev/pts/1
DISPLAY=:0.0
.
.

以及你的系统中其它的环境变量。
.

  再看看我们上面说的那个$env命令,看看结果是不是一样的。10行不到的东西就能搞定复杂的环境变量是不是很神奇。想一想它是怎么运行的:当输入./a.out并回车,shell就fork一个子进程,子进程去调用exec执行./a.out,并把各个环境变量存到char *envp[]里面去。其实这候,子进程调用的就是上一次我们所说的那个int   execve( path , argv , envp )。我们这个程序是在bash或是csh什么上进行的。所以这回你相信我们上次那个自制的shell原理和那些非常牛的bash,csh是一样一样的了吧。所以我们也可以自豪地说:有什么的啊,我们的自行车不就是比宝马少俩轮吗?在轮子都是圆的这方面,原理是和宝马一样的。呵呵,想宝马想疯了。
  这回我们给我们的shell加点功能,让它能够带上参数。
#include <stdio.h>

#include <string.h>

void getarg( char *argv[] , char *p );      
int main()
{
        static      char    prompt[64]="> ";
        char    command[256],     *argv[256],   *p;
        int   st;
        fprintf( stderr , "%s" , prompt );
        while( (p=gets(command))!=NULL )
        {
               getarg( argv , p );
                if( fork()==0 )
                {
                        execv(argv[0],argv);
                }
                else
                {
                        wait( &st );
                        fprintf( stderr , "%s" , prompt);
                }
        }
        return 0;
}
void getarg( char *argv[] , char *buf )
{
        int i = 1;
        argv[0] = strtok(buf," ");
        while((argv = strtok(NULL," ")) != NULL)
                i++;
}

这回编译再试试看看是不是可以带参数了。
这个程序我不给注释了,参照上次那个程序相信大家能看懂吧。说一下里面的一个新函数strtok()
#include <string.h>

char *strtok(char *str, const char *delim);

作用是将文字列str,按标记delim分段,delim可以是多个字符。使用方法:第一次调用时第一个参数是str本身,以后在调用第一个参数设成NULL。返回值就是分割后的每段段首的地址指针。举个例子,
#include        <stdio.h>

#include        <string.h>

int main(void)
{
        char str[] = "ABCD ef.1234.G";
        char *tp;
        tp = strtok( str, " ." ); //分段符设为空格和点
        puts( tp );
        while ( tp != NULL ) {
                tp = strtok( NULL," ." ); 
                if ( tp != NULL ) puts( tp );
        }
        
        return 0
}
;
运行结果:
ABCD

ef
1234
G

怎么样?一目了然,不用再多说明了吧。

好,我们的shell就先到这里了,毕竟我们为了入门嘛。我只是起了一个头,希望有兴趣的朋友自己可以在机器想些其它的有趣功能编着玩玩。当年我记得我们还做过,自动检查命令path的,可查询命令历史纪录的,显示子进程号的等等。“自行车”也做得像模像样的,可惜毕业时觉得也没什么用,就都留在学校机房的solaris里了,现在估计早被人删了。今天就说这么多,下次我准备说说关于进程状态。之后就开始说第二部分文件系统。

[ 本帖最后由 naker 于 2006-11-7 12:24 编辑 ]


 susesuse 回复于:2006-11-07 10:46:21

楼主写得很好啊,而且行文轻快幽默。
呵呵,可以出本《深入浅出unix系统编程》的书了。


 rhinux 回复于:2006-11-07 12:07:34

看楼主的文可作为一种复习和提高


 splitflag 回复于:2006-11-07 12:47:56

写得很有趣嘛,支持


 cuicp 回复于:2006-11-07 12:50:00

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
        static      char prompt[64]="> ";
        char    command[256];
        int   st;
        fprintf(stderr,"%s",prompt);    //  屏幕上的输出提示符
        while(gets(command)!=NULL)  //  取得键盘输入
        {
                if(fork()==0)     //  生成子进程
                {         //  子进程要做的事
                       execl(command,command,(char *)0)==-1        //执行所输入的命令
                }
                else
                {         //  父进程要做的事
                        wait(&st);  //  等待子进程结束
                        fprintf(stderr,"%s",prompt);  //  输出提示符,等待命令
                 }
        }
        return 0;
}




fork有个问题一直不明白,就是在循环中调用fork函数,在for中调用时,如果不加exit函数,会产生递归调用,产生第三层或更高层的子进程,在while中没有试过。

楼主的程序会不会产生同样的问题?
谢谢!!


 aitongqi017 回复于:2006-11-07 16:49:01

今天刚好看到进程这儿。大有收获啊!
谢谢楼主!


 naker 回复于:2006-11-07 19:25:18

引用:fork有个问题一直不明白,就是在循环中调用fork函数,在for中调用时,如果不加exit函数,会产生递归调用,产生第三层或更高层的子进程,在while中没有试过。

楼主的程序会不会产生同样的问题?
谢谢!! 


应该是不会的,别看没有exit,但我调用的是exec。你看了我上面的帖子就知道了,exec把它后面的东西都覆盖掉了,就算写了也白写,这是一。二,本质上说exec又被你调动的程序代替,所以不用担心。程序都写好了,你可以粘贴一下,自己跑跑试试看啊。我觉得自己动手试一试会比光读代码理解的深刻。
另外说一下递归调用,你说的那个for会产生递归调用,我不知道你说的情况时什么样的,不如你把代码写下来大家分析分析。但听你说的这个意思,好像是父进程没有wait().不是递归调用而是父进程没做好计划生育。你用ps axwf看看它们到底是谁的孩子不就行了。


 aitongqi017 回复于:2006-11-07 19:40:29

我想问楼主一个问题,是不是每一个创建的子进程都必须在使用完,由父进程调用wait函数来的到该进程的终止状态,释放资源。
是不是必须的?


 cuicp 回复于:2006-11-07 20:20:09

引用:原帖由 naker 于 2006-11-7 19:25 发表

应该是不会的,别看没有exit,但我调用的是exec。你看了我上面的帖子就知道了,exec把它后面的东西都覆盖掉了,就算写了也白写,这是一。二,本质上说exec又被你调动的程序代替,所以不用担心。程序都写好了,你 ... 





程序如下:
引用:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        pid_t pid;
        int i;

        for(i=0;i<3;i++)
        {
                if( (pid=fork()) < 0)
                        fprintf(stdout, "fork error on %d\n",i);
                else if( pid > 0)
                        printf("parent fork  %d\n",i);
                else
                        
                        printf("child fork %d\n", i);
        }
        
        exit(0);
}
执行结果:
parent fork  0
child fork 0
parent fork  1
parent fork  1
child fork 1
parent fork  2
parent fork  2
parent fork  2
child fork 1
parent fork  2
child fork 2
child fork 2
child fork 2
child fork 2
初步分析:
第一层循环3次i=0,1,2)
parent fork  0
parent fork  1
parent fork  2
child fork 0(进程1,初始i=0)
child fork 1(进程2,初始i=1)
child fork 2(进程3,初始i=2)
第二层进程1循环2次i=1,2)
parent fork  1
parent fork  2
child fork 1(进程4,初始i=1)
child fork 2(进程5,初始i=2)
第二层进程2循环1次i=2)
parent fork  2
child fork 2(进程6,初始i=2)
第三层进程4循环1次i=2)
parent fork  2
child fork 2(进程7,初始i=2)





这个问题不知道是不是你所说的没有等待的原因,我现在没有环境,所以没试验!
如果在之进程中写exit的话,程序就不会产生孙子进程了。
另外还有就是:

引用:
int main()
{
        pid_t pid;
        int i = 0;
        printf("asfsda\n");
     for(i=0;i<3;i++)
        while(1)        
        {       
                if( (pid=fork()) < 0)
                        fprintf(stdout, "fork error on %d\n",i);
                else if( pid > 0)
                        printf("parent fork  %d\n",i);
                else    
                {       
                    printf("child fork %d\n", i);
                  //exit(0);   //不明白的地方
                }       
        }       
        
        return(0);
}
(1)加exit(0);结果:
asfsda
parent fork  0
parent fork  1
child fork 0
child fork 1
child fork 2
parent fork  2
不加exit(0);结果:
asfsda
parent fork  0
parent fork  1
parent fork  2
child fork 0
parent fork  1
child fork 1
parent fork  2
child fork 1
parent fork  2
parent fork  2
child fork 2
child fork 2
child fork 2
child fork 2

这里我就不明白了,为什么子进程没有执行printf("asfsda\n");而是从for开始执行呢?是fork函数对循环作特殊处理了吗?




你说exec后面都覆盖掉了,但是如上面的程序,如果没加exit,也没有exec函数,怎么printf("asfsda\n");在孙子进程中就没有执行呢?

能解答一下吗?
谢谢!!


 naker 回复于:2006-11-07 21:18:54

今天我们来说说进程的状态。
虽然说,UNIX看上去好像可以同时运行多个进程,实际上一个CPU同一个时间里只能处理一个进程。然而,CPU速度飞快的在各个进程中穿梭,轮流地执行每一个进程。所以在宏观上看起来像是所有进程一起运行。但微观上,某一个时刻你的计算机只能有一个进程在被CPU处理。当然,这是一个CPU的情况下,多个CPU时涉及到原子操作等等概念,说起来就比较复杂了,有兴趣的朋友可以看看关于内核的资料。回刚才的话,这个正在被处理的进程状态就是执行态,而其它排队等待被执行的进程就是就绪态,还有一些进程自己还没准备好,比如等你的键盘输入等等,这样的进程它们不参加就绪态的排队,而是等准备好了再去排队这叫休眠态。还有全部执行完了还没有被回收的进程,也有一个叫法我们一会说。
举个例子,CPU与进程的关系好像就是象棋大师同时与很多象棋爱好者下棋一样。每个象棋爱好者都有自己的一盘棋,好比进程。象棋大师就是CPU,同时要处理很多盘棋。因为他非常牛,所以轮流到每个棋手前走上一步,那他正走的这盘棋就是执行态,其它已经走完自己的棋等大师过来走棋的就是就绪态,还有一些棋手有可能还在想他自己怎么走那大师没有必要过来理你,直到等他自己走完了下一次大师过来才会理他这就是休眠态。其实大师也是人不可能有那么好的记忆力,经常是走完了你的棋,走下一个人的棋的时候就把你的棋给忘了,那他怎么回来还能接着走呢,因为你的棋盘上保留的就是上次走过的原样,大师再看一遍就想起来了。这就是上下文切换。还有些人已经下输了,大师也不会再过来了。可难得有和大师交流的机会,这些人占着椅子(资源)在那里不走,这就是我们刚才说的那个全部执行完了还没有被回收的进程。为什么它的状态名这么难以启齿呢。因为我总觉得它翻译的太难听了。英语叫zombie,翻译过来叫僵死态或僵尸态,太恐怖了。所以我们还是叫他的英语zombie吧。计算机起源于西方,所以其计算机文化也就带有西方特色,有许多词英语听上去还挺好听甚至富有幽默感,但用中文说味道就不太一样了。比如,bug,daemon了等等。翻译过来就成了漏洞,精灵进程,很正统很严肃。反正原意也已经变了,为什么就不能体现一些有中国特色的幽默呢。比如bug可以叫一小撮隐藏在人民内部的破坏分子。daemon叫雷锋进程,因为它在后面默默无闻的奉献,但很少有人知道它们的存在。
不说了,扯远了。我们再说两句zombie。zombie一般情况下是没有用处的垃圾,所以一个残留zombie的程序不是一个好程序,甚至有安全漏洞。你想你的电脑里有许多的僵尸那是一件多恐怖的事呀。但也有一些高手专门留下zombie有用,不过那是高手的事情了,初学阶段就可以认为zombie没用,等你成了高手在考虑它有什么用也不迟。
用fork()生成子进程,子进程结束的时。要给父进程送去发一个信号(信号以后讲),然后转为zombie状态。当父进程收到了这个信号,就会来收回它孩子的进程列表,这就是父进程中wait()的另一个作用,子进程这时才算真正结束。如果父进程先于子进程结束,那么这些孤儿就会由init来收养,料理它们的后事。
我们用一个程序来证明这一点:
#include<stdio.h>

int main()
{
        int st;
        if( fork()==0 )
        {
                exit(1);        //子进程结束
        }
        else
        {
                sleep( 300 );     //父进程休眠300秒
                wait( &st );
                printf("Return code=%d\n",i);
        }
        return 0;
}

编译,执行,看看是不是有一个 zombie,这是因为它父亲还没睡醒,没来得及处理它的后事呢。

好,进程我们就说到这里。下一次开始我们说说文件系统。
剩下的内容还有,
标准输入输出及管道
信号
共有内存
消息队列
信号量

感谢大家对我的支持,希望大家继续支持我,你们是我发帖的原动力。

[ 本帖最后由 naker 于 2006-11-7 23:30 编辑 ]


 naker 回复于:2006-11-07 21:52:15

引用:原帖由 naker 于 2006-11-7 21:18 发表
你说exec后面都覆盖掉了,但是如上面的程序,如果没加exit,也没有exec函数,怎么printf("asfsda\n");在孙子进程中就没有执行呢?

能解答一下吗?
谢谢!! 


你的第一个程序,没加exit,也没有exec函数,子进程执行完else,按父进程代码继续往下走。
第二个程序,子进程从fork分叉的那一点开始往下执行,fork上面的是不执行的。


 hebioldman 回复于:2006-11-07 23:07:16

#include<stdio.h>
int main()
{
        int st;
        if( fork()==0 )
        {
                exit(1);        //子进程结束
        }
        else
        {
                sleep( 300 );     //父进程休眠300秒
                wait( &st );
                printf("Return code=%d\n",i);
        }
        return 0;
}
编译,执行,看看是不是有一个 zombie,这是因为它父亲还没睡醒,没来得及处理它的后事呢


楼主,这里子进程用exit(1)会可能会导致父进程也退出吧?用_exit(1)是不是更合理一些?
参考:
‘exit()’与‘_exit()’有不少区别在使用‘fork()’,特别是‘vfork()’时变得很 
突出。 

‘exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构 
(user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序 
(译者注:自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对 
应,后一个函数只为进程实施内核清除工作。 

在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是 
因为使用它会导致标准输入输出(译者注:stdio: Standard Input Output)的缓冲区被 
清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建 
在系统临时目录下,文件名由系统随机生成)。在C++程序中情况会更糟,因为静 
态目标(static objects)的析构函数(destructors)可以被错误地执行。(还有一些特殊情 
况,比如守护程序,它们的*父进程*需要调用‘_exit()’而不是子进程;适用于绝 
大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。) 

在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响 
*父*进程的状态


 nlsycb 回复于:2006-11-08 00:19:04

这帖子真不赖,忍不住要先回个,千万别断啊,偶一定跟住了看!


 linuxs 回复于:2006-11-08 07:57:21

楼主加油啊


 gabril 回复于:2006-11-08 09:06:56

强贴留名。我努力消化下


 aitongqi017 回复于:2006-11-08 09:23:07

引用:原帖由 naker 于 2006-11-7 21:18 发表
今天我们来说说进程的状态。
虽然说,UNIX看上去好像可以同时运行多个进程,实际上一个CPU同一个时间里只能处理一个进程。然而,CPU速度飞快的在各个进程中穿梭,轮流地执行每一个进程。所以在宏观上看起来像是所 ... 





:)刚好解决了我的疑问,谢谢!
继续楼主!


 cuicp 回复于:2006-11-08 09:45:16

引用:原帖由 naker 于 2006-11-7 21:52 发表

你的第一个程序,没加exit,也没有exec函数,子进程执行完else,按父进程代码继续往下走。
第二个程序,子进程从fork分叉的那一点开始往下执行,fork上面的是不执行的。 




我有把程序改了一下,
引用:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    pid_t pid;
    int i = 0;
    printf("asfsda\n");
    for(i=0;i<3;i++)
    //while(1)        
    {       
        printf("%d  pid = %d\n", i, getpid());
        if( (pid=fork()) < 0){
            fprintf(stdout, "fork error on %d\n",i);
        }else if( pid > 0){
            printf("parent fork  %d pid = %d\n", i, getpid());
        }else{       
            printf("child fork %d  pid = %d\n", i, getpid());
            //exit(0);   //不明白的地方
        }       
    }       
    return(0);
}

结果:
asfsda
0  pid = 25141
child fork 0  pid = 25142
1  pid = 25142
parent fork  0 pid = 25141
1  pid = 25141
child fork 1  pid = 25143
2  pid = 25143
parent fork  1 pid = 25142
2  pid = 25142
child fork 2  pid = 25146
parent fork  2 pid = 25142
child fork 1  pid = 25144
2  pid = 25144
parent fork  1 pid = 25141
2  pid = 25141
child fork 2  pid = 25148
parent fork  2 pid = 25141
child fork 2  pid = 25145
parent fork  2 pid = 25143
child fork 2  pid = 25147
parent fork  2 pid = 25144




打印pid 和打印parent 的数目一样多,也就是说子进程是从 for(i=0;i<3;i++)往下执行的,
那么是不是说在没有exit或exec(也就是没有终止或被覆盖)的情况下,
1。无循环:子进程执行分支下的语句。
2。有循环:子进程执行循环里的语句,而不是fork分支里的语句?
这是不是fork对循环的特殊处理?

至于exit和_exit的关系,不是太清楚,

http://bbs.chinaunix.net/viewthread.php?tid=62916

里也有讨论,我看我们公司里这方面的程序,也是用的exit,
没出现什么问题呀!

期待中!!


 shguan 回复于:2006-11-08 10:10:38

非常谢谢这种大公无私的人


 zhangdonglin 回复于:2006-11-08 10:28:56

系统全面,受益匪浅,支持楼主!


 yuccc 回复于:2006-11-08 10:29:15

恩恩。。。


 limutian 回复于:2006-11-08 11:49:40

it is very good !!!
ding!!!!


 wuchengeng 回复于:2006-11-08 13:03:59

不错!!


 jeromeecho 回复于:2006-11-08 13:31:06

引用:
#include<stdio.h>
int main ( int argc , char *argv[] , char *envp[] )
{
        int   i;
        for( i=0 ; envp!=NULL ; i++ )
        {
                printf( "%s\n" , envp );
        }
        return 0;
}



请问一下这个程式里,是怎么实现让envp指向环境变量指针的?
不是要把指针指向环境变量指针environ,并且要声明environ吗?extern char **environ
你的代码我运行了,是可以的。但我想不明白?查资料也没有查出来,请知道的说一下,新学编程。
还有main()函数的参数可以是三个吗?


 langue 回复于:2006-11-08 13:43:09

引用:原帖由 jeromeecho 于 2006-11-8 13:31 发表


请问一下这个程式里,是怎么实现让envp指向环境变量指针的?
不是要把指针指向环境变量指针environ,并且要声明environ吗?extern char **environ
你的代码我运行了,是可以的。但我想不明白?查资料也没有查 ... 



libc 会告诉你的 main(),这些参数分别是什么。


 jeromeecho 回复于:2006-11-08 13:59:06

没查到,还在研究中。。。。。。


 zhuoshi1981 回复于:2006-11-08 15:06:56

顶,再顶


 aitongqi017 回复于:2006-11-08 15:56:13

引用:原帖由 jeromeecho 于 2006-11-8 13:31 发表


请问一下这个程式里,是怎么实现让envp指向环境变量指针的?
不是要把指针指向环境变量指针environ,并且要声明environ吗?extern char **environ
你的代码我运行了,是可以的。但我想不明白?查资料也没有查 ... 



在每个程序执行时都接收一张环境表,可以使用自己定义的也可以使用系统的环境表(根据调用不同的exec函数而不同)。所以在main函数的参数中定义一个指向该环境表的指针数组,进而引用环境表。
main函数本来是有三个参数的,但是ANSI C 和POSIX.1都不推荐使用第三个参数,而是用函数getenv()得到它,或者引用全局变量environ.


 dchxcrow 回复于:2006-11-08 17:10:35

我收藏了。
楼住支持。


 sithui 回复于:2006-11-08 17:49:04

我收了,顶啊!
楼主讲得太精彩,太生动了


 naker 回复于:2006-11-08 20:08:42

引用:编译,执行,看看是不是有一个 zombie,这是因为它父亲还没睡醒,没来得及处理它的后事呢


楼主,这里子进程用exit(1)会可能会导致父进程也退出吧?用_exit(1)是不是更合理一些?
参考:
‘exit()’与‘_exit()’有不少区别在使用‘fork()’,特别是‘vfork()’时变得很 
突出。 

‘exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构 
(user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序 
(译者注:自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对 
应,后一个函数只为进程实施内核清除工作。 

在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是 
因为使用它会导致标准输入输出(译者注:stdio: Standard Input Output)的缓冲区被 
清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建 
在系统临时目录下,文件名由系统随机生成)。在C++程序中情况会更糟,因为静 
态目标(static objects)的析构函数(destructors)可以被错误地执行。(还有一些特殊情 
况,比如守护程序,它们的*父进程*需要调用‘_exit()’而不是子进程;适用于绝 
大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。) 

在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响 
*父*进程的状态 



这个问题我也不太清楚,不敢妄言,但我知道至少vfork时应该用_exit()。但一般情况下fork后用exit也没见出什么问题。
这个还是留给高手解答吧


 naker 回复于:2006-11-08 20:27:19

浏览量超过1000大关,发帖庆祝一下。另外到这个周末我要出差,所以可能不会更新。请支持我的朋友耐心等待一下。
感谢大家支持,有人让我把它弄成PDF,还有人替我把书名都想好了,真是太感动了。
另外还想问大家一句,是把所有内容写在一个贴子里好,还是每大章发一个新贴好。听听大家的意见,我好知道下一章怎么贴。我感觉都贴在一个贴子里,就把本来挺完整的内容贴零碎了。大家说呢?


 jiaoyingjun 回复于:2006-11-08 21:22:51

写成PDF的好啊!
看的清楚啊。
顶,支持楼主。。。


 jeffwang8001 回复于:2006-11-08 22:00:25

支持整理成pdf


 susesuse 回复于:2006-11-08 22:58:51

可以先分开帖子发,然后最后集中到一个帖子里并做成pdf里。
建议楼主除了对《unix环境高级编程》涉及的知识用形象的语言进行阐述,对编译器,链接器,加载器的原理和如何使用最好也进行相关阐述,举个例子来说,很多初学者对动态链接的实质和如何使用不是很清楚。要是再能结合一个适当大小的工程,讲述如何单独写makefile编译及用autoconf,automake等工具自动化编译,这就真的可以去出本书了,呵呵。这些东西似乎没有哪本书讲的很清楚易懂,一般只能查看gnu的官方文档。
楼主加油哈,期待你的大作! :)


 jackleexp 回复于:2006-11-09 07:15:09

感觉Unix里和Linux编这些都差不多,就是LZ说的比较生动,LZ有当老师的潜力


 dlms 回复于:2006-11-09 08:45:46

讲的是相当的不错!谢谢了!好久没有看到好文了,俺也上来冒个泡!


 zw2002 回复于:2006-11-09 09:12:12

放在一个贴子时原好,连贯!


 longshort 回复于:2006-11-09 10:04:50

引用:这个问题我也不太清楚,不敢妄言,但我知道至少vfork时应该用_exit()。但一般情况下fork后用exit也没见出什么问题。
这个还是留给高手解答吧


俺不是高手,不过俺多少知道一点fork后用exit的结果,俺也谈谈感想给大家作个参考。

一般情况下,只要子进程把该交代的都交代了,用exit退出就是安全的。原因就是子进程在一个独立的空间中运行,所有的指针和变量都是从父进程复制过来的,它并不造成对父进程的数据覆盖,除非它要操作共享存储器或者修改硬盘文件。exit只对本进程空间进行善后处理。

在C/S情况下,守护程序父进程设置SIGCHLD忽略,fork以后关闭socket回到listen,而子进程运行完毕可以直接exit,没有僵尸进程产生。

在共享数据处理的情况下,父进程要等待子进程的处理结果,那就必须调用wait,只要子进程exit,父进程的wait就解除,不会有僵尸进程产生。

如果open一个文件或socket,而没有关闭它们就退出,那么程序也会产生僵尸。曾经有一位同事在刚进开发组时,写的程序中多次调用fopen而没用fclose,结果程序运行老是报资源耗尽,又不好意思说,查了几天没结果,最后我让他用ps看一下,僵尸进程居然有二十多个,加上其他的用户进程,一个用户的进程配额就迅速耗尽了。这事自然成了大家茶余饭后的笑话,说是只知吃饭,不知拉屎,岂不撑破肚皮。笑话尽管笑话,这事也给大家重新复习了一下某些操作成对调用的必要性,当然这是后话了。


 naker 回复于:2006-11-09 19:56:39

引用:原帖由 susesuse 于 2006-11-8 22:58 发表
可以先分开帖子发,然后最后集中到一个帖子里并做成pdf里。
建议楼主除了对《unix环境高级编程》涉及的知识用形象的语言进行阐述,对编译器,链接器,加载器的原理和如何使用最好也进行相关阐述,举个例子来说,很多初学者对动态链接的实质和如何使用不是很清楚。要是再能结合一个适当大小的工程,讲述如何单独写makefile编译及用autoconf,automake等工具自动化编译,这就真的可以去出本书了,呵呵。这些东西似乎没有哪本书讲的很清楚易懂,一般只能查看gnu的官方文档。
楼主加油哈,期待你的大作! :) 


拜托,俺不是专业写作文的。大部分时间还得在公司里给老板干活呢


 gxguax 回复于:2006-11-10 12:25:59

好东东要顶,不要沉哦!


 zhongfangqing 回复于:2006-11-10 19:37:42

不顶,说不过去


 jacquen 回复于:2006-11-10 20:57:09

hao


 naker 回复于:2006-11-11 00:20:40

第三章:文件系统
这回我们来说一下UNIX的文件系统。由于一般情况下UNIX机的硬盘会很大,所以一般你可以给它分成几个区,而每个分区又都可以有独立的文件系统。如果你是UNIX系统,你有可能看见
/dev/sd/c0t0d0s0

/dev/sd/c0t0d0s1

这样的文件。
如果你是linux你有可能看见
/dev/hda0

/dev/hda1

等等。这些文件,每一个就可以看成是一个分区。当计算机启动时,系统先找到/(根目录),然后系统就像挖土豆子一样找到一个带出来一串似的,顺着根目录一带就带出来挂载在根目录下的所有目录(详细的请查一下/etc/fstab这个文件)。在UNIX下叫目录(directory)的感念,就像是windows下的文件夹(folder),但与windows最大的区别在于UNIX没有A盘,C盘这样的概念。它所有的文件都要挂载到根目录下的某个子目录底下,另外UNIX把所有设备都看成是文件。比如A盘就有可能是/dev/fd0这个文件。那比如我们想使用软盘怎么办呢,以为软盘是个临时文件,所以在开机时一般不会设为自动挂载。所以我们要手动挂载它在这个某一个目录下。有可能我说得大家有点糊涂,不要紧我们举个例子。文件系统就像一棵大树,树干只有一个那就是根目,树干往上是树杈就是一个一个的子目录,树杈往上有可能还是树杈,那就是这个子目录的子目录,也有可能是树叶,树叶就是文件。现在你手里有一根树枝上面有几个叶子,可你拿手攥着它,它是不会得到来自这颗大树的养分的,除非你把它嫁接在大树的某一个树杈上。就是这个道理。
回到刚才的话,具体怎么能使用软盘呢。
$mount /dev/fd0 /mnt/floppy

这样你就把这个小树杈,嫁接到了/mnt/floppy上。等你用完了软盘输入
$umount /mnt/floppy

就等于把这个树枝又掰折了。
说了这么多,换换口味看一个程序,让我们来调查一下分区的剩余空间有多少
#include <stdio.h>

#include <sys/types.h>
#include <sys/statvfs.h>
int main(int argc , char *argv[])
{
        struct statvfs buf[1];
        sync();
        if( statvfs(argv[1],buf)!=0 )
        {
                fprintf(stderr , "Cannot read super block !\n");
                exit(1);
        }
        fprintf(stderr , "%4.1f %% free\n",
                (float)buf[0].f_bfree / buf[0].f_blocks*100 );
        return 0;
}
编译执行:
$./a.out

36.7 % free

这里用到了statvfs()这个系统调用。它是用来得到文件系统总体信息的系统调用。得到的信息它会放在一个叫做statvfs的结构体里。这时候结构体里的f_bfree表示的就是空闲的block数,f_block表示的就是所有block数。他们相除就得到了使用比,是不是很简单呢。这里注意,statvfs系统调用返回的是一个叫做statvfs的结构体,名字相同别弄混了。下面把statvfs()的格式写一下。
#include<sys/types.h>
#include<sys/statvfs.h>

int statvfs( char *path , struct statvfs *buf );

返回值:        成功时:0失败时:-1

结构体statvfs里还有好多关于这个分区的信息,感觉就像windows下查看C盘属性的功能差不多,但还要强大,大家可以自己上机man一下。我就不一一说了。
上面另一个系统调用sync(),是用来将内存上的硬盘信息回写到硬盘上而用的。这时因为UNIX系统为了能快速查找硬盘信息,而将super block(下面要讲)信息放在内存里。它是一个没有参数也没有返回值的系统调用。
另外还有一个命令也叫sync,作用和sync()是一样的。

上面说到了一个概念叫super block,下面我们就说说它。
一般,一个分区包含有这么几个部分:boot block,super block,i node block,data block。我们一个一个来说说。
boot block :
它总是在每个分区的最前面,用来存放开机引导程序。大小是512或1024字节。LINUX下我们常用的LILO等就放在这里。
super block :
super block用来存放这个分区全体的管理信息。我说说关于i-node的信息,别的大家可以查查任何一本关于UNIX文件系统的书,都会有的。super block里有一个存放空i-node号的数组。如果我们建立新文件时,系统就可以知道现在哪些i-node号是可用的。上面我们说了,super block的信息是常驻内存的,这样系统建立新文件时就不用去读盘,而直接从内存里调出来就可以了,提高了系统运行速度。
i node block :
i node block就是存放i-node的部分。i-node即index node是简称。里面装的关于每一个文件的属性,就好像windows里选文件点右键,查看属性里显示的东西。比如,文件的所有者,权限,大小,修改日期,硬盘上的位置等等。
data block :
这里存放就是货真价实的数据了。没什么好说的了

我们再看一个程序。它是用来将i-node号及文件名输出来。
#include <stdio.h>

#include <dirent.h>
int main( int argc , char *argv[] )
{
        DIR *fp;
        struct dirent *p;
        fp=opendir(argv[1]);
        while( (p=readdir(fp))!=NULL )
        {
                printf("%i %s\n", p->d_ino , p->d_name );
        }
        closedir(fp);
        return 0;
}

执行
$./a.out /

96673 sbin
966721 dev
998945 root
11 lost+found
2 ..
2 .
.
.
等等
.
.
1031169 lib

好下面我们来说说上面的几个系统调用
opendir()
作用:打开一个目录

#include <dirent.h>
#include <sys/types.h>
DIR *opendir( char *dirname );
返回值     成功时返回DIR结构体的地址,失败返回NULL
 
readdir()
作用:读取目录信息到DIR结构体。DIR结构体是什么大家也可以查一下man 3 readdir

#include <linux/types.h>
#include <linux/dirent.h>
struct dirent *readdir(DIR *dir);
返回值:成功是返回DIR的地址,失败返回NULL


closedir()
作用:关闭目录

#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dir);
返回值:成功时为0,失败为1


今天就先说这么多,明天我看再来看看如何对文件进行操作。


 naker 回复于:2006-11-11 18:57:36

书接上回,上次我们说到了UNIX文件系统的结构,知道了有哪几个block,各是干什么用的,这回我们说说怎样对文件进行操作。
我们一般对文件进行操作时不是直接指定i-node号的,而是指定文件的路径的。然而,在内核对文件的操作却是通过i-node来进行的。那么问题就来了,系统又是如何将路径和i-node联系起来从而又找到硬盘上数据存放的具体位置的呢。
我来做一下解释,比如你要访问/home/user1/test.c这个文件。系统从根目录开始查找,根目录的i-node号是2这个是确定的。系统就去i-node block里找2号i-node,看过上次贴的朋友应该知道,它会告诉系统根目录信息数据存放在硬盘上的具体位置,里面会有根目录下的文件与i-node的对应表,就像我们上次那个程序里看到的那样。系统从而得到了home目录的i-node号然,然后再去硬盘看home目录下的文件与i-node的对应关系,这样也就找到了test.c在硬盘上的具体位置。
其实如果test.c这个文件很大那它在硬盘上也不是放在一起的。data block里其实是一个一个的小数据块,数据就放在这里。但这个块的大小是一定的有1024字节的也有2048,4096的这根据你的分区方法不同也不太一样。如果你的文件比较大那么一个数据块里就放不下它,这就需要更多的块。打个比方,你有130万RMB(好比一个文件),这么多钱可别被贼偷了去,要把它们放到保险柜(好比数据块)里,但保险柜太小一个只能放50万。那你需要几个保险柜呢。(好像是小学时候做的应用题)答案是3个,最后一个装了只装了30万。浪费了20万的空间。那我接着要问这时我正好有20万(好比另一个文件)也要放进保险柜,那我们一共要多少个保险柜呢。如果你说还是3个那你就错了。虽然你有一个保险箱浪费了20万的空间,但其他人的钱(其他文件)也是不能放进去的,否则谁也分不清里面哪些钱是你的哪些钱是我的了。所以我的20万也好即使是一块钱也需要一个新的保险柜。

下面我们举个例子来看看如何用i-node里面的信息。这个程序是将我们指定的两个文件名中输出更新时间晚的一个
#include <sys/types.h>

#include <sys/stat.h>
#include <stdio.h>
int main( int argc , char *argv )
{
        struct stat buf[2] , *p ;
        if( argc!=3 )
        {
                fprintf( stderr , "Usage : %s file1 file2\n" , argv[0] );
                exit(1);
        }
        p=buf;
        if( stat(argv[1],p)!=0 ) 
        {
                fprintf( stderr , "%s not found !\n" , argv[1] );
                exit(1);
        }
        p++;
        if( stat(argv[2],p)!=0 )    
        {
                fprintf( stderr , "%s not found !\n" , argv[2] );
                exit(1);
        }
        if( buf[0].st_mtime > buf[1].st_mtime )       //比较更新时间
                printf( "%s\n" , argv[1] );
        else
                printf( "%s\n" , argv[2] );
        return 0;
}

大家可以自己执行一下看看结果。
我们来说说stat(),使用它可以得到一个叫stat的结构体,它包含了i-node里面的一部分信息。诸如,文件的权限,i-node号,链接数,所有者id,组id,文件大小等等。具体的大家可man stat查看一下。
作用:得到一个stat结构体。 

#include <sys/types.h>
#include <sys/stat.h>
int stat( char *path , struct stat *buf );
返回值:        成功时:0 失败时:-1

为了简洁,我们的程序里没有考虑到更新时间相同的文件。大家可以自己补上。

下面我们做一个改变文件权限的程序。
#include <sys/types.h>

#include <sys/stat.h>
#include <stdio.h>
#define MASK 0555 //设置掩码
int main( int argc , char *argv[] )
{
        struct stat buf[1];
        mode_t mode;
        if( argc!=2 )
        {
                fprintf( stderr , "Usage : %s file\n" , argv[0] );
                exit(1);
        }
        if( stat(argv[1],buf)!=0 )
        {
                fprintf( stderr , "Cannot read i-node\n" );
                exit(1);
        }
        mode = ( buf[0].st_mode & MASK );     
        if ( chmod(argv[1],mode)!=0 )         //改变文件的权限
        {
                fprintf( stderr , "Cannot change mode\n" );
        }
        return 0;
}


怎么样这个程序不能我直接说里面的系统调用

chomd()
作用:改变文件的权限(关于权限下面要说)

#include <sys/types.h>
#include <sys/stat.h>
int chmod( char *path , mode_t mode );
返回值:        成功时:0 失败时:-1

那什么是文件的权限呢?你可以用
$ls –l /

命令来查看根目录下所有子目录的权限。你会看见类似下面这样的
drwxr-xr-x  2 root root  4096 11-04 22:43 bin

drwxr-xr-x  4 root root  1024 09-27 02:15 boot
drwxr-xr-x 10 root root  3640 11-11 15:08 dev
drwxr-xr-x 88 root root 12288 11-11 15:08 etc
drwxr-xr-x  3 root root  4096 09-27 02:40 home
drwxr-xr-x 11 root root  4096 10-31 21:49 lib
drwx------  2 root root 16384 09-27 10:51 lost+found
drwxr-xr-x  2 root root  4096 09-27 20:27 media
drwxr-xr-x  2 root root  4096 2006-02-11 misc
drwxr-xr-x  2 root root  4096 2006-02-12 mnt
drwxr-xr-x  2 root root     0 11-11 15:08 net
drwxr-xr-x  2 root root  4096 2006-02-12 opt

我们看见的每行最前面的那一串字符就是表示权限的。其中第一位表示文件的类型(d:目录,-:一般文件,l:链接,c:字符设备,b:块设备,最后在/dev目录中很常见,是表示设备的)。其后有九位,三位一组,第一组(2-4位)是说明文件所有者的权限的,第二组(5-7)位是说明与文件所有者同group其他用户对这个文件的权限,第三组(8-10)是所有人对这个文件的权限。每组的第一位是r表示读,第二位是w表示写,第三位是x表示可执行,如果某一位上是-说明就没有该位的相应权限。比如说/bin与root同group的人就没有写的权限因为它的是r-x,中间本应是w的那一位是-。
我们说的在详细点比如一个文件有这样的一个权限
rwxr-xr--

我们把它先分组
 
 rwx        r-x         r--

所有者    同group者   其他人

这样我们就可以清楚地看见谁有什么样的权限了。接着我们把有权限的为写成1,-位写成0的话会变成
111     101    100

这种形式,我们把它们化成十进数就变成了
7       5      4

所以我们也可以说这个文件的权限是754了。这回你应该明白为什么有时候说某个文件的权限是777,444,654等等了吧。下回看见了别再说它是777老虎机了。

好,文件系统我们就讲到这。其实文件系统的知识很多也很杂,我这里只是介绍了一下文件系统的4个block,和它们的作用,而且只是皮毛的皮毛。所以关于文件系统的知识希望大家还是多多参看其他的一些资料。
下一章,我们说说标准输入输出以及管道,在那里还将补充说明一些关于文件描述符和读写文件的一些知识,希望大家继续支持我!


 la.lune 回复于:2006-11-13 16:31:24

温故知新 
赞一个~


 susesuse 回复于:2006-11-14 09:12:28

接着来顶,呵呵。


 sunwindy 回复于:2006-11-16 00:21:18

up


 ehuccc 回复于:2006-11-16 00:40:37

安装Red Flat Linux 像蜗牛一样.

正好看LZ的教程....

不错..收藏了.


 ehuccc 回复于:2006-11-16 00:47:41

我想问一下各位大哥大姐:

Windows 2000 下通过虚拟机(VM Ware )安装的Red flat linux 5.0

与非虚拟机安装的Linux有什么不同呢?


我想学习在linux下的C语言.....



先谢过了.LZ能否给答一下啊?


 zw2002 回复于:2006-11-16 10:24:06


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#define MASK 0555 //设置掩码
int main( int argc , char *argv[] )
{
        struct stat buf[1];
        mode_t mode;
        if( argc!=2 )
        {
                fprintf( stderr , "Usage : %s file\n" , argv[0] );
                exit(1);
        }
        if( stat(argv[1],buf)!=0 )
        {
                fprintf( stderr , "Cannot read i-node\n" );
                exit(1);
        }
        mode = ( buf[0].st_mode & MASK );     
        if ( chmod(argv[1],mode)!=0 )         //改变文件的权限
        {
                fprintf( stderr , "Cannot change mode\n" );
        }
        return 0;
}



Segmentation fault
不知道为什么?


 semiwinter 回复于:2006-11-17 11:11:11

引用:原帖由 ehuccc 于 2006-11-16 00:47 发表
我想问一下各位大哥大姐:

Windows 2000 下通过虚拟机(VM Ware )安装的Red flat linux 5.0

与非虚拟机安装的Linux有什么不同呢?


我想学习在linux下的C语言.....



先谢过了.LZ能否给答一下啊? 



虚拟机下安装的linux必须先启动windows之后才能应用
独立安装的linux不用启动windows就能启动


 ngzyl 回复于:2006-11-17 12:02:26

顶,,,,


 dglwx 回复于:2006-11-17 12:24:46

千年难遇的强帖啊! 顶起来!
    我对楼主的敬仰有如... ...,又如... ... !


 ehuccc 回复于:2006-11-17 15:41:55

semiwinter 谢过了...

两个 ISO文件 直接 COPY到 CD 盘上了.

无法引导....

     这可愁死本座.:em14:

那位帮我讲一下.......

郁闷ING

[ 本帖最后由 ehuccc 于 2006-11-17 15:45 编辑 ]


 ngzyl 回复于:2006-11-18 09:52:04

引用:原帖由 ehuccc 于 2006-11-17 15:41 发表
semiwinter 谢过了...

两个 ISO文件 直接 COPY到 CD 盘上了.

无法引导....

     这可愁死本座.:em14:

那位帮我讲一下.......

郁闷ING 




...................... 直接COPY肯定不能引导咯..................................


 zhangbo1882 回复于:2006-11-19 15:33:52

不知道你是用的什么系统,我在RED HAT 7。3下运行没问题啊?你可以把你执行程序的结果帖出来吗??


 八月初三 回复于:2006-11-19 23:49:51

引用:原帖由 ehuccc 于 2006-11-17 15:41 发表
semiwinter 谢过了...

两个 ISO文件 直接 COPY到 CD 盘上了.

无法引导....

     这可愁死本座.:em14:

那位帮我讲一下.......

郁闷ING 


尊驾真乃强人!
ISO可以通过刻录软件刻录到光盘上,注意,要选择镜像-》CD


 mu_mu8309 回复于:2006-11-20 13:10:14

l楼主讲的是很好啊,就是看起来有点费劲,要是整理成文档的格式贴出来对我们这些初学者来说简直是太好了啊.

[ 本帖最后由 mu_mu8309 于 2006-11-20 13:13 编辑 ]


 nlsycb 回复于:2006-11-23 23:31:09

怎么没有了,继续阿lz!


 robin10 回复于:2006-11-24 17:31:10

所以我们也可以自豪地说:有什么的啊,我们的自行车不就是比宝马少俩轮吗?在轮子都是圆的这方面,原理是和宝马一样的。呵呵,想宝马想疯了。
:)


 robin10 回复于:2006-11-24 18:02:58

所以我们也可以说这个文件的权限是754了。这回你应该明白为什么有时候说某个文件的权限是777,444,654等等了吧。下回看见了别再说它是777老虎机了。
hehe~~~不错~~~内容很好,说的也风趣`~谢谢~~


 onzka 回复于:2006-12-06 00:12:27

谢谢楼主,收藏先,慢慢看


 linux__@@@@@ 回复于:2006-12-06 11:06:07

8错....:mrgreen:


 yxinjian 回复于:2006-12-06 16:25:05

好帖,非常适合我们初学者!谢谢了!


 bagol 回复于:2006-12-06 19:48:37

好贴要顶起,慢慢来消化。


 一切为你! 回复于:2006-12-06 23:34:26

学习一下~


 weilc 回复于:2006-12-07 06:24:23

收藏


 etile 回复于:2006-12-09 16:54:38

正准备学APUE,谢谢楼主。希望早点骑上宝马


 源方 回复于:2006-12-11 15:56:29

引用:原帖由 longshort 于 2006-11-9 10:04 发表

俺不是高手,不过俺多少知道一点fork后用exit的结果,俺也谈谈感想给大家作个参考。

一般情况下,只要子进程把该交代的都交代了,用exit退出就是安全的。原因就是子进程在一个独立的空间中运行,所有的指针和 ... 


不错


 回族四少 回复于:2006-12-11 18:51:52

楼主写的东西相当警辟啊,而且还很有趣,楼主不要停啊哈哈,对于象我这样的菜鸟真的很喜欢阅读这样的东西,这样让我们觉得在unix下变成比再windows下编程更加有趣哈哈,不过我相当菜啊,因为我都不知道在unix下该如何编译楼主的程序,烦请楼主写一下如何编译的过程,谢谢了,俺是超级菜鸟,表要仍砖啊,否则打击新人的学习积极性啊,比我们学校的教授讲得有趣味多了,建议楼主出书.嘿嘿

哈哈顺便说一下俺79年出生啊,回复的楼层也是79,HOHO.:em02::em17:
强烈建议斑斑将此帖置顶,大家跟着教教象我这样的超级菜鸟啊,谢谢啊...:em03:

[ 本帖最后由 回族四少 于 2006-12-11 18:54 编辑 ]


 山_兽 回复于:2006-12-11 21:33:39

呵呵~~~好贴!!

现在大家都忙着怎么改善生活,忘了我们还可以做一些有意义的事情!

楼主激起了俺的热情啊,可以把自己知道的东西拿出来给大家分享下!

感谢!!!


 可可火山 回复于:2006-12-13 14:22:36

顶下 这书以前看过 理解得还不够深刻 复习下


 MackedNice 回复于:2006-12-13 15:26:09

引用:原帖由 aitongqi017 于 2006-11-7 19:40 发表
我想问楼主一个问题,是不是每一个创建的子进程都必须在使用完,由父进程调用wait函数来的到该进程的终止状态,释放资源。
是不是必须的? 


不是必须的。

如果你的程序不是一个守护程序或者父进程不需要获取子进程的终止状态。
你在父进程是完全没有必要必须wait子进程,来给子进程来收尸(释放资源)
这个工作可以交由由init进程来处理。


#include<stdio.h>
#include<unistd.h>
int main()
{
    int st;
    if( fork()==0 )
    {
        printf("children %d parent %d run..\n", getpid(), getppid());
        sleep(10);
        printf("children %d parent %d run..\n", getpid(), getppid());
        exit(1);        //子进程结束
    }
    else
    {
        sleep(5);
        printf("Parent %d run finished..\n", getpid());
        exit(1);
    }
    return 0;
}


子进程的父进程如果先于子进程结束的话,那么该父进程的所有子进程将成为孤儿进程,将由init进程领养。
该父进程的所有子进程的父进程将变为init进程。子进程结束时由init进程为它们收尸(释放资源)。
所以不是必须的。


 etile 回复于:2006-12-19 20:46:59

继续期待


 cjaizss 回复于:2006-12-19 21:06:43

顶楼主


 stephen412 回复于:2006-12-20 11:43:05

Still paying attention on ......


 zglcl008 回复于:2006-12-20 16:33:04

引用:原帖由 aitongqi017 于 2006-11-7 19:40 发表
我想问楼主一个问题,是不是每一个创建的子进程都必须在使用完,由父进程调用wait函数来的到该进程的终止状态,释放资源。
是不是必须的? 


这个倒是不是必需的,
可以用信号来控制。。。。。。

很早以前相关的资料书籍也少,
网络也没有搞这个的人也少,
现在交流方便多了。。。。。。


 zgqmm 回复于:2006-12-23 13:02:59

楼主继续,跟贴,顶上去


 bombzhao 回复于:2006-12-23 14:16:19

mark一下,慢慢看


 batfic887 回复于:2006-12-26 22:37:15

支持楼主,我们刚好要考试了,当作复习资料.
希望楼主继续发!!!!^0^


 lisf 回复于:2006-12-27 13:55:04

很长时间没来,太精采了.
顶!!


 小强泡泡 回复于:2007-01-19 11:28:16

先感谢一下楼主,然后我在慢慢看,不过我觉得,楼主有blog否,每一个主题或者每一章为一篇blog,然后再把这些blog组成系统性的知识,让后来的人或者已经读过的人更加理得顺一些,像这样回贴和主题贴混杂在一起,有点乱了.呵呵


 yinheng8066 回复于:2007-02-14 00:23:06

不顶对不起楼主,我不敢潜水了~~~~~~~~~`


 yxm0513 回复于:2007-03-29 23:48:55

继续期待中。。。。


 japleak 回复于:2007-03-30 13:20:00

我也继续等待......谢谢


 x.jc 回复于:2007-03-30 17:26:41

不错啊,顶起来!


 googoolth 回复于:2007-03-30 17:51:36

谢谢搂主分享!


 bobozhang 回复于:2007-04-03 12:36:02

像搂主这样的中国人太少了


 zhongf1114 回复于:2007-04-03 14:40:27

真是千古一贴啊!!!


 yuxiaqiao 回复于:2007-04-04 09:30:34

很长时间没来,太精采了.
顶!!


 zylinux 回复于:2007-05-03 18:51:23

再学习


 64812328 回复于:2007-06-13 13:15:05

望lz继续呀


 huachong 回复于:2007-06-13 19:54:42

很精彩,喜欢的要命!
希望楼主继续
BTW:你可以出书了。。。


 aobai 回复于:2007-06-14 09:19:02

我什么时候能够达到这个水平就好了。


 微雨骑驴入剑门 回复于:2007-06-14 09:52:16

收藏了,继续啊


 wangchacha 回复于:2007-06-14 14:06:04

好人啊~


 zkheartboy 回复于:2007-07-02 01:13:26

潜了  好久,不得不冒出来顶了




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=851919
转载请注明作者名及原文出处



收藏本页到: