《unix环境高级编程》--- 进程间通信

管道
1、半双工,即数据只能在一个方向上流动。
2、只能在具有公共祖先的进程间使用。

下图显示父进程关闭读端(fd[0]),子进程关闭写端(fd[1])后的管道流向。
这里写图片描述
创建一个从父进程到子进程的管道,由父进程向子进程发送数据。

#include "apue.h"

int main(void)
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];

    /*
       int pipe(int filedes[2]);
       filedes[0]: 为读而打开
       filedes[1]: 为写而打开
       filedes[1]的输出是filedes[0]的输入
       如果父进程关闭filedes[0],子进程关闭了filedes[1],
       则创建了从父进程到子进程的管道
    */
    if(pipe(fd) < 0)
        err_sys("pipe error");
    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid > 0)
    {
        close(fd[0]);
        write(fd[1], "hello world\n", 12);
    }
    else
    {
        close(fd[1]);
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }
    exit(0);
}

这里写图片描述

将文件复制到分页程序
功能是每次一页显示已产生的输出。思路:将输出通过管道直接送到分页程序。
先创建一个管道,然后父进程关闭读端,将文件中的内容写入写端。子进程关闭写端,调用dup2,使其标准输入成为管道的读端,然后调用exec,执行用户喜爱的分页程序。当执行分页程序时,标准输入是管道的读端。

#include "apue.h"
#include <sys/wait.h>

#define DEF_PAGER "/bin/more"  /* default pager program */

int main(int argc, char *argv[])
{
    int n;
    int fd[2];
    pid_t pid;

    char *pager, *argv0;
    char line[MAXLINE];
    FILE *fp;

    if(argc != 2)
        err_quit("usage: a.out <pathname>");

    if((fp = fopen(argv[1], "r")) == NULL)
        err_sys("can't open %s", argv[1]);
    if(pipe(fd) < 0)
        err_sys("pipe error");

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid > 0)
    {
        close(fd[0]);  /* close read end */

        /* parent copies argv[1] to pipe */
        while(fgets(line, MAXLINE, fp) != NULL)
        {
            n = strlen(line);
            if(write(fd[1], line, n) != n)
                err_sys("write error to pipe");
        }
        if(ferror(fp))
            err_sys("fgets error");

        close(fd[1]);  /* close write end of pipe for reader */

        if(waitpid(pid, NULL, 0) < 0)
            err_sys("waitpid error");
        exit(0);
    }
    else
    {
        close(fd[1]);  /* close write end */
        if(fd[0] != STDIN_FILENO)
        {
            if(dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) /* 对输入文件描述符进行重定向 */
                err_sys("dup2 error to stdin");
            close(fd[0]);  /* don't need this after dup2 */
        }

        /* get arguments for execl() */
        if((pager= getenv("PAGER")) == NULL)
            pager = DEF_PAGER;
        if((argv0 = strchr(pager, '/')) != NULL)
            argv0++;  /* step past rightmost slash */
        else
            argv0 = pager;  /* no slash in pager */

        if(execl(pager, argv0, (char *)0) < 0)
            err_sys("execl error for %s", pager);
    }
    exit(0);
}

这里写图片描述

利用管道使父子进程同步

在fork之前创建两个管道。父进程在调用TELL_CHILD时,写一个字符”p”至上一个管道,子进程在调用TELL_PARENT时,经由下一个管道写一个字符”c”。相应的WAIT_xxx调用read读这个字符,并发生阻塞。
这里写图片描述

#include "apue.h"

static int pfd1[2], pfd2[2];

void TELL_WAIT(void)
{
    if(pipe(pfd1) < 0 || pipe(pfd2) < 0)
        err_sys("pipe error");
}

void TELL_PARENT(pid_t pid)
{
    if(write(pfd2[1], "c", 1) != 1)
        err_sys("write error");
}

void WAIT_PARENT(void)
{
    char c;

    if(read(pfd1[0], &c, 1) != 1)
        err_sys("read error");

    if(c != 'p')
        err_quit("WAIT PARENT: incorrect data");

}

void TELL_CHILD(pid_t pid)
{
    if(write(pfd1[1], "p", 1) != 1)
        err_sys("write error");
}

void WAIT_CHILD(void)
{
    char c;

    if(read(pfd2[0], &c, 1) != 1)
        err_sys("read error");

    if(c != 'c')
        err_quit("WAIT_CHILD: incorrect data");
}

使用popen向分页程序传送文件

#include "apue.h"
#include <sys/wait.h>

#define PAGER "${PAGER:-more}"  /* environment variable, or default */

int main(int argc, char *argv[])
{
    char line[MAXLINE];
    FILE *fpin, *fpout;

    if(argc != 2)
        err_quit("usage: a.out <pathname>");
    if((fpin = fopen(argv[1], "r")) == NULL)
        err_sys("can't open %s", argv[1]);

    /*
       FILE *popen(const char *cmdstring, const char *type);
       创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,调用exec以
       运行命令cmdstring,然后等待命令终止。
       type:"w",文件指针连接到cmdstring的标准输入,返回的文件指针是可写的。
             "r",文件指针连接到cmdstring的标准输出,返回的文件指针是可读的。
    */
    if((fpout = popen(PAGER, "w")) == NULL)
        err_sys("popen error");

    /* copy argv[1] to pager */
    while(fgets(line, MAXLINE, fpin) != NULL)
    {
        if(fputs(line, fpout) == EOF)
            err_sys("fputs error to pipe");
    }
    if(ferror(fpin))
        err_sys("fgets error");

    /*
       int pclose(FILE *fp);
    */
    if(pclose(fpout) == -1)
        err_sys("pclose error");

    exit(0);
}

这里写图片描述

popen和pclose函数
每次调用popen时,应记住创建进程的进程ID,以及文件描述符或FILE指针。在childpid中保存子进程ID,并用文件描述符作为下标。这样,当以FILE指针作为参数调用pclose时,调用fileno可得到其文件描述符,进而得到子进程ID,并调用waitpid,当发现子进程不存在时,返回-1,errno被设置为ECHILD。

#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>

/* Pointer to array allocated at run-time */
static pid_t *childpid = NULL;

static int maxfd;

FILE *popen(const char *cmdstring, const char *type)
{
    int i;
    int pfd[2];
    pid_t pid;
    FILE *fp;

    /* only allow "r" or "w" */
    if((type[0] != 'r' && type[0] != 'w') || type[1] != 0)
    {
        errno = EINVAL;  /* required by POSIX */
        return (NULL);
    }
    if(childpid == NULL)  /* first time through */
    {
        /* allocate zeroed out array for child pids */
        maxfd = open_max();
        if((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
            return (NULL);
    }

    if(pipe(pfd) < 0)
        return (NULL);  /* errno set by pipe() */

    if((pid = fork()) < 0)
        return (NULL);
    else if(pid == 0)
    {
        if(*type == 'r')
        {
            close(pfd[0]);
            if(pfd[1] != STDOUT_FILENO)
            {
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
        }
        else
        {
            close(pfd[1]);
            if(pfd[0] != STDIN_FILENO)
            {
                dup2(pfd[0], STDIN_FILENO);
                close(pfd[0]);
            }
        }

        /*  子进程需关闭以前调用popen时打开的且当前仍打开的I/O流 */
        for(i=0; i<maxfd; i++)
            if(childpid[i] > 0)
                close(childpid[i]);

        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);
    }

    /* parent continue... */
    if(*type == 'r')
    {
        close(pfd[1]);
        if((fp = fdopen(pfd[0], type)) == NULL)
            return (NULL);
    }
    else
    {
        close(pfd[0]);
        if((fp = fdopen(pfd[1], type)) == NULL)
            return (NULL);
    }

    childpid[fileno(fp)] = pid;  /* remember child pid for this fd */
    return (fp);
}

int pclose(FILE *fp)
{
    int fd, stat;
    pid_t pid;

    if(childpid == NULL)
    {
        errno = EINVAL;
        return (-1);  /* popen() has never been called */
    }

    fd = fileno(fp);
    if((pid = childpid[fd]) == 0)
    {
        errno = EINVAL;
        return (-1);   /* fp wasn't opened by popen() */
    }

    childpid[fd] = 0;
    if(fclose(fp) == EOF)
        return (-1);

    /* 若pclose的调用者以及为信号SIGCHLD设置了一个信号处理程序,
       则pclose的waitpid返回EINR。因运行调用者捕捉此信号,
       所以waitppid 被中断时,只是再次调用waitpid */
    while(waitpid(pid, &stat, 0) < 0)
        if(errno != EINTR)
    return (stat);  /* return child's termination status */
}

调用大写/小写过滤程序以读取命令
过滤程序:从标准输入读取数据,对其进行适当处理后写到标准输出。
向标准输出写一个提示,然后从标准输入读1行。使用popen,可在应用程序和输入之间插入一个程序以便对输入进行变换处理。
这里写图片描述
myuclc.c

#include "apue.h"
#include <ctype.h>

int main(void)
{
    int c;

    while((c = getchar()) != EOF)
    {
        if(isupper(c))
            c = tolower(c);
        if(putchar(c) == EOF)
            err_sys("output error");
        if(c == '\n')
            fflush(stdout);
    }
    exit(0);
}

popen2.c

#include "apue.h"
#include <sys/wait.h>

int main(void)
{
    char line[MAXLINE];
    FILE *fpin;

    if((fpin = popen("./myuclc", "r")) == NULL)
        err_sys("popen error");
    for(;;)
    {
        fputs("prompt> ", stdout);
        fflush(stdout);
        if(fgets(line, MAXLINE, fpin) == NULL)  /* read from pipe */
            break;
        if(fputs(line, stdout) == EOF)
            err_sys("fputs error to pipe");
    }
    if(pclose(fpin) == -1)
        err_sys("pclose error");
    putchar('\n');
    exit(0);
}

这里写图片描述

协同进程
协同进程:当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序为协同进程。

进程先创建两个管道:一个时协同进程的标准输入,另一个是协同进程的标准输出。
本例中,协同进程从标准输入读两个数,计算和,然后写到标准输出。
这里写图片描述

如果将协同进程的read和write替换成标准I/O,则不再工作。因为对标准输入的第一个fgets引起标准I/O库分配一个缓冲区,并选择缓冲区类型。因为标准输入是管道,所以缓冲区类型为全缓冲,标准输出同理。当add2在其标准输入读取阻塞时,add2tostdio也在管道读发生阻塞,产生死锁。
add2.c

#include "apue.h"

int main(void)
{
    int n, int1, int2;
    char line[MAXLINE];

    while((n = read(STDIN_FILENO, line, MAXLINE)) > 0)
    {
        line[n] = 0;  /* null terminate */
        if(sscanf(line, "%d%d\n", &int1, &int2) == 2)
        {
            sprintf(line, "%d\n", int1 + int2);
            n = strlen(line);
            if(write(STDOUT_FILENO, line, n) != n)
                err_sys("write error");
        }
        else
        {
            if(write(STDOUT_FILENO, "invalid args\n", 13) != 13)
                err_sys("write error");
        }
    }
    exit(0);
}

add2tostdio.c

#include "apue.h"

static void sig_pipe(int);  /* out signal handler */

int main(void)
{
    int n, fd1[2], fd2[2];
    pid_t pid;
    char line[MAXLINE]; 

    if(signal(SIGPIPE, sig_pipe) == SIG_ERR)
        err_sys("signal error");

    if(pipe(fd1) < 0 || pipe(fd2) < 0)
        err_sys("pipe error");

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid > 0)
    {
        close(fd1[0]);
        close(fd2[1]);
        while(fgets(line, MAXLINE, stdin) != NULL)
        {
            n = strlen(line);
            if(write(fd1[1], line, n) != n)
                err_sys("write error to pipe");
            if((n = read(fd2[0], line, MAXLINE)) < 0)
                err_sys("read error from pipe");
            if(n == 0)
            {
                err_msg("child closed pipe");
                break;
            }
            line[n] = 0;  /* null terminate */
            if(fputs(line, stdout) == EOF)
                err_sys("fputs error");
        }

        if(ferror(stdin))
            err_sys("fgets error on stdin");
        exit(0);
    }
    else
    {
        close(fd1[1]);
        close(fd2[0]);
        if(fd1[0] != STDIN_FILENO)
        {
            if(dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
                err_sys("dup2 error to stdin");
            close(fd1[0]);
        }

        if(fd2[1] != STDOUT_FILENO)
        {
            if(dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
                err_sys("dup2 error to stdout");
            close(fd2[1]);
        }

        if(execl("./add2", "add2", (char *)0) < 0)
            err_sys("execl error");
    }
    exit(0);
}

static void sig_pipe(int signo)
{
    printf("SIGPIPE caught\n");
    exit(1);
}

这里写图片描述

FIFO
FIFO:命名管道。不具有公共祖先的进程也能用其交换数据。
若用write写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE.

消息队列
消息队列是消息的连接标,存放在内核中并由消息队列标识符标识。

#include <sys/msg.h>

int msgget(key_t key, int flag);
创建一个新队列或打开一个现存的队列。
成功返回非负队列ID,出错返回-1。
flag:权限位

int msgctl(int msquid, int cmd, struct msqid_ds *buf);
对队列执行多种操作。
成功返回0,出错返回-1.
cmd为对队列执行的命令:
IPC_STAT  取队列的msqid_ds结构,存放在buf
IPC_SET   按buf设置msqid_ds结构
IPC_RMID  删除消息队列及队列中的数据

int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
将新消息添加到队列尾端。
成功返回0,出错返回-1。
ptr:指向长整型数,包含了正的整型消息类型,其后紧跟消息数据。可用下列结构:
struct mymesg
{
    long mtype;           positive message type
    char mtext[512];      message data, of length nbytes
};
nbytes:实际数据字节数。
flag:可以指定为IPC_NOWAIT。类似于文件I/O的非阻塞I/O标志。

ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
从队列取消息。
成功返回消息的数据部分长度,出错返回-1type:0,返回队列的第一个消息;>0,返回消息类型为type的第一个消息;
      <0,返回类型小于或等于type绝对值的消息,多个则取类型值最小的。
flag:设置MSG_NOERROR时,若返回消息大于nbytes,消息被截短。可设置为IPC_NOWAIT。

信号量
信号量是一个计数器,用于多进程对共享对象的访问。

#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);
获得一个信号量ID。
成功返回信号量ID,出错返回-1。
nsems:集合中的信号量数。如果创建新集合,则指定nsems。如果引用一个现存的集合,则nsems为0int semctl(int semid, int semnum, int cmd, ...);
多种信号量操作。
第4个参数可选,是多个特定命令的联合:
union semun
{
    int val;                   for SETVAL
    strut semid_ds *buf;       for IPC_STAT and IPC_SET
    unsigned short *array;     for GETALL and SETALL
};
semnum:信号量集合中的一个成员。
cmd:IPC_STAT、IPC_SET、IPC_RMID、GETVAL、SETVAL、GETPID、GETNCNT、GETZCNT、GETALL、SETALL

int semop(int semid, struct sembuf semoparray[], size_t nops);
自动指向信号量集合上的操作数组,原子操作。
成功返回0,出错返回-1。
setbuf:信号量操作
struct sembuf
{
    unsigned short sem_num;  member # in set (0, 1, ..., nsems-1)
    short sem_op;        operation (negative, 0, or positive)
    short sem_flag;      IPC_NOWAIT, SEM_UNDO
};
sem_op>0:释放占用的资源,sem_op值加到信号量的值上。如果指定了undo,则减去sem_op。
sem_op<0:要获取由该信号量获取的资源。若该信号量值大于sem_op的绝对值,则减去sem_op的
          绝对值。如果指定了undo,则加上sem_op的绝对值。
sem_o=0:调用进程希望等待该信号量值变为0.
nops:semoparray中的操作数。

共享存储
运行两个或更多进程共享一给定的存储区。因数据不需要在客户进程和服务器进程之间复制,所以是最快的一种IPC。信号量被用来实现对共享存储访问的同步。

不同
消息队列、信号量、共享存储器:1、创建时需要指定一个键。2、在系统范围内起作用,没有访问计数。如消息队列和内内容会余留在系统中,直至某进程读消息或删除队列消息,或删除队列,或系统再启动时删除队列。3、在文件系统中没有名字。
管道:当最后一个访问管道的进程终止时,管道被完全删除。
FIFO:当最后一个引用FIFO的进程终止时其名字仍保留在系统中,直至显式删除,但FIFO中的数据却已全部被删除。

大量应用程序仍可有效地使用管道和FIFO。在新的应用程序中,要尽可能避免使用消息队列及信号量,而应考虑全双工管道和记录锁,它们使用起来更为简单。共享存储有其应用场合,而mmap也能提供同样的功能。

信号量锁与记录锁比较:
1、记录锁比信号量锁慢。
2、记录锁简单,进程终止时系统会处理任何遗留下的锁。

打印各种不同数据类型所存放的位置
内核将以地址0连接的共享存储段放在什么位置与系统密切相关。

#include "apue.h"
#include <sys/shm.h>

#define ARRAY_SIZE 40000
#define MALLOC_SIZE 100000
#define SHM_SIZE 100000
#define SHM_MODE 0600  /* user read/write */

char array[ARRAY_SIZE];  /* uninitialized data = bss */

int main(void)
{
    int shmid;
    char *ptr, *shmptr;

    printf("array[] from %lx to %lx\n", (unsigned long)&array[0], 
        (unsigned long)&array[ARRAY_SIZE]);
    printf("stack around %lx\n", (unsigned long)&shmid);

    if((ptr = malloc(MALLOC_SIZE)) == NULL)
        err_sys("malloc error");
    printf("malloced from %lx to %lx\n", (unsigned long)ptr, 
        (unsigned long)ptr+MALLOC_SIZE);

    /*
        int shmget(key_t key, size_t size, int flag);
        获得一个共享存储标识符。
        IPC_PRIVATE是一个特殊的,用于创建一个新的共享存储段。
        成功返回共享存储ID,出错返回-1。
        size:共享存储段的长度(字节)。
    */
    if((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0)
        err_sys("shmget error");

    /*
       void *shmat(int shmid, const void *addr, int flag);
       返回:执行共享存储的指针。
       addr为0,则此段连接到内核选择的第一个可用地址,非0,且未指定SHM_RND,则连接到addr指定地址。
       addr非0,且指定了SHM_RND,则连接到(addr-(addr mod ulus SHMLBA))指定的地址。一般为0。
       flag指定了SHM_RDONLY,则以只读方式连接此段,否则以只写方式连接此段。
    */
    if((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
        err_sys("shmat error");
    printf("shared memory attached from %lx to %lx\n",
        (unsigned long)shmptr, (unsigned long)shmptr+SHM_SIZE);

    /*
       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
       对共享存储段执行多种操作。
       成功返回0,出错返回-1。
       cmd:
         IPC_STA  取shmid_ds结构,放在buf
         IPC_RMID 删除共享存储段
         IPC_SET  SHM_LOCK  SHM_UNLOCK   
    */
    if(shmctl(shmid, IPC_RMID, 0) < 0)
        err_sys("shmctl error");

    exit(0);
}

这里写图片描述
共享存储段紧靠在栈之下。

在父、子进程间使用/dev/zero存储映射I/O的IPC
将/dev/zero作为IPC,进行存储映射时,具有特殊性质:
1、存储区都初始化为0
2、多个进程的共同祖先进程对mmap指定了MAP_SHARED标准时,则这些进程可共享此存储区。

打开/dev/zero,指定长整型的长度调用mmap。存储映射成功后,关闭此设备。然后创建一个子进程。因为在调用mmap时指定了MAP_SHARED,所以一个进程写到存储区的数据可由另一个进程见到。

然后,父、子进程交替运行,使用同步函数对各自的共享存储映射区中的长整型数加1。存储映射区由
mmap初始为0。父进程先增1,使其成为1,然后子进程增1,使其成为2,然后父进程使其成为3…。

使用/dev/zero的优点,在调用mmap创建映射区前,无需存在一个实际的文件。缺点,只能在相关进程
间起作用。

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>

#define NLOOPS 1000
#define SIZE sizeof(long)  /* size of shared memory area */

static int update(long *ptr)
{
    return ((*ptr)++);  /* return value before increment */
}

int main(void)
{
    int fd, i, counter;
    pid_t pid;
    void *area;

    if((fd = open("/dev/zero", O_RDWR)) < 0)
        err_sys("open error");
    if((area = mmap(0, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED)
        err_sys("mmap error");
    close(fd);   /* can close /dev/zero now that it's mapped */

    TELL_WAIT();

    if((pid = fork()) < 0)
        err_sys("fork error");
    else if(pid > 0)
    {
        for(i=0; i<NLOOPS; i+=2)
        {
            if((counter = update((long*)area)) != i)
                err_quit("parent: expected %d, got %d", i, counter);

            TELL_CHILD(pid);
            WAIT_CHILD();
        }
    }
    else
    {
        for(i=1; i<NLOOPS+1; i+=2)
        {
            WAIT_PARENT();

            if((counter = update((long*)area)) != i)
                err_quit("child: expected %d, got %d", i, counter);

            TELL_PARENT(getppid());
        }
    }
    exit(0);
}
本书全面介绍了UNIX系统的程序设计界面—系统调用界面和标准C库提供的许多函数。 本书的前15章着重于理论知识的阐述,主要内容包括UNIX文件和目录、进程环境、进程控制、 进程间通信以及各种I/O。在此基础上,分别按章介绍了多个应用实例,包括如何创建数据库函数库, PostScript 打印机驱动程序,调制解调器拨号器及在伪终端上运行其他程序的程序等。 本书内容丰富权威, 概念清晰精辟,一直以来被誉为UNIX编程的“圣经”,对于所有UNIX程序员—无论是初学者还是专家级人士 —都是一本无价的参考书籍。 目 录 译者序 译者简介 前言 第1章 UNIX基础知识 1 1.1 引言 1 1.2 登录 1 1.2.1 登录名 1 1.2.2 shell 1 1.3 文件和目录 2 1.3.1 文件系统 2 1.3.2 文件名 2 1.3.3 路径名 2 1.3.4 工作目录 4 1.3.5 起始目录 4 1.4 输入和输出 5 1.4.1 文件描述符 5 1.4.2 标准输入、标准输出和标准 出错 5 1.4.3 不用缓存的I/O 5 1.4.4 标准I/O 6 1.5 程序和进程 7 1.5.1 程序 7 1.5.2 进程和进程ID 7 1.5.3 进程控制 7 1.6 ANSI C 9 1.6.1 函数原型 9 1.6.2 类属指针 9 1.6.3 原始系统数据类型 10 1.7 出错处理 10 1.8 用户标识 11 1.8.1 用户ID 11 1.8.2 组ID 12 1.8.3 添加组ID 12 1.9 信号 12 1.10 UNIX时间值 14 1.11 系统调用和库函数 14 1.12 小结 16 习题 16 第2章 UNIX标准化及实现 17 2.1 引言 17 2.2 UNIX标准化 17 2.2.1 ANSI C 17 2.2.2 IEEE POSIX 18 2.2.3 X/Open XPG3 19 2.2.4 FIPS 19 2.3 UNIX实现 19 2.3.1 SVR4 20 2.3.2 4.3+BSD 20 2.4 标准和实现的关系 21 2.5 限制 21 2.5.1 ANSI C限制 22 2.5.2 POSIX限制 22 2.5.3 XPG3限制 24 2.5.4 sysconf、pathconf 和fpathconf 函数 24 2.5.5 FIPS 151-1要求 28 2.5.6 限制总结 28 2.5.7 未确定的运行时间限制 29 2.6 功能测试宏 32 2.7 基本系统数据类型 32 2.8 标准之间的冲突 33 2.9 小结 34 习题 34 第3章 文件I/O 35 3.1 引言 35 3.2 文件描述符 35 3.3 open函数 35 3.4 creat函数 37 3.5 close函数 37 3.6 lseek函数 38 3.7 read函数 40 3.8 write函数 41 3.9 I/O的效率 41 3.10 文件共享 42 3.11 原子操作 45 3.11.1 添加至一个文件 45 3.11.2 创建一个文件 45 3.12 dup和dup2函数 46 3.13 fcntl函数 47 3.14 ioctl函数 50 3.15 /dev/fd 51 3.16 小结 52 习题 52 第4章 文件和目录 54 4.1 引言 54 4.2 stat, fstat和lstat函数 54 4.3 文件类型 55 4.4 设置-用户-ID和设置-组-ID 57 4.5 文件存取许可权 58 4.6 新文件和目录的所有权 60 4.7 access函数 60 4.8 umask函数 62 4.9 chmod和fchmod函数 63 4.10 粘住位 65 4.11 chown, fchown和 lchown函数 66 4.12 文件长度 67 4.13 文件截短 68 4.14 文件系统 69 4.15 link, unlink, remove和rename 函数 71 4.16 符号连接 73 4.17 symlink 和readlink函数 76 4.18 文件的时间 76 4.19 utime函数 78 4.20 mkdir和rmdir函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值