<<UNIX环境高级编程>>之第一章理解

1.登录
登录名:系统可在口令文件中查看登录名,通常在/etc/passwd;
口令文件中的登录项由7个以冒号分隔的字段组成:登录名,加密口令,数字用户ID,数字组ID,注释字段,其实目录,以及shell程序;
如下是我口令文件的登录项:
yichen:x:1000:1000:yichen,,,:/home/yichen:/bin/bash
密码不可见,数字ID,和数字组ID在以后会提到.(注释字段不知为何物)
2.shell
我所用UBUNTU bash是shell中的一种.
shell是一个命令行解释器,它读取用户输入,然后执行命令,用户通常用终端,有时通过文件(SHELL脚本)向shell进行输入.
3.文件和目录
unix文件系统目录起点为根(root),表示为字符/。
目录(directory):是一个包含目录项的文件,逻辑上认为每个目录项都包含一个文件名和该文件属性的信息。
文件属性:文件类型,文件长度,文件所有者,文件的许可权(例如,其他用户能否访问该文件),文件的最后修改时间等。
查询文件属性:stat filename 或 fstat 系统描述符(系统描述符后面有讲,stat也可后跟绝对路径)
以下为;stat filename
文件:’apue.3e’
大小:4096 块:8 IO 块:4096 目录
设备:802h/2050d Inode:10485795 硬链接:27
权限:(0755/drwxr-xr-x) Uid:( 1000/ yichen) Gid:( 1000/ yichen)
最近访问:2017-01-11 22:22:02.397532962 +0800
最近更改:2017-01-11 21:51:18.284918487 +0800
最近改动:2017-01-11 21:51:18.284918487 +0800
创建时间:-
文件名:目录中的各个名字称为文件名,不能在文件名中出现的字符有两个(/和null);
当创建新目录时,自动创建两个文件名:.和.. 点引用当前目录,点点引用父目录.在最高层次目录,两者相同.
路径名: 0个或多个以斜线分隔的文件名序列.以斜线开头的路径名称为绝对路径名,否则称为相对路径名.
实例:不难列出一个目录中所有文件:
实现ls(1)命令:

#include <sys/types.h>
#include <dirent.h>
#include "apue.h"
int main(int argc,char * argv[])
{
    DIR  *dp;
    struct dirent *dirp;
    if(argc != 2)
       err_quit("a single argument (the directory name) is required");
    if( (dp = opendir(argv[1]) ) == NULL)
       err_sys("can't open %s",argv[1]);
    while( (dirp = readdir(dp)) != NULL)
       printf("%s\n",dirp->d_name);
    closedir(dp);
    exit(0);
}

运行gcc 1-1.c -o 1-1
./1-1 /dev
表示打开dev文件,并输出每个文件名,但注意源代码,并没有要求按字母序输出.
首先:argc(argument counter)表示命令中所有字符串的数目,argv(argument vector)是由这些字符组成的数组;
以上命令:./1-1 /dev argc = 2,argv[0] = “./1-1”,argc[1] = “/dev”;
我们仅需man一下opendir和readdir介绍如下:
The opendir() function opens a directory stream corresponding to the directory name, and returns a pointer to the directory stream. The stream is positioned at the first entry in the directory.
The readdir() function returns a pointer to a dirent structure repre‐senting the next directory entry in the directory stream pointed to by dirp. It returns NULL on reaching the end of the directory stream or if an error occurred.
man后我们可知函数返回值是什么,需要的参数是什么,发生错误返回什么值,这样代码就很好理解了.
然后错误处理会在1.7之后详解:
tips:exit(0),以0退出表示正常结束,以1~255退出表示花式错误结束.
综上:此程序实现功能,打开一个文件,并将其内的文件的文件名全部输出,如何输出我们还可以输出系统一开始给每个文件建好的文件.和.. ,opendir打开文件,并返回文件内部directory stream entry,打开失败返回NULL,readdir开始从entry读,一个一个读入,返回一个结构体dirent,结构体dirent中有char d_name[]来存访各文件名,读到directory stream尾部返回null;

工作目录:每个进程都有一个工作目录(working directory,有时称为当前工作目录(current working directory)).
所有先对路径名都从工作目录开始解释.进程可以用chdir函数更改其工作目录.
例如:doc/memo/joe 这是相对路径名,doc则为”工作目录中的一个目录项”.
/usr/lib/lint 这是绝对路径名,仅指一个目录关系,且并不知lint是文件还是目录
起始目录:工作目录设置为起始目录(home directory),该起始目录从口令文件中的登录项中获取.
tips:口令文件查询见1.2节.
4.输入和输出
文件描述符:文件描述符是一个小的非负整数,内核用以标识一个特定进程正在存访的文件.当内核打开一个现存文件或创建一个新文件时,它就返回一个文件描述符.当读或写文件时,我们就可以使用它.
标准输入,标准输出和标准出错:当运行新程序时,所有的shell都为其打开三个文件描述符,如果像简单命令ls那样没有做什么特殊处理,则这三个描述符都连向终端.大多数shell都提供一种方法,使任何一个或所有这三个描述符重新定向到文件.
例如: ls > file.list 表示"执行"ls命令,其标准输出重新定向到名为file.list的文件上.
以下字体变大:
不用缓存的I/O:函数open,read,write,lseek以及close提供了不用缓存的I/O.这些函数都用文件描述符进行工作.
实例:如果愿意从标准输入读,并写向标准输出,则下列程序可用于复制任一UNIX文件.

#include "apue.h"
#define BUFFSIZE 8192
int main(void)
{
    int n;
    char buf[BUFFSIZE];
    while( (n = read(STDIN_FILENO,buf,BUFFSIZE)) > 0)
        if(write(STDOUT_FILENO,buf,n) != n)
                err_sys("write error");
    if(n < 0)
        err_sys("read error");
    exit(0);
}

程序理解:buffsize和buf很好理解,buffsize的设定肯定会影响程序效率,然后我们来看看这文件描述符,详细文件描述符见以上.我们可以将其中任一一个文件描述符重定义到某个文件:比如ls > file.list表示把ls的输出重定义向到file.list的输出.而STDIN_FILENO是一个非负整数,是内核标记某进程正在访问的文件,其为标准输入(一般是键盘)我们将其重新定位到一个新的文件,对它进行读写,读到BUF中,再重BUF中写出来,我们就可以利用这个原理复制UNIX文件.
标准I/O:标准I/O函数提供一种对不用缓存的I/O函数的带缓存的界面.使用标准I/O可无需担心如何选取最佳的缓存长度.
实例:

#include "apue.h"#include "apue.h"
int main(void)
{
    int c;
    while( (c = getc(stdin)) != EOF)
          if(putc(c,stdout) == EOF)
                err_sys("output error");
    if(ferror(stdin))
        err_sys("input error");
    exit(0);
}

int main(void)
{
    int c;
    while( (c = getc(stdin)) != EOF)
          if(putc(c,stdout) == EOF)
                err_sys("output error");
    if(ferror(stdin))
        err_sys("input error");
    exit(0);
}

这就是标准输入与输出,因为一个一个读所以不会考虑缓冲器大小与上面程序相似.
5.程序与进程:
程序:是存放在磁盘文件中的可执行文件.使用6个exec函数中的一个由内核将程序读入存储器,并使其执行. 
进程:程序的执行实例.
进程ID:每个unix进程都一定有一个唯一的数字标识符,称为进程ID,进程ID总是非负整数.

#include "apue.h"
int main(void)
{
        printf("hello world from process ID %d\n",getpid());
        exit(0);
}
编译后运行两次,会发现不同的进程ID,差值为1,验证以上进程ID描述.
进程控制:有三个用于进程控制的主要函数:fork,exec和waitpid(exec函数有六种变体,但经常把它们统称为exec函数).
实例:

#include <sys/types.h>
#include <sys/wait.h>
#include "apue.h"
int main(void)
{
        char buf[MAXLINE];
        pid_t pid;
        int status;
        printf("%% ");
        while(fgets(buf,MAXLINE,stdin) != NULL){
                buf[strlen(buf) - 1] = 0;

                if( (pid = fork()) < 0)
                        err_sys("fork error");
                else if(pid == 0){
                        execlp(buf,buf,(char *)0);
                        err_ret("couldn't execute: %s",buf);
                        exit(127);
                }
                if( (pid = waitpid(pid,&status,0)) < 0)
                        err_sys("waitpid error");
                printf("%% ");
        }
        exit(0);
}

此实例需要了解的较多:1.打印'%'的打法.2.fgets,这个直接man看一下即可.3.fork(),execlp,waitpid,这三个函数怎么运行,以及进程的替换,为什么没有执行err_ret语句,都在一下博客可见:http://www.cnblogs.com/mickole/p/3187409.html.4.waitpid:紫禁城调用execllp执行新程序文件的时候,父进程等待子进程终止,调用waitpid实现,waitpid函数返回子进程的终止状态变量.以上程序没有使用这个值,如果使用我们可以精确的确定子进程是如何终止的.
tips:限制,不能执行命令的传递参数.(传递参数,需要分析输入行,而我们的输入行直接用作exec等函数的参数)(自己理解,欢迎交流).
6.ANSI C
函数原型:头文件<unistd.h>包含了许多unix系统服务的函数原型,read,write,getpid.(可以man一下看一下它们的形式).
类属指针:看了,没理解.****标记下
7.出错处理
当unix函数出错时,通常根据函数返回值类型返回一个负值或者一个NULL指针.
errno:文件<errno.h>中定义了变量errno以及可以赋与它的各种常数.定义为:extern int errno;满足两条规则,1,如果没有出错,值不会被一个例程清除,仅当函数的返回值指明出错时,惨检验其值.2,任一函数都不会将errno值设置为0,在<errno.h>中定义的所有常数都不为0;(0,表示正常退出,即函数正常结束);
例:

#include <errno.h>
#include "apue.h"
int main(int argc,char *argv[])
{
        fprintf(stderr,"EACCES: %s\n",strerror(EACCES));
        errno = ENOENT;
        perror(argv[0]);
        exit(0);
}
执行后:./1-6
EACCES: Permission denied
./1-6: No such file or directory

理解:char *strerror(int errno),将errno映射为一个字符串,并返回;
void perror(const char*msg),先输出由msg指向的字符串,然后冒号,空格,然后对应errno值出错信息,换行符.
8.用户标识
用户ID:以上口令文件中讲过,它用于向系统标识各个不同用户.每个用户ID唯一.ID为0是根或超级用户,口令文件中,通常有一个登录项,登录名为root,这种用户特权称为超级用户特权.
tips:一个进程具有超级用户特权,则大多数文件许可权检查都不再进行.某些操作系统功能仅向超级用户提供,超级用户对系统有自由的支配权.
例:

#include "apue.h"
int main(void)
{
        printf("uid = %d,gid = %d\n",getuid(),getgid());
        exit(0);
}
结果:
uid = 1000,gid = 1000

这就是用户ID和组ID在口令文件都可见.
组ID:组ID由系统管理员在确定用户登录名时分配.一般口令文件中多个记录项具有相同的组ID,这种机制允许同组的各个成员之间共享资源(比如文件).组文件将组名映射为数字组ID,通常在/etc/group
tips:口令文件包含登录名与用户ID的映射关系,组文件包含组名与组ID的映射关系.
9.信号:信号是通知进程已发生某种条件的一种技术.(学过QT,感觉和QT信号与槽的关系类似)
1.忽略该信号;硬件异常,0除或访问进程地址空间以外的单元(理解为越界)
2.按系统默认方式处理;0除的系统默认方式是终止它;
3.提供一个函数,信号发生时调用该函数.使用这种方式,我们将能知道什么时候产生了信号,并按所希望的方式处理它.
产生信号条件:很多,比如中断键delete,ctrl-c和退出键ctrl-\,被用于终端当前运行进程.
还有调用kill函数.
限制:当向一个进程发送信号时,我们必须是该进程的所有者.
例:1-8

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include "apue.h"
static void sig_int(int);
int main(void)
{
        char buf[MAXLINE];
        pid_t pid;
        int status;
        if(signal(SIGINT,sig_int) == SIG_ERR)
                err_sys("signal error");
        printf("%% ");
        while(fgets(buf,MAXLINE,stdin) != NULL){
                buf[strlen(buf) - 1] = 0;
                if( (pid = fork()) < 0)
                        err_sys("fork error");
                else if(pid == 0){
                        execlp(buf,buf,(char*)0);
                        err_ret("couldn't execute: %s",buf);
                        exit(127);
                }
                if( (pid = waitpid(pid,&status,0)) < 0)
                err_sys("waitpid error");
                printf("%% ");
        }
        exit(0);
}
void sig_int(int signo)
{
    printf("interrupt\n%% ");
}

理解:SIG_INT是中断信号,运行程序,声明信号,信号参数是SIG_INT,会捕捉中断信号,当我们按下CTRL + C,捕捉到后执行它的槽函数,也就是信号与槽的关系(信号与槽是QT的说法).
建议阅读:http://www.tutorialspoint.com/c_standard_library/c_function_signal.htm
10.unix时间值
日历时间:UTC(国际标准时间)起始时间:1970年1月1日00:00:00;
进程时间:也被称为CPU时间,用以度量进程使用的中央处理机资源.unix系统使用三个进程时间值:1.时钟时间2.用户cpu时间3.系统cpu时间.
时钟时间:时钟时间又被称为墙上时钟时间(wall clock time).它是进程运行的时间总量,其值与系统中同时运行的进程数有关.在我们报告时钟时间时,系统中没有其他活动执行.
用户CPU时间:是执行用户指令所用的时间量.
系统CPU时间:是为该进程执行内核所经历的时间. 例:执行read函数,这个函数所话费时间计入该进程的系统CPU时间.
CPU时间:用户CPU时间和系统cpu时间的和;
tips:time命令可直接获取任一进程的时钟时间,用户时间,系统时间.
例:
cd /usr/include
time grep _POSIX_SOURCE /.h > /dev/null
结果如下:
real 0m19.81s
user 0m0.43s
sys 0m4.53s
理解:不理解,顺带man了一下grep函数,是用来通过匹配字符串来查询文件的函数.
11.系统调用和库函数:理解即可,看书上图就可理解,这里我就不贴图了.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值