介绍:
该命令创建指定的目录名,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录
语法:

mkdir [-m] [-p] 目录名

选项介绍:
    -m: 对新建目录设置存取权限,也可以用chmod命令设置;
    -p: 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录;

执行范例:

01$ mkdir DIRA
02$ ls
03DIRA
04$ mkdir -m 775 DIRB
// 775相当于chmod 下的模式。也可以用符合标示  a=rwx,r标示读,w标示写,x标示可以进入。  
05$ ls -l
06drwxr-xr-x  2 xxxx users 4096 Feb 19 22:29 DIRA
07drwxrwxr-x  2 xxxx users 4096 Feb 19 22:30 DIRB
08$ mkdir -p DIRC/hello
09$ ls -l
10drwxr-xr-x  2 xxxx users 4096 Feb 19 22:29 DIRA
11drwxrwxr-x  2 xxxx users 4096 Feb 19 22:30 DIRB
12drwxr-xr-x  3 xxxx users 4096 Feb 19 22:31 DIRC
13$ ls DIRC
14hello

rm -rf 命令: 删除文件

 rm -rf aa


access函数
  头文件:io.h
  功 能: 确定文件或文件夹的访问权限。即,检查某个文件的存取方式,比如说是只读方式、只写方式等。如果指定的存取方式有效,则函数返回0,否则函数返回-1。
  用 法: int access(const char *filenpath, int mode); 或者int _access( const char *path, int mode );
  参数说明:
  filenpath
  文件或文件夹的路径,当前目录直接使用文件或文件夹名
  备注:当该参数为文件的时候,access函数能使用mode参数所有的值,当该参数为文件夹的时候,access函数值能判断文件夹是否存在。在WIN NT 中,所有的文件夹都有读和写权限
  mode
  要判断的模式
  在头文件unistd.h中的预定义如下:
  #define R_OK 4 /* Test for read permission. */
  #define W_OK 2 /* Test for write permission. */
  #define X_OK 1 /* Test for execute permission. */
  #define F_OK 0 /* Test for existence. */
  具体含义如下:
  R_OK 只判断是否有读权限
  W_OK 只判断是否有写权限
  X_OK 判断是否有执行权限
  F_OK 只判断是否存在
  access函数程序范例(C语言中)
  #include <stdio.h>
  #include <io.h>
  int file_exists(char *filename);
  int main(void)
  {
  printf("Does NOTEXIST.FIL exist: %s\n",
  file_exists("NOTEXISTS.FIL") ? "YES" : "NO");
  return 0;
  }
  int file_exists(char *filename)
  {
  return (access(filename, 0) == 0);
  }


字号:
open与close函数


(1) 函数说明
open函数用于打开或者创建文件,在打开或创建文件时可以制定文件的属性及用户的权限等各种参数。
close函数用于关闭一个打开文件。当一个进程终止时,内核会自动关闭该进程所有打开的文件,很多程序都使用这一功能而不使用close函数显示的关闭一个文件。

(2) 函数格式
open函数:
所需头文件:#include<sys/types.h>  /*提供类型pid_t,size_t的定义*/
#include<sys/stat.h>
#include<fcntl.h>
函数原型: int open(const char *pathname, flags, int perms)

返回值:成功则返回文件描述符,否则返回 -1
pathname: 被打开的文件名或路径名。

flags文件打开的方式。

flags这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。

   O_RDONLY       只读模式打开文件
     O_WRONLY       只写模式打开文件
     O_RDWR         读写模式打开文件

打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:

 O_APPEND       每次写操作都写入文件的末尾
        O_CREAT         如果指定文件不存在,则创建这个文件
       O_EXCL          如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
       O_TRUNC         如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
       O_NOCTTY        如果路径名指向终端设备,不要把这个设备用作控制终端
       O_NONBLOCK     如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继  I/O设置为非阻塞模式

以下三个常量同样是选用的,它们用于同步输入输出

O_DSYNC        等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
      O_RSYNC         read 等待所有写入同一区域的写操作完成后再进行
      O_SYNC          等待物理 I/O 结束后再 write,包括更新文件属性的 I/O

perms 访问权限位,用8进制法表示。一般仅在需要创建文件时使用。

注意: 在open函数中,flags参数可以通过"|"组合,但是前三个中只能取一个使用。

     

open 返回的文件描述符一定是最小的未被使用的描述符。

    如果 NAME_MAX(文件名最大长度,不包括'\0')是 14,而我们想在当前目录下创建文件名长度超过 14 字节的文件,早期的 System V 系统(如 SVR2)会截断超出部分,只保留前 14 个字节;而由 BSD 衍生的(BSD-derived)系统会返回错误信息,并且把 errno 置为 ENAMETOOLONG。

    POSIX.1 引入常量 _POSIX_NO_TRUNC 用于决定是否截断长文件名/长路径名。如果_POSIX_NO_TRUNC 设定为禁止截断,并且路径名长度超过 PATH_MAX(包括 '\0'),或者组成路径名的任意文件名长度超过 NAME_MAX,则返回错误信息,并且把 errno 置为 ENAMETOOLONG。

what is the difference between



close函数

所需头文件: #include<unistd.h>

函数原型:   int close(int fd)

返回值: 成功返回0,失败返回-1。

fd: 文件描述符


举例:

#include<stdio.h>

#include<stdlib.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<unistd.h>

int main()

{

int fd;

fd = open("/tmp/hello.c",O_CREAT | O_TRUNC | O_WRONLY, 0600);

/*调用open函数*/

if(fd<0)

{

perror("open:");

exit(1);

}

else  printf("open file: hello.c %d\n",fd);

close(fd);          /*调用close函数*/

exit(0);

}

注意open 返回的文件描述符一定是最小的未被使用的描述符。

1.open 函数不要创建文件,最后只用来打开已有文件;
2. read, write, and open 属于linux内核函数; fopen, fclose, fread,则为标注c函数;

Originally, the open system call could only open files that existed. Now that you can do "open(file, O_CREAT|O_TRUNC|O_WRONLY, mode)", creat no longer has any great use. But lots of code had been written using creat, so it persists. Don't use creat, it's day is long past.

The system calls like read, write, and open must exist if programs are going to be able to use the kernel's drivers. Stuff like fopen, fclose, fread, etc are put of the standard I/O library or stdio



一个文件描述符关闭之后对其write操作竟然成功了!!!上代码
  1. #include<fcntl.h>
  2. #include<string.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<unistd.h>
  6. int main()
  7. {
  8.         char buf1[]="01234";
  9.         char buf2[]="56789";
  10.         int fd1,fd2;
  11.         //1
  12.         if((fd1=open("foo1",O_RDWR)) < 0)
  13.                 printf("open error");       
  14.         //2       
  15.         if(close(fd1)==-1)
  16.                 printf("close error");
  17.         //3       
  18.         if((fd2=open("foo1",O_RDWR)) < 0)
  19.                 printf("open error");
  20.         //4
  21.         if(write(fd1, buf1, strlen(buf1)) != strlen(buf1))
  22.                 printf("write error");
  23. }
复制代码
更恐怖的是把语句块2和3颠倒之后就会报错了
  1. #include<fcntl.h>
  2. #include<string.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<unistd.h>
  6. int main()
  7. {
  8.         char buf1[]="01234";
  9.         char buf2[]="56789";
  10.         int fd1,fd2;
  11.         //1
  12.         if((fd1=open("foo1",O_RDWR)) < 0)
  13.                 printf("open error");       
  14.         //3       
  15.         if((fd2=open("foo1",O_RDWR)) < 0)
  16.                 printf("open error");
  17.         //2
  18.         if(close(fd1)==-1)
  19.                 printf("close error");       

  20.         //4
  21.         if(write(fd1, buf1, strlen(buf1)) != strlen(buf1))
  22.                 printf("write error");
  23. }
复制代码
第一个打开后:
fd1=5
fd2未赋值

关闭fd1后:
fd1=5(值未改变)
fd2未赋值

打开fd2:
fd1=5
fd2=5
所以fd1照旧能写

再看下第二段代码,打开fd2后:
fd1=5
fd2=6
关闭fd1后当然是写失败了!




mkfifo 创建有名管道(异于管道)
在linux下我们先通过一个实例来说明一下mkfifo 的作用, 然后再看mkfifo 的使用详解

# mkfifo myfifo
# ping
edu.cn >> myfifo

另开一个终端:
# cat myfifo

看到效果了吧

mkfifo 命令

用途

制作先进先出(FIFO)特别文件。
语法

mkfifo [ -m Mode ] File …
描述

根据指定的顺序,mkfifo 命令创建由 File 参数指定的 FIFO 特别文件。如果没有指定 -m Mode 标志,则 FIFO 文件的文件方式是通过文件方式创建所修改的包含 OR 的 S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH 和 S_IWOTH 许可权的一位宽度(请参阅 umask 命令)。

mkfifo 命令与 mkfifo 子例程运行相似。
标志

-m Mode     设置新创建的 FIFO 文件的文件许可权位的值为指定的方式值。 Mode 变量与为 chmod 命令定义的方式操作数相同。如果使用了字符 +(加号)和 -(减号),则它们是相对于初始值 a=rw 来解释的(即具有许可权 rw-rw-rw-)。
退出状态

这条命令返回以下退出值:
0     成功创建所有指定的 FIFO 特别文件。
>0     发生错误。
示例

   1. 要使用许可权 prw-r–r– 创建 FIFO 特别文件,请输入:
      mkfifo  -m 644 /tmp/myfifo
      此命令使用所有者的读/写许可权以及组和其他用户的读许可权来创建 /tmp/myfifo 文件。
   2. 使用 -(减号)操作符创建一个 FIFO 特别文件以设置 prw-r—– 许可权,请输入:
      mkfifo  -m g-w,o-rw /tmp/fifo2
      此命令创建 /tmp/fifo2 文件,删除组的写权限和其他用户的所有许可权。

          注:如果多于一个的文件是用 -(减号)操作符创建的,那么用顿号分隔每个方式说明符,中间不用空格。

文件

/usr/bin/mkfifo     包含 mkfifo 命令。

 

 

Linux下进程间通信:命名管道-mkfifo

由 lgb 于 星期日, 2010/09/12 - 21:24 发表 IPC Linux mkfifo mknode 命名管道 进程间通信

摘要:进程间通信的方法有很多,FIFO与管道是最古老,也是相对来说更简单的一个通信机制。FIFO相对管道有一个优势,就是FIFO只要求两个进程是同一主机的,而不要求进程之间存在亲缘关系。FIFO是存在于文件系统的文件,可以使用诸如open、read、write等函数来操作。本文总结网络和APUE关于FIFO讨论,同时参考了Linux系统手册。

目录 [隐藏]FIFO(命名管道)概述mkfifo函数命名管道读写规则从FIFO中读取数据从FIFO中写入数据FIFO示例

FIFO(命名管道)概述

FIFO是一种进程通信机制,它突破通常管道无法进行无关进程之间的通信的限制,使得同一主机内的所有的进程都可以通信。FIFO是一个文件类型,stat结构中st_mode指明一个文件结点是不是一个FIFO,可以使用宏S_ISFIFO来测试这一点。

当一个FIFO存在于文件系统里时,我们只需要在想进行通信的进程内打开这个文件就可以了。当然FIFO作为一个特殊的文件,它有一些不同普通文件特性,下面会详细详述它的读写规则,这些相对精通文件来有一定的区别。

我们可以使用open、read、write来操作FIFO文件,从而实现进程间通信的目的。在shell环境下,也可以直接使用FIFO,这时往往与重写向有一些关联,一般系统都提供mkfifo实用程序来创建一个FIFO文件,这个程序实际上使用mkfifo系统调用来完成这个事。

mkfifo函数

mkfifo创建一个指定名字的FIFO,它的函数原型如下:

#include<sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);

返回值:成功,0;失败,-1

参数pathname指出想要创建的FIFO路径,参数mode指定创建的FIFO访问模式。这个访问会与当前进程的umask进程运算,以产生实际应用的权限模式。

mkfifo返回-1时表示创建过程中遇到某种错误,此时会设置errno,用户可以检测errno来取得进一步信息:

EACCES: 路径所在的目录不允许执行权限EEXIST:路径已经存在,这时包括路径是一个符号链接,无论它是悬空还没有悬空。ENAMETOOLONG:要么全部的文件名大于PATH_MAX,要么是单独的文件名大于NAME_MAX。在GNU系统里没有这个文件名长度的限制,但在其它系统里可能存在。ENOENT:目录部分不存在,或者是一个悬空链接。ENOTDIR:目录部分不一个目录。EROFS:路径指向一个只读的文件系统。

命名管道读写规则

FIFO又叫命名管道,事实上它与管道确实在下许多相似之处,下面关于规则的讨论很体现这个相似。

从FIFO中读取数据

约定:如果一个进程为了从FIFO中读取数据而阻塞打开了FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。

如果有进程写打开FIFO,且当前FIFO为空,则对于设置了阻塞标志的读操作来说,将一直阻塞下去,直到有数据可以读时才继续执行;对于没有设置阻塞标志的读操作来说,则返回0个字节,当前errno值为EAGAIN,提醒以后再试。对于设置了阻塞标志的读操作来说,造成阻塞的原因有两种:一、当前FIFO内有数据,但有其它进程在读这些数据;二、FIFO本身为空。
解阻塞的原因是:FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量,只要有数据写入即可。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程中有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数少于请求的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。从FIFO中写入数据

约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。

FIFO的长度是需要考虑的一个很重要因素。系统对任一时刻在一个FIFO中可以存在的数据长度是有限制的。它由#define PIPE_BUF定义,在头文件limits.h中。在Linux和许多其他类UNIX系统中,它的值通常是4096字节,Red Hat Fedora9下是4096,但在某些系统中它可能会小到512字节。

虽然对于只有一个FIFO写进程和一个FIFO的读进程而言,这个限制并不重要,但只使用一个FIFO并允许多个不同进程向一个FIFO读进程发送请求的情况是很常见的。如果几个不同的程序尝试同时向FIFO写数据,能否保证来自不同程序的数据块不相互交错就非常关键了à也就是说,每个写操作必须“原子化”。

设置了阻塞标志的写操作:

当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。即写入的数据长度小于等于PIPE_BUF时,那么或者写入全部字节,或者一个字节都不写入,它属于一个一次性行为,具体要看FIFO中是否有足够的缓冲区。当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

没有设置阻塞标志的写操作:

当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。FIFO示例

本段给出使用FIFO一个示例,它体现了两个使用FIFO的典型情景。

创建FIFO

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
        int res = mkfifo("/tmp/my_fifo", 0777);
        if(res == 0)
        {
                printf("FIFO created\n");
        }
        exit(EXIT_SUCCESS);
}

使用FIFO

#include<errno.h>
#include<sys/stat.h>
#include<fcntl.h>

FIFO "/tmp/my_fifo"
//本程序从一个FIFO读数据,并把读到的数据打印到标准输出
//如果读到字符“Q”,则退出
int main(int argc, char** argv)
{
        char buf_r[100];
        int fd;
        int nread;
        if((mkfifo(FIFO, O_CREAT) < 0) && (errno != EEXIST))
        {
                printf("不能创建FIFO\n");
                exit(1);
        }

        printf("准备读取数据\n");
        fd = open(FIFO, O_RDONLY, 0);
        if(fd == -1)
        {
                perror("打开FIFO");
                exit(1);
        }

        while(1)
        {
                if((nread = read(fd, buf_r, 100)) == -1)
                {
                        if(errno == EAGAIN) printf("没有数据\n");
                }

                //假设取到Q的时候退出
                if(buf_r[0]=='Q') break;

                buf_r[nread]=0;
                printf("从FIFO读取的数据为:%s\n", buf_r);
                sleep(1);
        }

}

  







/*****************************************************
函数名:CreatePipe  
*****************************************************/
int CreatePipe(void)
{
    int res = 0;
int mode;

       
    if (osal_access(SCCPIPE, F_OK) == -1) //判断是否建立了文件夹,如果没有建立,则新建
    {
        res = osal_system("mkdir -m a=rwx SCCPIPE");  // system 函数作用相当于在shell下输入命令。
        if (res != 0)
        {
            printf(" %s: Could not create directory SCCPIPE\n", __FUNCTION__);
            return -1;
        }
    }

    if (osal_access(PIPE_IN, F_OK) == -1)  //判断是否建立了管道,如果没有,则新建
    {
        if((osal_mkfifo(PIPE_IN, 0777) != 0)&& (errno != EEXIST))
        {
            printf(" %s: Could not create PIPE_IN %s\n", __FUNCTION__, PIPE_IN);
            return -1;
        }
    }
    if (osal_access(PIPE_OUT, F_OK) == -1) //判断是否建立了管道,如果没有,则新建
    {
        if((osal_mkfifo(PIPE_OUT, 0777) != 0)&& (errno != EEXIST))
        {
            unlink(PIPE_IN);
            printf("%s return: Could not create PIPE_OUT %s\n", __FUNCTION__, PIPE_OUT);
            return -1;
        }
    }
pipein_id = osal_open(PIPE_IN, O_WRONLY);  //只读打开管道PIPE_IN
if(pipein_id < 0)
{
printf("open pipe_in wrong\n");
res = pipein_id;
}
    return res;
}

read函数从打开的设备或文件中读取数据。

#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); 返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0

 

参数count是请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。注意这个读写位置和使用C标准I/O库时的读写位置有可能不同,这个读写位置是记在内核中的,而使用C标准I/O库时的读写位置是用户空间I/O缓冲区中的位置。比如用fgetc读一个字节,fgetc有可能从内核中预读1024个字节到I/O缓冲区中,再返回第一个字节,这时该文件在内核中记录的读写位置是1024,而在FILE结构体中记录的读写位置是1。注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。read函数返回时,返回值说明了buf中前多少个字节是刚读上来的。有些情况下,实际读到的字节数(返回值)会小于请求读的字节数count,例如:

  • 读常规文件时,在读到count个字节之前已到达文件末尾。例如,距文件末尾还有30个字节而请求读100个字节,则read返回30,下次read将返回0。

  • 从终端设备读,通常以行为单位,读到换行符就返回了。

  • 从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数,后面socket编程部分会详细讲解。

write函数向打开的设备或文件中写数据。

#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); 返回值:成功返回写入的字节数,出错返回-1并设置errno

写常规文件时,write的返回值通常等于请求写的字节数count,而向终端设备或网络写则不一定。

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:

  • 正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。

  • 就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。

下面这个小程序从终端读数据再写回终端。

例 28.2. 阻塞读终端

#include <unistd.h> #include <stdlib.h> int main(void) { char buf[10]; int n; n = read(STDIN_FILENO, buf, 10); if (n < 0) { perror("read STDIN_FILENO"); exit(1); } write(STDOUT_FILENO, buf, n); return 0; }

执行结果如下:

$ ./a.out hello(回车) hello $ ./a.out hello world(回车) hello worl$ d bash: d: command not found

第一次执行a.out的结果很正常,而第二次执行的过程有点特殊,现在分析一下:

  1. Shell进程创建a.out进程,a.out进程开始执行,而Shell进程睡眠等待a.out进程退出。

  2. a.out调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走10个字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。

  3. a.out进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符d和换行符,把它当成一条命令解释执行,结果发现执行不了,没有d这个命令。

如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备暂时没有数据可读就返回-1,同时置errnoEWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里(would block,虚拟语气),事实上并没有阻塞而是直接返回错误,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

while(1) { 非阻塞read(设备1); if(设备1有数据到达) 处理数据; 非阻塞read(设备2); if(设备2有数据到达) 处理数据; ... }

如果read(设备1)是阻塞的,那么只要设备1没有数据到达就会一直阻塞在设备1的read调用上,即使设备2有数据到达也不能处理,使用非阻塞I/O就可以避免设备2得不到及时处理。

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。

while(1) { 非阻塞read(设备1); if(设备1有数据到达) 处理数据; 非阻塞read(设备2); if(设备2有数据到达) 处理数据; ... sleep(n); }

这样做的问题是,设备1有数据到达时可能不能及时处理,最长需延迟n秒才能处理,而且反复查询还是做了很多无用功。以后要学习的select(2)函数可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,从而圆满地解决了这个问题。

以下是一个非阻塞I/O的例子。目前我们学过的可能引起阻塞的设备只有终端,所以我们用终端来做这个实验。程序开始执行时在0、1、2文件描述符上自动打开的文件就是终端,但是没有O_NONBLOCK标志。所以就像例 28.2 “阻塞读终端”一样,读标准输入是阻塞的。我们可以重新打开一遍设备文件/dev/tty(表示当前终端),在打开时指定O_NONBLOCK标志。

例 28.3. 非阻塞读终端

#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdlib.h> #define MSG_TRY "try again\n" int main(void) { char buf[10]; int fd, n; fd = open("/dev/tty", O_RDONLY|O_NONBLOCK); if(fd<0) { perror("open /dev/tty"); exit(1); } tryagain: n = read(fd, buf, 10); if (n < 0) { if (errno == EAGAIN) { sleep(1); write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); goto tryagain; } perror("read /dev/tty"); exit(1); } write(STDOUT_FILENO, buf, n); close(fd); return 0; }

以下是用非阻塞I/O实现等待超时的例子。既保证了超时退出的逻辑又保证了有数据到达时处理延迟较小。

例 28.4. 非阻塞读终端和等待超时

#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdlib.h> #define MSG_TRY "try again\n" #define MSG_TIMEOUT "timeout\n" int main(void) { char buf[10]; int fd, n, i; fd = open("/dev/tty", O_RDONLY|O_NONBLOCK); if(fd<0) { perror("open /dev/tty"); exit(1); } for(i=0; i<5; i++) { n = read(fd, buf, 10); if(n>=0) break; if(errno!=EAGAIN) { perror("read /dev/tty"); exit(1); } sleep(1); write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); } if(i==5) write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT)); else write(STDOUT_FILENO, buf, n); close(fd); return 0; }