环境高级编程文件I/O

系统调用和库函数:

系统调用是操作系统为用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口函数;


库函数:顾名思义是把函数放到库里。是把一些常用到的函数编完放到一个库文件里,供别人用,别人用的时候把它所在的文件名用#include<>加到里面就可以了。可分为两类,一类是c语言标准规定的库函数,一类是编译器特定的库函数。


man 手册第一部分是命令         man 1 printf
man 手册第二部分是系统调用  man 2 write
man 手册第三部分是库函数     man 3 printf

文件描述符:

 对于内核而言,所有打开的文件、设备、网络socket都是通过文件描述符引用,文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用open或creat返回的文件描述符标识该文件,将其作为参数传给read或write。
按照惯例,UNIX系统shell把文件描述符与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。这是各种shell以及很多应用程序使用的惯例,与UNIX内核无关。如果不遵循这种惯例,很多UNIX系统应用程序就不能正常工作。

文件描述符的变化范围是0~OPEN_MAX-1。

    应用程序运行的时候,系统将会为该进程默认打开三个文件描述符:
          标准输入:   STDIN_FILENO (0)
          标准输出:   STDOUT_FILENO (1)
          标准出错:   STDERR_FILENO (2)


   文件描述符的变化范围是:  0~OPEN_MAX-1 

open()和openat()

#include

 int open(const char *path, int oflag, ... /*mode_t mode*/);
int openat(int fd,const char *path, int oflag, ... /*mode_t mode*/);
//两函数的返回值:若成功,返回文件描述符;若出错,返回-1

返回值 int  fd 文件描述符(file description),  open系统调用返回的文件描述符一定是最小的、未使用的文件描述符数值。

对于open函数而言,仅当创建新文件时才使用这个函数。


参数:
path:  要打开的文件或者创建文杰的名字、设备的路径名
oflag:    由多个选项进行“或”运算构造oflag参数
     必选: O_RDONLY (只读)、    O_WRONLY(只写)、  O_RDWR(读写)
     可选: O_APPEND   每次写时都追加到文件的尾端。
                 O_CREAT      文件不存在则创建它,使用该选项需要第三个参数mode
                 O_NONBLOCK  如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作设置非阻塞模式方式。
                 O_TRUNC     如果文件存在,而且为只写或读写成功打开,则将其长度截取为0;
                 O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY....
mode:  oflag带O_CREAT选项时,必须带该参数用来指定打开文件的权限模式,如066。


例: int    fd;             fd = open(“text.txt”, O_RDWR|O_CREAT|O_TRUNC, 0666);
                                 fd = open(“text.txt”, O_WRONLY|O_APPEND);

creat()

#include

 int creat(const char *path, mode_t mode);
//返回值:若成功,返回为只写打开的文件描述符;若出错,返回-1

此函数用来创建一个新文件并返回其fd。它等价于
  open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);

creat的一个不足之处是它以只写方式打开创建的文件。




 例:  int    fd;     fd=creat(“text.txt”, 0644);

close()


  该函数用来关闭一个打开的文件,关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件。

    #include
    int  close(int   fd);

//返回值:若成功,返回0;若出错,返回-1

lseek()

 每打开一个文件时都有一个与其关联的“当前文件偏移量”。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都是从当前文件偏移量处开始,并使用偏移量增加所读写的字节数。
    按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0.

    #include
    off_t  lseek(int fd, off_t  offset, int  whence);
//返回值:若成功,返回新的文件偏移量;若出错,返回-1
对参数offset的解释与参数whence的值相关:
whence:   SEEK_SET, 则将该文件偏移量设置为距文件开始处offset个字节;
        SEEK_CUR,则该文件的偏移量设置为当前值加offset,offset可为正或负;
        SEEK_END,则将该文件偏移量设置为长度加offset,offset可正可负;

若lseek成功执行,则返回新的文件偏移量,为此可以用下列方式确定打开文件偏移量:
off_t    pos;
pos = lseek(fd, 0, SEEK_CUR);

这种方法也可以用来确定所涉及的文件是否可以设置偏移量。如果文件描述符指向的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。

例如:

所示的程序用于测试对其标准输入是否设置偏移量。

#include "apue.h"

int

main(void)

{

  if(lseek(STDIN_FILENO,0,SEEK_CUR)==-1)

    printf("cannot seek\n");

  else

    printf("seek OK\n");

  exit(0);

}

read()/write()

 read()函数用来从打开的文件中读取数据:

#include
ssize_t   read(int fd,  void *buf, size_t   nbytes);
//返回值:读到的字节数,若以读到文件尾,返回0;若出错,返回-1

  如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。




    write()函数用来往打开的文件中写入数据:

#include
ssize_t   write(int fd,  const void *buf, size_t   nbytes);
//返回值:若成功,返回已写的字节数;若出错,返回-1

  如write成功,则返回实际写入的字节数,失败返回-1。

write出错的一个常见原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。

对于普通文件,写操作从当前文件的当前偏移量出开始。

系统调用出错处理:

大部分的Linux系统调用返回值都是0表示成功,-1表示失败。在库函数中有个整形类型的errno变量,每个errno值对应着以字符串表示的错误类型。如果系统调用出错,则该函数将重新设置errno的值,通过该信息我们可以查看系统调用出错的具体原因。
  perror() 用来将上一个函数发生错误的原因输出到标准出错上(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
   strerror通过标准错误的标号,获得错误的描述字符串 ,将单纯的错误标号转为字符串描述,方便用户查找错误。
    #include
    #include


    int     fd;
    if(  (fd=open(“none_exsit_file.xt”, O_RDONLY)) )
   {
              perror(“Oen file failure”);
              printf(“Open file failure: %s\n”, strerror(errno));
   }

例如:

int main(int argc, char *argv)
{
    int                 fd = -1;
    char                buf[1024];
    //char                *buf;


    fd = open("file.txt", O_RDWR|O_CREAT|O_TRUNC);
    if(fd < 0)
    {
        perror("Open failure");
        printf("Open %s failure:[%d] %s\n", "file.txt", errno, strerror(errno));
        return ;
    }
    write(fd, "Hello", 5);


    memset(buf, 0, sizeof(buf));
    read(fd, buf, sizeof(buf));
    printf("file content: %s\n", buf);


    close(fd);
}

dup()和dup2()

  以下两个函数都可以用来复制一个现有的文件描述符。


int  dup(int  fd);
int dup2(int fd, int fd2);


   由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。
   dup2可以用fd2参数指定新描述符的值。如果fd2已经打开,则先关闭。如fd等于fd2, 则dup2返回fd2, 而不关闭它。


  使用这两个函数可以用来实现标注输入、标准输出、标准出错重定向:
    int     fd = -1;


     fd = open(“std.txt”, O_RDWR|O_CREAT|O_TRUNC, 0666);
     dup2(fd, STDIN_FILENO);
     dup2(fd, STDOUT_FILENO);
     dup2(fd, STDERR_FILENO);
     
     printf(“Hello Word!\n”); 

例如;

int main(int argc, char *argv)
{
    int                 fd = -1;


    fd = open("file.txt", O_RDWR|O_CREAT|O_TRUNC);
    if(fd < 0)
    {
        printf("Open %s failure:[%d] %s\n", "file.txt", errno, strerror(errno));
        return ;
    }


    dup2(fd, 1);


    printf("fd=%d", fd);


    close(fd);
}

ioctl()

 ioctl()函数一直是I/O操作的杂物箱,不能用本章中其他函数表示的I/O操作通常都能用ioctl()表示。终端I/O、设备I/O是使用ioctl()最多的地方。


   int  ioctl(int  fd,  int cmd,  ...);


fd:  文件描述符
cmd:  命令字,这个参数需要与设备驱动中的cmd保持一致。
第三个参数可选


  今后的LED驱动,我们将会在驱动中实现该函数,在应用程序空间再调用该函数控制LED的亮和灭。


  int     fd = -1;
  fd = open(“/dev/led”, O_RDWR);
  ioctl(fd, TURN_OFF,  3);
  close(fd);

stat系列函数:

stat系列函数用来返回文件或目录的相关信息:


int stat(const char * restrict path, struct  stat *restrict buf);
int fstat(int fd, struct stat *buf);


struct   stat
{
    mode_t   st_mode;                            struct  timespec   st_atime;
    ino_t       st_ino;                                  struct  timespec   st_mtime;
    dev_t      st_dev;                                 struct  timespec   st_ctime;
    dev_t      st_rdev;                                blksize_t              st_blksize;
    nlink_t    st_nlink;                                blkcnt_t                st_blocks;
    uid_t       st_uid;                                 }
    gid_t       st_gid;
    off_t        st_size;


文件类型:  普通文件(-)、目录文件(d)、块设备(b)、字符设备(c)、FIFO(p)、套接字socket(s)、符号链接(l)

例如:

#include
#include
#include


int main (int argc, char **argv)
{
    struct   stat   stbuf; 


    stat("file.txt", &stbuf);


    printf("File size: %d bytes UID:%d GID:%d\n", 
            stbuf.st_size, stbuf.st_uid, stbuf.st_gid);


    return 0;
}

access()

access可以用来测试文件是否存在或测试其权限位:
  int access(const char *path, int mode);


mode:   F_OK、R_OK、W_OK、X_OK


  测试文件是否存在:
  if(  !(access(“test.txt”, F_OK)) )   // 返回0表示存在;返回-1表示不存在。
  {
       printf(“File exist\n”);
  }


   测试文件是否可读可写:
  if(  !(access(”test.txt”, R_OK|W_OK)) )   // 返回0表示存在;返回-1表示不存在。
  {
       printf(“File  mode is read/write \n”);
  }

unlink()和rename()

int unlink(const char *path);


  调用该函数将path指定的文件的链接数减1,如果对该文件还有其他链接存在,则仍可以通过其他链接访问该文件的数据。
    只有当链接记数达到0时,该文件的内容才可被删除。如果有进程打开了该文件,其内容也不能被删除。关闭一个文件时,内核首先检查打开该文件的进程个数,如果这个记数达到0,内核再去检查它的链接记数,如果记数也是0,那么就删除该文件内容。








int  rename(const char *oldname, const char *newname);


调用该函数可以实现文件的重命名。

文件夹操作:

创建文件夹:
 int  mkdir(const char *pathname,   mode_t   mode);


删除文件夹:
int  rmdir(const char *pathname);


打开文件夹:
DIR *opendir(const char  *pathname);


读文件夹:
struct dirent * readdir(DIR  *dp);


关闭文件夹:
int  closedir(IDR *dp);


改变工作目录:
int chdir(const char * pathname);

编程任务:

   创建文件 eeprom,该文件的:
  0地址处开始用来保存产品序列号,其格式为LY-FL2440-NNN (其中NNN为001, 002...)
 0x10地址处开始用来保存MAC地址(字符串形式存放),MAC地址格式如   00:05:6E:00:00:01(MAC地址前3个字节为厂商ID,后3个字节为流水号)
0x30地址开始处为 所有者姓名拼音,长度不超过32个字节;如 guowenxue


编写程序用来生产时往该文件里写入产品序列号、MAC地址、和所有者




学习我这个代码希望大家搞明白以下几点:
1, 看看我的函数,变量的命名规范,看看我代码的缩进,空行;
2, 一个功能模块抽象成一个函数,函数入口地方要判断函数输入参数的合法性, 每个函数调用应该判断他的返回值做出错处理;
3, 在我这个代码里,使用到了 getopt_long 来做命令行参数解析,这个函数大家需要百度学习一下,这个是固定的讨论,大家了解之后以后碰到这个需求就这么处理就行了;
4, MAC地址和产品序列号是变化的,所以我们使用了snprintf()根据命令行参数 -s 指定的序列号值来自动生产相应的产品序列号和MAC地址;
5, 关于对SN、MAC、User的读或写功能分别抽象成为两个个函数,这样在进行读或写的时候代码可以复用;

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include


#define EEPROM_SIZE            256
#define EEPROM_DEVPATH         "eeprom.txt"


#define SN_OFFSET              0x0
#define SN_LEN                 14  /* Serial Number: LY-FL2440-001 */


#define MAC_OFFSET             0x10
#define MAC_LEN                18  /* MAC address  : 00:05:69:00:00:01 */


#define USER_OFFSET            0x30
#define USER_LEN               24  /* Ower name    : guowenxue  */


#define MODE_NONE              0
#define MODE_WR                1
#define MODE_RD                2


int write_eeprom(int fd, int offset, char *data, int len);
int read_eeprom(int fd, int offset, char *buf, int size);


static void prog_usage(const char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf("This program used to write/read SN,MAC,Name into/from EEPROM\n");


    printf(" -r[read    ]  read SN,MAC,Name from EEPROM\n");


    printf(" -s[sn      ]  Specify intgegrate serial number, such as 10. Will use it generate SN and MAC address.\n");
    printf(" -u[sn      ]  Specify user name, string such as \"lingyun\"\n");


    printf(" -h[help    ]  Display this help information\n");
    return ;
}




int main (int argc, char **argv)
{
    int                          fd = -1;
    int                          sn = 0;
    char                        *user = NULL;
    char                         buf[EEPROM_SIZE];


    const char                  *progname=NULL;
    int                          opt;
    int                          rwmode = MODE_NONE;
    int                          oflags;


    struct option long_options[] = 
    {
        {"read", no_argument, NULL, 'r'},
        {"sn", required_argument, NULL, 's'},
        {"user", required_argument, NULL, 'u'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };


    progname = basename(argv[0]);


    /*  Parser the command line parameters */
    while ((opt = getopt_long(argc, argv, "rs:u:h", long_options, NULL)) != -1)
    {
        switch (opt)
        {
            case 'r':
                rwmode = MODE_RD;
                break;


            case 's':  /*  serial number */
                sn = atoi(optarg);
                rwmode = MODE_WR;
                break; 
            
            case 'u':  /*  user name */
                user= optarg;
                rwmode = MODE_WR;
                break; 
            
            case 'h':  /*  Get help information */
                prog_usage(progname);
                return 0; 
            
            default:
                break;
        } /*   end of "switch(opt)" */
    }


    if( MODE_NONE == rwmode )
    { 
        prog_usage(progname);
        return 0; 
    }




    if( MODE_WR==rwmode )
        oflags=O_RDWR|O_CREAT|O_TRUNC;
    else
        oflags=O_RDWR;




    if( (fd=open(EEPROM_DEVPATH, oflags, 0644)) < 0 )
    {
        printf("Open EEPROM device '%s' failure: %s\n", EEPROM_DEVPATH, strerror(errno));
        return -1;
    }


    if( MODE_WR == rwmode )
    { 
        if( sn )
        {
            /* Generate and write serial number into EEPROM  */ 
            snprintf(buf, SN_LEN, "LY-FL2440-%03X", sn);
            if( write_eeprom(fd, SN_OFFSET, buf, SN_LEN) < 0)
            {
                printf("Write serial number into EEPROM failure\n");
                goto cleanup;
            }


            /* Generate and write MAC address into EEPROM  */ 
            snprintf(buf, MAC_LEN, "00:05:69:00:00:%02d", sn);
            if( write_eeprom(fd, MAC_OFFSET, buf, MAC_LEN) < 0)
            {
                printf("Write MAC address into EEPROM failure\n");
                goto cleanup;
            }
        } 
        
        if( user )
        {
            /* Write board user name  */
            if( write_eeprom(fd, USER_OFFSET, user, strlen(user)) < 0)
            {
                printf("Write user name into EEPROM failure\n");
                goto cleanup;
            }
        }
    }
    else if( MODE_RD == rwmode )
    { 
        char                   sn[SN_LEN];
        char                   mac[MAC_LEN];
        char                   user[USER_LEN];


        /*  Read serial number from EEPROM  */ 
        memset(sn, 0, sizeof(sn));
        if( read_eeprom(fd, SN_OFFSET, sn, SN_LEN) < 0)
        {
            printf("Read serial number from EEPROM failure\n");
            goto cleanup;
        }
        printf("Serial Number: %s\n", sn); 
        
        /*  Read MAC address from EEPROM  */ 
        memset(mac, 0, sizeof(mac));
        if( read_eeprom(fd, MAC_OFFSET, mac, MAC_LEN) < 0)
        {
            printf("Read MAC address from EEPROM failure\n");
            goto cleanup; 
        }
        printf("MAC Address  : %s\n", mac);


        
        /*  Read user name from EEPROM  */ 
        memset(user, 0, sizeof(user));
        if( read_eeprom(fd, USER_OFFSET, user, USER_LEN) < 0)
        {
            printf("Read user name from EEPROM failure\n");
            goto cleanup;
        }
        
        printf("User Name    : %s\n", user);
    }




cleanup:
    close(fd);


    return 0;





int write_eeprom(int fd, int offset, char *data, int len)
{
    if( fd<0 || (offset<0||offset>=EEPROM_SIZE) || !data || len<=0 )
    {
        printf("Invalid input arguments for %s()\n", __func__);
        return -1;
    }


    lseek(fd, offset, SEEK_SET);


    if( write(fd, data, len) != len )
    {
        printf("write data into EEPROM failure: %s\n", strerror(errno));
        return -2;
    
    }


    return 0;
}




int read_eeprom(int fd, int offset, char *buf, int size)
{
    if( fd<0 || (offset<0||offset>=EEPROM_SIZE) || !buf || size<=0 )
    {
        printf("Invalid input arguments for %s()\n", __func__);
        return -1;
    }


    lseek(fd, offset, SEEK_SET);


    if( read(fd, buf, size) < 0 )
    {
        printf("read data from EEPROM failure: %s\n", strerror(errno));
        return -2;
    
    }


    return 0;
}




  


你可能感兴趣的:(linux环境下C语言编程)