Unix环境高级编程——第一章-UNIX基础知识

1.2 UNIX体系结构

1、体系结构:
(1)内核 (2)系统调用 (3)Shell、公共函数库 (4)应用程序


1.4 文件和目录

1、例:ls(l)命令的简要实现

#include "apue.h"
#include 
int main(int argc,char * argv[])
{
    DIR *dp;
    struct dirent *dirp;
    if(argc!=2)
        err_quit("Usages: ls directory_name");
    
    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);
}

(1)系统头文件dirent.h,以便使用opendir和readdir的函数原型,以及dirent结构的定义。
(2)opendir()函数返回指向DIR结构的指针,我们将该指针传送给readdir来读每个目录项。当目录已无目录项可读时则返回null指针。
(3)函数exit终止程序,参数0为正常结束,参数值1~255表示出错


1.5 输入和输出

1、文件描述符(file descriptor):通常是一个小的非负整数,内核用以标识特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,它都返回一个文件描述符,在读、写文件时,可以使用这个文件描述符。

2、每当运行一个新程序时,所有的shell都为其打开3个文件描述符,即标准输入(standard input)、标准输出(standard output)以及标准错误(standard error)。

3、不带缓冲的I/O
(1)函数open、read、write、lseek以及close提供了不带缓冲I/O。这些函数都使用文件描述符。

4、如果愿意从标准输入中读,并向标准输出写,则下列程序可用于复制任一UNIX普通文件。

//查看STDIN_FILENO和STDOUT_FILENO值
#define  STDIN_FILENO   0       
/* standard input file descriptor */
#define STDOUT_FILENO   1       
/* standard output file descriptor */
#define STDERR_FILENO   2       
/* standard error file descriptor */


#include "apue.h"

#define BUFFSIZE 4096

int main(void)
{
    int n;
    char buf[BUFFISZE];
    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);
}

(1)头文件unistd.h(apue.h中包含了此头文件)及两个常量STDIN_FILENO和STDOUT_FILENO是POSIX标准的一部分。头文件unistd.h包含了很多UNIX系统服务的函数原型,包括read和write。
(2)两个常量STDIN_FILENO和STDOUT_FILENO定义unistd.h头文件中,它们指定了标准输入和标准输出的文件描述符。在POSIX标准中,它们的值分别是0和1,但是考虑到可读性,我们将使用这些名字代表这些变量。
(3)read函数返回读取字节数,此之用作要写的字节数。当到达输入文件的尾端时,read返回0,程序停止执行。如果发生了一个读错误,read返回-1.出错时大多数系统函数返回-1。

5、标准I/O
(1)标准I/O库函数为那些不带缓冲的I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。
(2)使用标准I/O函数还简化了对输入行的处理,如fgets函数读取一个完整的行,而read函数读取指定字节数。

(2)例:将标准输入复制到标准输出,也就是能复制任一UNIX普通文件。

#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);
}

1.6 程序和进程

1、程序
(1)程序(program)是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程序。

2、进程和进程ID
(1)程序执行的实例被成为进程(process)/任务(task)。

(2)UNIX系统确保每个进程都有一个唯一的数字标识符,将进程ID(process ID)。进程ID总是一个非负整数。

例:用于打印进程的ID。

#include "apue.h"

int main(void)
{
    printf("hello world from process ID %ld\n",
    (long)getpid());
    exit(0);
}

//使用man 2 getpid()查看getpid()函数原型

(1)此程序运行时,它调用函数getpid得到其进程ID。
(2)getpid返回一个pid_t数据类型。我们不知道它的大小,仅知道是标准会保证它在一个长整型中。
(3)虽然大多数进程ID可以用整型表示,但用长整型可以提高移植性。

3、进程控制
(1)有3个用于进程控制的主要函数:fork、exec和waitpid。(exec函数有7种变体,但经常把它们统称为exec函数。)

(2)例:该程序从标准输入读取命令,然后执行这些命令。

#include "apue.h"
#include 

int main(void)
{
    char buf[MAXLINE];       /* from apue.h */
    pid_t pid;
    int status;
    printf("%% ");            
/* print prompt (printf requires %% to print %) */
    while(fgets(buf,MAXLINE,stdin)!=NULL){
        if(buf[strlen(buf)-1]=='\n')
            buf[strlen(buf)-1]=0;  
        /* replace newline with null */
        if ((pid==fork())<0){
            err_sys("fork error");
        }else if (pid==0) {  /* child */
            execlp(buf,buf,(char *)0);
            exec_ret("could't execute: %s",buf);
        }

        /* parent */
        if((pid=waitpid(pid,&status,0))<0)
            err)sys("waitpid error");
        printf("%% ");
    }
    exit(0);
    
}

1.调用fork创建一个新进程。fork对父进程返回一个新的子进程的进程ID(一个非负整数),对子进程则返回0。
2.在子进程中,调用execlp以执行从标准输入读入的命令。
3.子进程调用execlp执行新程序文件,而父进程希望等待子进程终止,这是通过调用waitpid实现的。
4.waitpid函数返回子进程的终止状态(status变量)。

4、线程和线程ID
(1)通常,一个进程只有一个控制线程(thread)——某一时刻执行的一组机器指令。
(2)一个进程内的所有线程共享同一地址空间、文件描述符、栈以及进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据需要采取同步措施以避免不一致性。

1.7 出错处理

1、当UNIX系统函数出错时候,通常会返回一个负值,而整型变量errno通常被设置为具有特定信息的值。

2、头文件errno.h中定义了errno以及可以赋予它的各种常量。

3、对于errno应当注意两条规则
(1)如果没有出错,其值不会被例程清楚。因此,仅当函数的返回之指明出错时,才检验其值。
(2)任何函数都不会将errno值设为0而且在errno.h中定义的所有常量都不为0。

4、C标准定义了两个函数,用于打印出错信息:

#include 
char * strerror(int errnum);
//strerror函数将errnum(通常为errno值)映射为一个出错信息字符串,并返回此字符串的指针。

#include 
void perror(const char *msg);
//perror函数基于errno的当前值,在标准错误上产生一条出错信息,并返回。首先输出由msg指向的字符串,然后是一个:,一个空格,接着对应errno值的出错信息,最后是一个换行符。

5、例:程序显示两个出错函数的使用方法

#include "apue.h"
#include 

int main(int argc,char * argv[])
{
    fprintf(stderr,"EACCES: %s\n",stderror(EACCES));
    errno=ENOENT;
    perror(argv[0]);
    exit(0);
}

//man errno查看错误的类型

6、出错恢复
(1)致命性错误:无法执行恢复动作,最多就打印出一条错误信息或将错误信息写进日志文件。
(2)非致命性错误可妥善进行处理。

1.8 用户标识

1、例:用于打印用户ID组ID

#include "apue.h"

int main(void)
{
    printf("uid = %d, gid = %d\n",getuid(),getgid());
    exit(0);
}

//man getuid 查看getuid函数原型
//man getgid 查看getgid函数原型

1.9 信号

1、信号(signal)用于通知进程发生了某种情况。

2、进程有以下3种处理信号的方式:
(1)忽略信号
(2)按系统默认方式处理
(3)提供一个函数,信号发生时调用该函数,这被成为捕捉该信号

3、终端上的键盘有两重产生信号的方法,分别成为中断键(interrupt key,通常是Delete键或Ctrl+C)和退出键(quit key,通常是Ctrl+),它们被用于中断当前进程。

4、另一种产生信号的方法是调用kill函数。在一个进程中调用此函数就可向另一个进程发送信号。

5、限制:当向一个进程发送信号时,我们必须是进程的user或者root用户。

6、例:为了捕捉SIGINT信号,程序需要调用signal函数,其中指定了当产生SIGINT信号时要调用的函数的名字,函数名为sig_int。

#include "apue.h"
#include 

static void sig_int(int); /* out signal-caching functions */
int main(void)
{
    char buf[MAXLINE];   /* from apue.h */
    pid_t pid;
    int status;
    
    if(signal(SIGINT,sig_int)==SIG_ERR)
        err_sys("signal error");
    
    printf("%% "); /* format print */ 
    while(fgets(buf,MAXLINE,stdin)!=NULL){
        if(buf[strlen(buf)-1]=='\n')
            buf[strlen(buf)-1]=0;
        /* replace newline with null */
        
        if((pid=fork())<0){
            err_sys("fork error");
        }else if(pid==0){ /* child */
            execlp(buf,buf,(char *)0);
            err_ret("couldn't execute: %s",buf);
            exit(127);
        }
        
        /* parent */
        if((pid=waitpid(pid,&status,0))<0)
            err_sys("waitpid error");
        printf("%% ");
        
    }
    exit(0);
}

void sig_int(int signo)
{
    printf("interupt\n%% ");
}

1.10 时间值

1、UNIX系统使用过两种不同时间值
(1)日历时间,该值是子协调世界时(Coordinated Universal Time,UTC)1970年1月1日 00:00:00这个特定时间以来所经过的秒数累计值。
(系统基本数据类型time_t用于保存这种时间值)
(2)进程时间。也被称为CPU时间,以度量进程使用中央处理器资源。进程时间以始终滴答计算。
(系统基本数据类型clock_t)用于保存这种时间值

2、UNIX系统为一个进程维护了3个进程时间值:
(1)时钟时间
(2)用户CPU时间
(3)系统CPU时间

3、时钟时间又成为墙上时钟时间(wall clock time),它是进程运行的时间总量,其值与系统中同时运行的进程数有关。

4、用户CPU时间是指执行用户指令所用的时间量。

5、系统CPU时间是该进程执行内核程序所经历的时间

6、用户CPU时间和系统CPU时间之和被成为CPU时间

1.11 系统调用和库函数

1、所有的操作系统都提供多种服务的入口,这些入口被称为系统调用(system call)。

2、系统调用和库函数都是以C函数的形式出现,两者都为应用程序提供服务。

3、我们可以替换库函数,但系统调用通常是不能被替换的。

4、以malloc库函数为例,UNIX系统调用处理存储分配的是sbrk(2),sbrk在内核中分配一块空间给进程,而库函数malloc则在用户层次管理这一空间。

5、系统调用和库函数之间的另一个差别:系统调用通常提供最小的接口,而库函数提供比较复杂的功能。

你可能感兴趣的:(c,unix,apue)