Ⅰ. lseek/fcnt/ioctl/mmap 高级控制

lseek

每个打开的文件都记录着当前读写位置,打开文件时读写位置是0, 表示文件开头,通常读写多少个字节就会将读写位置往后移多少个字节。
但是有一个例外,如果以 O_APPEND 方式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。 lseek 和标准I/0 库的 fseek 函数类似。

#include <sys/types.h> 
#include <unitsd.h>

off_t lseek(int fd, off_t offset, int whence);
  1. 返回值:当前读写位置,相当于ftell(FILE* stream)
  2. 参数 whence 为下列其中一种:
    • SEEK_SET 参数offset 即为新的读写位置(即以文件头位置为起始位置,将读写位置移动offset个字节,此时offset不能为负值)
    • SEEK_CUR 以当前的读写位置增加offset个位移量
    • SEEK_END 将读写位置指向文件尾后再增加offset个位移量(即以文件末尾尾位置为起始位置,将读写位置移动offset个字节)

      当whence 值为SEEK_CUR 或SEEK_END 时, 参数offet 允许负值的出现

fcntl

可以用 fcnt 函数改变一个已打开的文件的属性而不必重新 open 文件,可以重新设置读、写、追加、非阻塞等标志。

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, flock *lock);

除了F_GETFL和F_SETFL命令之外,fcntl还有很多命令做其它操作,例如设置文件记录锁等。可以通过fcntl设置的都是当前进程如何访问设备或文件的访问控制属性

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/*
将阻塞的标准输入改为非阻塞的
*/
int main() {
    int flags;
    flags = fcntl(STDIN_FILENO, F_GETFL);  // 获取标准输入的所有标志
    if (flags < 0) {
        perror("fcntl get flags");
        exit(-1);
    }
    flags |= O_NONBLOCK;  // 在原先的基础上加上非阻塞权限
    flags = fcntl(STDIN_FILENO, F_SETFL, flags);
    if (flags < 0) {
        perror("fcntl set flags");
        exit(-2);
    }

    char buf[10];
    ssize_t n;
    while (1) {
        n = read(STDIN_FILENO, buf, 5);
        if (n > 0) {
            break;  //读到东西了
        }

        if (errno != EAGAIN) {
            perror("read");
            exit(-3);
        }
        write(STDOUT_FILENO, "try again\n", 10);  // 本次没读到
        sleep(1);
    }
    write(STDOUT_FILENO, buf, n);

    return 0;
}
/*
获取文件的权限
*/
int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("usage: cmd filename\n");
        exit(-1);
    }
    int flags;
    flags = fcntl(atoi(argv[1]), F_GETFL);
    if (flags < 0) {
        perror("fcntl get flags");
        exit(-2);
    }
    printf("flags=%d\n", flags);

    switch (flags & O_ACCMODE) {
        case O_RDONLY:
            printf("read only");
            break;
        case O_WRONLY:
            printf("write only");
            break;
        case O_RDWR:
            printf("read write");
            break;
        default:
            printf("invaild access mode\n");
    }

    if (flags & O_APPEND) {
        printf(", append");
    }
    if (flags & O_NONBLOCK) {
        printf(", nonblock");
    }

    return 0;
}

ioctl

ioctl用于向设备发控制和配置命令

#include <sys/ioctl.h>

int ioctl(int d, int request, ...);
  1. d是某个设备的文件描述符
  2. request 是 ioct 的命令,可变参数取決于 request,通常是一个指向变量或结构体的指针
  3. 若出错则返回-1, 若成功返回其他值,返回值也是取决于 request
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main() {
    struct winsize size;
    if (!isatty(1)) {  //判断是否是终端
        printf("1 is not tty\n");
        exit(-1);
    }
    int rtn = ioctl(1, TIOCGWINSZ, &size);  //获取终端尺寸size
    if (rtn < 0) {
        perror("ioctl");
        exit(-2);
    }
    printf("%d rows, %d columns\n", size.ws_row, size.ws_col);

    return 0;
}

mmap

mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来做而不需要 read/ write 函数

#include <sys/mmap.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Linux系统编程笔记(4)——文件与I/O(3)-萤火
  1. addr:如果 addr 参数为 NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果 addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择 adrr 之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。
  2. length:是需要映射的那一部分文件的长度
  3. offset:从文件的什么位置开始映射,必须是页大小的整数倍
  4. fd:该文件的描述符
  5. prot:有四种取值:
    • PROT_EXEC 表示映射的这一段可执行,例如映射共享库
    • PROT_READ 表示映射的这一段可读
    • PROT_WRITE:表示映射的这一段可写
    • PROT_NONE 表示映射的这一段不可访问
  6. flag:
    • MAP_SHARED 多个进程对相同文件映射共享
    • MAP_PRIVATE 多个进程对相同文件映射不共享
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

/*
使用mmap修改文件test.txt,可以通过命令od -tx1 -tc test.txt观察程序执行前后的变化
*/
int main() {
    int fd = open("./test.txt", O_RDWR);
    if (fd < 0) {
        perror("open file");
        exit(-1);
    }

    // int *p = mmap(NULL, 6, PROT_WRITE, MAP_SHARED, fd, 0);  // p是指向int类型的指针变量,一次可以修改4个字节
    // p[0] = 0x30313233;                                      // 修改前4个字节,注意高低位,33是低位
    // ((int *)(((char *)p) + 1))[0] = 0x30313233;  //第一个字节不修改,改第2-5个字节

    char *p = mmap(NULL, 6, PROT_WRITE, MAP_SHARED, fd, 0);  // p是指向char类型的指针变量,一次可以修改1个字节
    p[0] = 'A';
    p[1] = 'B';
    p[2] = 'C';
    munmap(p, 6);  // 释放映射的地址

    return 0;
}