linux gcc编译下的文件读写操作

linux下的文件操作

所有目录
            1.文件及文件系统的定义
            2.linux文件的类型
            3.linux文件的权限
            4.文件操作
                    4.1 文件的创建
                    4.2 文件的打开及关闭
                    4.3 文件的读写操作
                    4.4 文件上锁
                    4.5 文件的定位
                    4.6 特殊文件的操作
                             4.6.1 目录文件的操作
                             4.6.2 链接文件的操作
            5.部分函数说明
*************************************************************
正文
*************************************************************
1.文件及文件系统的定义
       文件是指有名字的一组相关信息的集合。文件系统是指按照一定规律组织起来的有序的文件组织结构,是构成系统中所有数据的基础。linux系统中,文件的准确定义是不包含有任何其他结构的字符流。换句话说,文件中的字符和字符之间除了同属于一个文件之外,不存在任何其他的关系。linux系统提供的文件系统,是树形层次结构系统。Linux中常用的文件系统主要有ext3、ext2及reiserfs 。
2.linux文件的类型
       linux下最常见的文件类型有5种,它们是普通文件,目录文件,链接文件,字符设备文件和块设备文件,管道文件,套接口文件。
       我们用 ls -lh 来查看某个文件的属性,可以看到有类似 -rw-r--r-- ,值得注意的是第一个符号是 - ,这样的文件在Linux中就是普通文件。这些文件一般是用一些相关的应用程序创建,比如图像工具、文档工具、归档工具... .... 或 cp工具等。这类文件的删除方式是用rm 命令。当我们在某个目录下执行,看到有类似 drwxr-xr-x ,这样的文件就是目录,目录在Linux是一个比较特殊的文件。注意它的第一个字符是d。创建目录的命令可以用 mkdir 命令,或cp命令,cp可以把一个目录复制为另一个目录。删除用rm 或rmdir命令。我们可以看到/dev/tty的属性是 crw-rw-rw- ,注意前面第一个字符是 c ,这表示字符设备文件。比如猫等串口设备我们看到 /dev/hda1 的属性是 brw-r----- ,注意前面的第一个字符是b,这表示块设备,比如硬盘,光驱等设备;这个种类的文件,是用mknode来创建,用rm来删除。目前在最新的Linux发行版本中,我们一般不用自己来创建设备文件。因为这些文件是和内核相关联的。当我们启动MySQL服务器时,会产生一个mysql.sock的文件。注意这个文件的属性的第一个字符是 s。我们了解一下就行了。当我们查看文件属性时,会看到有类似 lrwxrwxrwx,注意第一个字符是l,这类文件是链接文件。是通过ln -s 源文件名 新文件名 。上面是一个例子,表示setup.log是install.log的软链接文件。怎么理解呢?这和Windows操作系统中的快捷方式有点相似。
        这儿有个程序,他可以列出当前目录下的文件信息,以及系统“/dev/sda1”和“/dev/lp0”的文件信息。
#include
#include
#include
#include
#include
#include
int main()
{
    int newret;
    printf("列出当前目录下的文件信息:\n");
    newret=system("ls -l");
    printf("列出“/dev/sda1”的文件信息:\n");
    newret=system("ls /dev/sda1 -l");
    printf("列出“/dev/ lp0”的文件信息:\n");
    newret=system("ls /dev/lp0 -l");
    return 0;
}
大家可以调试一下。其中system()函数说明如下:表头文件
        #i nclude
定义函数
        int system(const char * string);
函数说明
        system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值
  =-1:出现错误  
  =0:调用成功但是没有出现子进程  
  >0:成功退出的子进程的id
        如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。 如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。
附加说明
        在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。
3.文件的权限
        linux文件的权限可分为四种:可读取(Readable),可写入(writable),可执行(eXecute)和无权限,分别用r,w,x和-表示。在linux系统中,对于任意一个文件来说,他都有一个特定的所有者,也是创建此文件的用户,同时,由于linux中用户是按组分类的,一个用户属于一个或多个组。文件所有者以外的用户,又可以分为文件所有者的同组用户和其他用户。因此,linux系统按文件所有者,文件所有者同组用户和其他用户三类规定了了不同的文件访问权限。用ls -lh显示的作为权限的10个字符,可分为四部分:1).第一位:一般表示文件类型。2).第二到第四位:表示文件所有者的访问权限。3).第五位到第七位:表示文件所有者同组用户的访问权限。4).第八位到第十位:表示其他用户的访问权限。上面介绍了system()函数,可以用system函数编程来实现对文件权限的修改。当然还有个chmod()函数也可以用来设置文件的权限。如果希望每个新建的文件都能被赋予某种权限,则需要由系统的权限掩码设置函数umask来决定。下面关于umask函数的用法有个例子。
#include
#include
#include
#include
#include
#include
int main()
{
    mode_t new_umask,old_umask;
    new_umask=0666;
    old_umask=umask(new_umask);
    printf("系统原来的权限掩码是:%o\n",old_umask);
    printf("系统新的权限掩码是:%o\n",new_umask);
    system("touch hu1");
    printf("创建了文件hu1\n");
    new_umask=0444;
    old_umask=umask(new_umask);
    printf("系统原来的权限掩码是:%o\n",old_umask);
    printf("系统新的权限掩码是:%o\n",new_umask);
    system("touch hu2");
    printf("创建了文件hu2\n");
    system("ls hu1 hu2 -l");
    return 0;
}
该程序的运行结果是:系统原来的权限掩码是:22
系统新的权限掩码是:666
创建了文件hu1
系统原来的权限掩码是:666
系统新的权限掩码是:444
创建了文件hu2
---------- 1 hubin hubin 0 2009-08-02 11:07 hu1
--w--w--w- 1 hubin hubin 0 2009-08-02 11:07 hu2
程序结果说明:先将系统的权限掩码为0666,所以新建的文件hu1访问权限为0000,即"--------".再将系统的权限掩码为0444,所以新建的文件hu2访问权限为0444,即"--w--w--w-".语句system("touch hu1")的作用是调用system函数来运行shell命令"touch hu1",touch命令的作用是更改时间标记,如文件不在,则新建文件。
unmask函数说明:
所需头文件:#include
                    #include
函数功能:设置建立新文件时的权限掩码
函数原型:mode_t umask(mode_t mask);
函数传入值:4位八进制数
函数返回值:返回值为原先系统的umask值
       要想获得文件的其他属性,可以使用stat函数或者是fstat函数,其函数说明如下,例子见附件的st_mode.c.
函数原型
#include
int stat(const char *restrict pathname, struct stat *restrict buf);
提供文件名字,获取文件对应属性。
int fstat(int filedes, struct stat *buf);
通过文件描述符获取文件对应的属性。
int lstat(const char *restrict pathname, struct stat *restrict buf);
连接文件描述命,获取文件属性。
 
2 文件对应的属性
struct stat {
        mode_t     st_mode;       //文件对应的模式,文件,目录等
        ino_t      st_ino;       //inode节点号
        dev_t      st_dev;        //设备号码
        dev_t      st_rdev;       //特殊设备号码
        nlink_t    st_nlink;      //文件的连接数
        uid_t      st_uid;        //文件所有者
        gid_t      st_gid;        //文件所有者对应的组
        off_t      st_size;       //普通文件,对应的文件字节数
        time_t     st_atime;      //文件最后被访问的时间
        time_t     st_mtime;      //文件内容最后被修改的时间
        time_t     st_ctime;      //文件状态改变时间
        blksize_t st_blksize;    //文件内容对应的块大小
        blkcnt_t   st_blocks;     //伟建内容对应的块数量
      };
 
可以通过上面提供的函数,返回一个结构体,保存着文件的信息。
4.文件操作
        Linux系统把目录,设备和连接等操作都等同于文件的操作。它通过一个文件描述符来进行区分和引用特定的文件。文件描述符是一个非负的整数,是一个索引值,指向内核中每个进程打开的文件的记录表。
        Linux系统中,基于文件描述符的文件的操作有:不带缓存的文件I/O操作和带缓存的流文件I/O操作。不带缓存的文件I/O操作又称系统调用I/O操作,符合POSIX标准,设计的程序能在兼容POSIX标准的系统间方便的移植。主要的函数有:creat,open,close,read,write,lseek,flock,fcntl等。带缓存的流文件I/O操作是在内存开辟一个“缓存区”,为程序中的每一个文件使用。带缓存的文件I/O操作又称标准I/O操作,符合ANSI C标准,比不带缓存的文件I/O程序方便移植。主要函数有fopen,fclose,fgetc,fputc,fgets,fread,fwrite,fseek,rewind,ftell等。由于流文件的I/O操作在C语言经常用到,一般的C语言书上也有介绍,所以就不多说了,其函数原型见部分函数说明。
4.1 文件的创建。
       在linuxC程序设计中,创建文件可以调用creat函数。
       int creat(const char *filename, mode_t mode);
  参数mode指定新建文件的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:下面有个例子说明该函数的用法,程序是在“/home”目录下创建一个“file”的文件,并把此文件的权限为所有者具有只读权限。
#include
#include
#include
#include
#include
#include
int main()
{
    int fd;
    fd=creat("/home/file",S_IRUSR);/*所有者具有只读权限*/
    system("ls /home/file -l");
    return 0;
}
有兴趣的可以调一下。如果所创建的文件原本就存在,则就不能创建该文件了。
附creat函数的说明:
creat函数:创建一个文件
相关头文件:#include
函数表达式:int creat(const char *pathname,mode_t mode);
参数说明:参数pathname表示需要创建的文件的路径。参数mode表示文件的文件权限。
返回值说明:如果成功创建一个文件则返回新的文件描述符,失败则返回-1.
函数功能详解:creat函数创建一个新文件,并以“只写”的方式打开,返回新文件的文描述符。
函数使用说明:
          crea函数用于创建一个文件,并且将其以“只写” 形式打开。因此程序是无法读该文件的。如果需要读这个新文件中的内容,则必须将其关闭后从新打开。
          creat函数的第一个参数表示的文件不存在,则创建该文件并且以“只写”方式打开;如果该文件爱你存在则将其截短谓0,再以“只写”方式打开。因此使用creat函数要注意文件重名时可能带来的问题。以下是定义于 <sys/stat.h> 中的九种文件访问权限位(用于构成参数 mode):
          S_IRUSR   // user-read(文件所有者读)
          S_IWUSR   // user-write(文件所有者写)
          S_IXUSR   // user-execute(文件所有者执行)
          S_IRGRP   // group-read
          S_IWGRP   // group-write
          S_IXGRP   // group-execute
          S_IROTH   // other-read
          S_IWOTH   // other-write
          S_IXOTH   // other-execute
其中 user 指文件所有者,group 指文件所有者所在的组,other 指其他用户。
creat 函数只能以只读方式创建新文件。如果我们要以读写方式创建新文件,可以用 open 函数:由于open函数比creat函数好用,所以open函数用得较多。
4.2 文件的打开和关闭
      文件的打开可以用open,fopen函数。关闭用close和fclose函数。open和close属于不带缓存的文件I/O操作而fopen和fclose函数属于流文件操作。当用open函数时,即使原来的文件不存在,也可以用open函数创建文件。在打开或者创建文件时,可以指定文件的属性及用户的权限等参数。close函数则关闭一个打开的文件而fopen和fclose函数在C语言编程就用得很多了,这里就不举例子
例:要求在“./home”下以可读写的方式打开一个名为"file"的文件。如果该文件不存在,则创建此文件;如果存在,将文件清空后关闭。
#include
#include
#include
int main()
{
    int fd;
    if((fd=open("/home/file",O_CREAT|O_TRUNC|O_WRONLY,0600))<0)
    {
          printf("打开文件出错");
          exit(1);
    }
    else
    {
          printf("打开(创建)文件“file”,文件描述符为:%d",fd);
    }
    if(close(fd)<0)
    {
          printf("关闭文件出错");
          exit(1);
    }
    system("ls /home/file -l");
    return 0;
}
open 函数的简单描述
#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t mode */);
返回值:成功则返回文件描述符,否则返回 -1
对于 open 函数来说,第三个参数(...)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 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设置为非阻塞模式(nonblocking mode)
以下三个常量同样是选用的,它们用于同步输入输出
      O_DSYNC        等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
      O_RSYNC        read 等待所有写入同一区域的写操作完成后再进行
      O_SYNC         等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
open 返回的文件描述符一定是最小的未被使用的描述符。
close函数说明:
头文件:#include <unistd.h>
函数原型:int close(int filedes);
返回值:0(成功)或者 -1(出错)
进程结束时,该进程打开的所有文件都会自动被内核(kernel)关闭。
4.3 文件的读写
     当文件按指定的工作方式打开以后,就可以执行对文件的读和写。下面按文件的性质分类进行操作。针对文本文件和二进制文件的不同性质,对文本文件来说,可按字符读写或按字符串读写;对二进制文件来说,可进行成块的读写或格式化的读写。 下面是流文件的读写操作。
1. 读写字符
    C提供fgetc和fputc函数对文本文件进行字符的读写,其函数的原型存于stdio.h头文件中,格式为:
    int fgetc(FILE *stream)
    fgetc( )函数从输入流的当前位置返回一个字符,并将文件指针指示器移到下一个字符处,如果已到文件尾,函数返回EOF,此时表示本次操作结束,若读写文件完成,则应关闭文件。
    int fputc(int ch,FILE *stream)
    fputc()函数完成将字符c h的值写入所指定的流文件的当前位置处,并将文件指针后移一位。fputc()函数的返回值是所写入字符的值,出错时返回EOF
2. 读写字符串
    C提供读写字符串的函数原型在stdio.h头文件中,其函数形式为:
    Char *fgets(char *str,int num,FILE *stream)
    fgets() 函数从流文件stream中读取至多num-1个字符,并把它们放入str指向的字符数组中。读取字符直到遇见回车符或E O F(文件结束符)为止,或读入了所限定的字符数。
    int fputs(char *str,FILE *stream)
    fputs( )函数将str指向的字符串写入流文件。操作成功时,函数返回0值,失败返回非零值
3. 格式化的读写
前面的程序设计中,我们介绍过利用scanf( )和printf( )函数从键盘格式化输入及在显示器上进行格式化输出。对文件的格式化读写就是在上述函数的前面加一个字母f成为fscanf( )和fprintf( )。其函数调用方式:
int fscanf(FILE *stream,char *format,arg_list)
int fprintf(FILE *stream,char *format,arg_list)
其中,stream为流文件指针,其余两个参数与scanf( )和printf( )用法完全相同。
4. 成块读写
前面介绍的几种读写文件的方法,对其复杂的数据类型无法以整体形式向文件写入或从文件读出。C语言提供成块的读写方式来操作文件,使其数组或结构体等类型可以进行一次性读写。成块读写文件函数的调用形式为:
int fread(void *buf,int size,int count,FILE *stream)
int fwrite(void *buf,int size,int count,FILE *stream)
fread()函数从stream 指向的流文件读取count (字段数)个字段,每个字段为size(字段长度)个字符长,并把它们放到b u f(缓冲区)指向的字符数组中。
fread()函数返回实际已读取的字段数。若函数调用时要求读取的字段数超过文件存放的字段数,则出错或已到文件尾,实际在操作时应注意检测。
fwrite( )函数从buf(缓冲区)指向的字符数组中,把count(字段数)个字段写到stream所指向的流中,每个字段为size个字符长,函数操作成功时返回所写字段数。
关于成块的文件读写,在创建文件时只能以二进制文件格式创建。
       这些流文件的读写操作我们在C语言中经常用到,所以就不举例子了,下面的是不带缓存的文件读写,主要用到read函数,write函数和lseek函数。read函数用于将指定的文件描述符中读出数据。write函数用于向打开的文件写数据,写操作从文件当前位置开始。lseek函数用于在指定的文件描述符中将文件指针定位到相应的位置。
1. read
头文件:#include <unistd.h>
函数原型:ssize_t read(int filedes, void *buf, size_t nbytes);
返回值:读取到的字节数;0(读到 EOF);-1(出错)
read 函数从 filedes 指定的已打开文件中读取 nbytes 字节到 buf 中。以下几种情况会导致读取到的字节数小于 nbytes :
A. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行
C. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。
 D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。
 E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
 F. 在读取了部分数据时被信号中断。
读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
2. write
头文件:#include <unistd.h>
函数原型:ssize_t write(int filedes, const void *buf, size_t nbytes)
返回值:写入文件的字节数(成功);-1(出错)
write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf 。返回值一般总是等于 nbytes,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。
例程:编写一个程序,在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello, software weekly”,关闭该文件。再次打开该文件,读取其中的内容并输出在屏幕上
#include
#include
#include
#include
#include
#include
#define LENGTH 100
main()
{
 int fd, len;
 char str[LENGTH];
 fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 创建并打开文件 */
 if (fd)
 {
  write(fd, "Hello, Software Weekly", strlen("Hello, software weekly")); /* 写入 Hello, software weekly字符串 */
  close(fd);
 }

 fd = open("hello.txt", O_RDWR);
 len = read(fd, str, LENGTH); /* 读取文件内容 */
 str[len] = '\0';
 printf("%s\n", str);
 close(fd);
}
4.4 文件的上锁
      Linux是多用户操作系统,多个用户共同使用,操作同一个文件的事情很容易就发生。linux为了避免这种情况,就给这个文件上锁,以避免共享资源产生竞争,导致数据读写错误。Linux中给文件上锁主要有建议性锁和强制性锁。建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。强制性锁是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。给文件加建议性锁的是flock函数,加强制性锁的是fcntl函数。,一般情况下,系统使用强制性锁,而很少使用建议性锁。
fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。文件描述词,当一个文件打开后,系统会分配一部分资源来保存该文件的信息.以后对文件的操作就可以直接引用该部分资源了,文件描述词可以认为是该部分资源的一个索引,在打开文件时返回.fcntl是用来对文件的一些属性进行设置的,需要一个文件描述词参数.有以下几种情况: F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参 数fd的文件描述词。执行成功则返回新复制的文件描述词。请参考dup2()。F_GETFD取得close-on-exec旗标。若此旗标的 FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。
F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。参数lock指针为flock 结构指针,定义如下
struct flcok
{
short int l_type; /* 锁定的状态*/
short int l_whence;/*决定l_start位置*/
off_t l_start; /*锁定区域的开头位置*/
off_t l_len; /*锁定区域的大小*/
pid_t l_pid; /*锁定动作的进程*/
};
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
返回值 成功则返回0,若有错误则返回-1,错误原因存于errno.
该函数可以改变已打开的文件的性质。
#include
int fcntl(int fields, int cmd, .../* int arg */); //若成功则依赖于cmd,若出错则返回-1
第三个参数总是一个整数,与上面所示函数原型中的注释部分相对应。但是在作为记录锁用时,第三个参数则是指向一个结构的指针。
fcntl函数有5种功能:
1.复制一个现有的描述符(cmd=F_DUPFD).
2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
例子:打开“/home/hubin”下的一个“file”文件,打开后对其加上强制性的写入锁,然后释放写入锁。程序源代码见附件中的lock_set.c.编译成功后,打开两个终端,都执行lock_set,程序结果如下:
终端1:
加上写入锁的是:7800
释放强制性锁:7800
终端2:
加上写入锁的是:7800
加上写入锁的是:7803
释放强制性锁:7803
由程序运行结果可知,此程序在终端1中运行后的第一个进程,先打开或创建文件"file",接着给“file”加上写入锁,锁定文件,并打印输出加锁信息和给文件加锁进程的进程号,这儿加的是“写入锁”,这台机器上第一个给文件加锁进程 的进程号是“7800”,最后等待用户按任意键后解除锁定,并打印输出解锁信息和给文件解锁进程的进程号。如果在第一个进程解除锁定前,此程序在另一个终端中在运行,即运行第二个进程,此时文件已经被第一进程上锁,那么第二个进程无法上锁,只能先打印输出文件已经上锁和第一个进程的进程号的信息。直到前一个进程解锁后,后一个进程才能上锁,解锁。
4.5 文件的定位
      文件中有一个位置指针,指向当前读写的位置。如果顺序读写一个文件,每次读写一个字符,则读写完一个字符后,该位置指针自动移动指向下一个字符位置。当需要改变文
件顺序读写的次序时,根据需要随时指定文件读写的位置,也就是能实现文件的随机读写操作,这种功能是由 C 语言提供的文件定位函数来实现的。 文件的定位函数有lseek,fseek,rewind,ftell等函数。其中lseek是不带缓存的文件操作,其他的是流文件操作。
4.5.1.lseek函数
      所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。使用 lseek 函数可以改变文件的 cfo 。函数说明:
头文件: #include <unistd.h>
函数原型:off_t lseek(int filedes, off_t offset, int whence);
返回值:新的偏移量(成功),-1(失败)
参数 offset 的含义取决于参数 whence:
1. 如果 whence 是 SEEK_SET,文件偏移量将被设置为 offset。
2. 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
3. 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
SEEK_SET、SEEK_CUR 和 SEEK_END 是 System V 引入的,在这之前使用的是 0、1 和 2。
lseek 的以下用法返回当前的偏移量
off_t    currpos;
currpos = lseek(fd, 0, SEEK_CUR);
这个技巧也可用于判断我们是否可以改变某个文件的偏移量。如果参数 fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 为 ESPIPE。对于普通文件(regular file),cfo 是一个非负整数。但对于特殊设备,cfo 有可能是负数。因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 成功与否,而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。lseek 仅将 cfo 保存于内核中,不会导致任何 I/O 操作。这个 cfo 将被用于之后的读写操作。如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。例程:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

 

char    buf1[] = "abcdefghij";
char    buf2[] = "ABCDEFGHIJ";
int main(void)
{
     int  fd, size;
     if ((fd = creat("file.hole", S_IRUSR|S_IWUSR)) < 0)
     {
           printf("creat error\n");
           return -1;
     }
     size = sizeof(buf1);
     if (write(fd, buf1, size) != size)
     {      
            printf("buf1 write error\n");
            return -1;
     }
     /* offset now = 10 */
     if (lseek(fd, 16384, SEEK_SET) == -1)
     {
           printf("lseek error\n");
           return -1;
     }
      /* offset now = 16384 */
     size = sizeof(buf2);
     if (write(fd, buf2, size) != size)
     {
            printf("buf2 write error\n");
            return -1;
     }
      /* offset now = 16394 */
    return 0;
    }
4.5.2 定位读写指针函数fseek ( )
      该函数的功能是将文件的读写指针从某个位置移到指定的位置,该函数将为C语言对文件的随机读写提供了方法。该函数调用格式如下:
fseek(fp, f<偏移量),(起始位置))
其中,fseek是该函数的函数名;fp是指向被操作文件的文件指针;<偏移量)是表示移动当前读写指针的距离量,该参数的类型为long int型;<起始位置)是偏移量的相对位置。例如,起始位置为文件头,偏移量为50,则表示将读写指针移到相对文件头距离为50个字节的位置。起始 位置的设置方法有三;
    0表示相对于文件头
    1表示相对于文件的当前位置
    2表示祖对于文件尾
    实际中,常用宏定义来替代起始位置,规定如下:
    SEEK_SE'C表示文件头
    SEEK_CUR表示当前位置
    SEEK_END表示文件尾
    例如:
    fseek(fp,200L,0);将读写指针移到离文件头20.个字节处。
    fseek(fp, 80L,1);将读写指针移到离当前位置80个字节处。
    fseek (fp , -50L,0);将读写指针移到从文件尾向后退50个字节处。
    该函数一般用于二进制文件。如果用于文本文件要发生字符转换,计算位置时会发生误差。
4.5.3 归位读写指针函数rewind ()
       该函数的功能是将某个文件的读写指针归位于文件头。该函数的调用格式如下:
rewind(fp)其中,rewind是该函数的函数名,fp是被操作文件的文件指针。使用该函数后,会使被操作文件的读写指针指向文件头。该函数的功能与下列函数功能相同。
(seek (fp,OL.0);
4.5.4 返回读写指针函数ftell ( )
       该函数的功能是返回指定文件当前读写指针的位置,该位置是用相对于文件头所相隔的字节数来表示。例如,该函数返回某个文件的当前读写指针的位置是100字节,即表示当前读写指针在离文件头有100个字节处。该函数调用格式如下:ftell(fp)它返回一个表示字节数的long int型数值。
例程:建立一个数据文件,随机读取其中的某个数据。使用fprintf( )函数建立一个数据文件xy. dat ,然后,指定从某个数据起连续读出若干个数据,最后,再读出这组数据的起始数据。程序内容如下:

#inciude

main ( )

{

    int i,x,y;

    FLLE * fp;

    fp=fopen("xy. dat" ,"wb十rb");

    for (i=O;i<20;i++)

      fprintf (fp,,%5d",i+1);

    printf("\nlnput x:").

    scanf(^%d"&x),

    for(i=o;i<5:i十十)

    {

      fseek(fp, (long) (5*(x一l+i),0);

      fscanf (fp ,"%d",&y);

      printf("%d\t",y).

    }

fseek(fp,(Iong)(5*x一5),0):

fscan((fp,"%d",&c ),

rewind(fp);

printf("\n%d\n",&x);

fclose(fp);

4.6 特殊文件的操作
      linux中,除了普通文件外,还有积累重要的特殊文件。特殊文件的操作和普通文件操作类似,最常用的主要有目录文件和链接文件。
4.6.1 目录文件的操作
      对目录文件的操作可以使用mkdir函数,opendir函数,closedir函数,readdir函数和scandir函数。例程:读取系统目录文件"/etc/rc.d"中的所有目录结构。
#include
#include
#include
#include
#include
#include
int main()
{
    DIR *dir;
    struct dirent *ptr;
    int i;
    dir=opendir("/etc/rc.d");
    while((ptr=readdir(dir))!=NULL)
          printf("目录:%s\n",ptr->d_name);
    closedir(dir);
}
opendir函数说明:
所需头文件:#include
                   #include
函数功能:打开目录句柄
函数原型:DIR *opendir(const char *name);
函数传入值:打开参数name指定的目录,并返回DIR *形态的目录流,对目录的读取和搜索都要使用此返回值
函数返回值:成功则返回DIR *形态的目录流,打开失败则返回NULL。
readdir函数说明:
所需头文件:#include
                   #include
                   #include
函数功能:读取目录文件
函数原型:struct dirent *readdir(DIR *dir);
函数传入值:返回参数dir目录流的下个目录进入点
                    dirent结构定义如下:
                    struct dirent
                    {
                          ino_t d_ino;
                          ff_t d_off;
                          signed short int d_reclen;
                          unsigned char d_type;
                          har d_name[256];
                   }
                   d_ino 此目录进入点的inode
                   d_off 目录文件开头至此目录进入点的位移
                   d_reclen_name的长度,不包含NULL字符
                   d_type d_name所指的文件类型
                   s_name 文件名
函数返回值:成功则返回下个目录进入点,有错误发生或读取到目录文件尾则返回NULL
closedir函数说明:
所需头文件:#include
                   #include
函数功能:关闭目录文件
函数原型:int closedir(DIR *dir)
函数传入值:参数dir:目录流
函数返回值:关闭成功则返回0,失败返回-1。
4.6.2 链接文件的操作
       Linux系统中的链接文件,有点类似于Windows系统中的“快捷方式”,但并不完全一样,Linux系统中的链接方式有两种:软链接和硬链接。
1.软链接文件
      软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。链接文件甚至可以链接不存在的文件,这就产生了一般称之为“断链”的问题,链接文件甚至可以循环链接自己,类似于编程语言中的递归。例程:设计一个程序,要求为“/etc/passwd”文件建立软链接“link”,并查看此链接文件和“/etc/passwd”文件。
#include
int main()
{
    symlink("/etc/passwd","link");
    system("ls link -l");
    system("ls /etc/passwd -l");
    return 0;
}
程序运行结果如下:
hubin@hubin-desktop:~/linux_c$ ./slink
lrwxrwxrwx 1 hubin hubin 11 2009-08-03 08:29 link -> /etc/passwd
-rw-r--r-- 1 root root 1487 2009-04-23 17:57 /etc/passwd
从程序运行结果看,在新创建的“link”文件代表权限的10个字符中,第一位是"l",而且最后显“->/etc/passwd”,表明链接目标是"/etc/passwd"文件。
symlink函数说明:
所需头文件:#include
函数功能:建立软链接
函数原型:int symlink(const char *oldpath,const char *newpath);
函数传入值:参数newpath:链接名称
                    参数oldpath:已存在文件路径或文件名
函数返回值:成功返回0,失败返回-1
2. 硬链接文件
       对硬链接文件进行读写和删除操作时候,结果和软链接相同。但如果删除硬链接文件的源文件,硬链接文件依然存在,而且保留了原有的内容,只是,系统就“忘记了”它曾经是硬链接文件,而把它当成一个普通文件。硬链接文件有两个限制:1).不允许给目录创建硬链接;2).只有在同一文件系统中的文件之间才能创建链接。
       例程:设计一个程序,为“/etc/passwd”文件建立硬链接文件“hpasswd”,并查看此链接文件和“/etc/passwd”文件。
#include
int main()
{
    link("/etc/passwd","hardlink");
    system("ls hardlink -l");
    system("ls /etc/passwd -l");
    return 0;
}
程序运行结果:
-rw-r--r-- 1 hubin hubin 0 2009-08-03 08:49 hardlink
-rw-r--r-- 1 root root 1487 2009-04-23 17:57 /etc/passwd
      从程序结果看,两个文件表面上是一样的,除了文件名,新建立的文件跟原文件显示的属性一模一样,而且第二项是“1”,因此,确实建立了一个硬链接文件。
link函数说明:
所需头文件:#include
函数功能:建立硬链接
函数原型:int link(const char *oldpath,const char *newpath);
函数传入值:参数newpath:链接名称
                    参数oldpath:已存在文件路径或文件名
函数返回值:成功返回0,失败返回-1
备注:如果参数newpath指定的名称为一不存在的文件,则不会建立链接。 

5.部分函数说明

chmod -- 改变文件模式

说明

bool chmod ( string filename, int mode)

尝试将 filename 所指定文件的模式改成 mode 所给定的。注意 mode 不会被自动当成八进制数值,而且也不能用字符串(例如 "g+w")。要确保正确操作,需要给 mode 前面加上 0:
chmod ("/somedir/somefile"755);   // 十进制数,可能不对
chmod ("/somedir/somefile""u+rwx,go+rx"); // 字符串,不对
chmod ("/somedir/somefile"0755);  // 八进制数,正确的 mode 值
mode 参数包含三个八进制数按顺序分别指定了所有者、所有者所在的组以及所有人的访问限制。每一部分都可以通过加入所需的权限来计算出所要的权限。数字 1 表示使文件可执行,数字 2 表示使文件可写,数字 4 表示使文件可读。加入这些数字来制定所需要的权限。有关 UNIX 系统的文件权限可以阅读手册“man 1 chmod”和“man 2 chmod”。如果成功则返回 TRUE,失败则返回 FALSE注: 当前用户指的是执行 PHP 的用户。很可能和通常的 shell 或者 FTP 用户不是同一个。 本函数不能作用于远程文件,被检查的文件必须通过服务器的文件系统访问。

 
 

 

你可能感兴趣的:(linux gcc编译下的文件读写操作)