首页 > 代码库 > 第3章 文件I/O(4)_dup、dup2、fcntl和ioctl函数

第3章 文件I/O(4)_dup、dup2、fcntl和ioctl函数

5. 其它I/O系统调用

(1)dup和dup2函数

头文件

#include<unistd.h> 

函数

int dup(int oldfd);

int dup2(int oldfd, int newfd);

返回值

若成功返回新文件描述符,出错返回-1

功能

文件描述符的复制(将oldfd复制给newfd

参数

old:原先的文件描述符

newfd: 新文件描述符

备注

(1)由dup返回的新文件描述符一定是当前可用文件描述符中最小数值

(2)用dup2则可以用newfd参数指定新描述符的数值。如果newfd己经打开,则先将其关闭。如果old等于newfd,则dup2返回newfd,而不关闭它。

(3)在进程间通信时可用来改变进程的标准输入和标准输出设备。(4)注意,复制的只是3个内核结构中的文件描述符中的文件表项指针也就是newfd与oldfd指向了同一个文件表项。但要注意并不文件表项本身和inode节点项这两个内核数据结构。

【编程实验】1. 自定义的cat命令

//io.h

#ifndef __IO_H__
#define __IO_H__

extern void copy(int fdin, int fdout);//文件复制
extern void set_fl(int fd, int flag); //设置文件状态标志
extern void clr_fl(int fd, int flag); //取消文件状态标志


#endif

//io.c

#include "io.h"
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

//编译命令:gcc -o obj/io.o -Iinclude -c src/io.c

#define BUFFER_LEN 1024 //与分区文件块大小一致。可以通过
                        //tune2fs -l /dev/sda1命令查看
                        

void copy(int fdin, int fdout)
{
    char buffer[BUFFER_LEN];
    
    ssize_t size;

    //保证从文件开始处复制
    lseek(fdin, 0L, SEEK_SET); 
    lseek(fdout, 0L, SEEK_SET); 

    while((size = read(fdin, buffer, BUFFER_LEN)) > 0){
        if(write(fdout, buffer, size) != size)
        {
           fprintf(stderr, "write error: %s \n", strerror(errno));
           exit(1);
        }
    }

    if (size < 0 )
    {
        fprintf(stderr, "read error: %s\n",strerror(errno));
        exit(1);  //return 1;
    }   
}

void set_fl(int fd, int flag) //设置文件状态标志
{
    //获取原来的文件状态标志
    int val = fcntl(fd, F_GETFL);

    //增加新的文件状态标志
    val |= flag;

    //重新设置文件状态标志
    if(fcntl(fd, F_SETFL, val) < 0)
    {
        perror("fcntl error");
    }
}

void clr_fl(int fd, int flag) //取消文件状态标志
{

    //获取原来的文件状态标志
    int val = fcntl(fd, F_GETFL, val);

    //清除指定的文件状态标志(置0)
    val &= ~flag;

    //重新设置文件状态标志
    if(fcntl(fd, F_SETFL, val) < 0 )
    {
        perror("fcntl error");
    }
}

//cat.c

#include "io.h"
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

//模拟cat命令,设编译后的程序名也叫cat
//实验1:bin/cat file1.txt file2.txt
//实验2:bin/cat < file1.txt
//实验3:bin/cat > file1.txt

int main(int argc, char* argv[])
{
    int fd_in = STDIN_FILENO;    //0
    int fd_out = STDOUT_FILENO;  //1

    int i = 0;
    for(i=1; i<argc; i++){
        //1.输入cat file1.txt file2.txt时会将file1和file2
        //  分别拷贝到标准输出,即输出到屏幕
        fd_in = open(argv[1], O_RDONLY);
        if(fd_in < 0)
        {
            perror("open error");
            continue;
        }

        copy(fd_in, fd_out);
        close(fd_in);
    }
    //2.如果cat命令后面不带参数,则直接接受键盘输入。
    //3.如果输入cat < file1.txt,由于<表示输入重定向,Linux
    //会将file1.txt这个参数作为输入,复制给STDIN_FILENO。而不会将其
    //传给argv参数。即这种情况下,argc==1,表示程序本身,argv中不包含file1.txt。
    if(argc == 1) copy(fd_in, fd_out);
    
    return 0;
}

【编程实验】2.dup和dup2实现重定向

//io.h和io.c与上例相同

#include "io.h"
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
/*
 * bin/mcat + file1.txt (+为输入重定向)
 * bin/mcat - file1.txt (-为输出重定向)
 */

int main(int argc, char* argv[])
{
    int fd_in;
    int fd_out;

    int i=0;

    for(i=1; i<argc; i++){
        if(!strcmp("+", argv[i])){
            fd_in = open(argv[++i], O_RDONLY);
            if(fd_in < 0){
                perror("open error");
                exit(1);
            }

            //将标准输入重定向到文件(即,将fd_in的文件表项指针复制给STDIN_FILENO
            //因此,STDIN_FILENO文件描述符与fd_in就同时指向同一个文件表项,也就
            //同时指向了file1.txt这个文件。
            if(dup2(fd_in, STDIN_FILENO) != STDIN_FILENO){
                perror("dup2 error");
                exit(1);
            }

            close(fd_in);
        }else if(!strcmp("-", argv[i])){

            fd_out = open(argv[++i], O_WRONLY | O_CREAT | O_TRUNC, 0777);
            if(fd_out < 0){
                perror("open error");
                exit(1);
            }

            //将标准输出重定向到文件(即,将fd_out的文件表项指针复制给STDOUT_FILENO
            //因此,STDOUT_FILENO文件描述符与fd_out就同时指向同一个文件表项,也就
            //同时指向了file1.txt这个文件。
            if(dup2(fd_out, STDOUT_FILENO) != STDOUT_FILENO){
                perror("dup2 error");
                exit(1);
            }

            close(fd_out);
        }else{
              fd_in = open(argv[i], O_RDONLY);
              if(fd_in < 0){
                  perror("open error");
                  exit(1);
              }

              if(dup2(fd_in, STDIN_FILENO) != STDIN_FILENO)
              {
                  perror("dup2 error");
                  exit(1);
              }

              close(fd_in);
        }

        copy(STDIN_FILENO, STDOUT_FILENO);
    }

   //命令后面不带参数的情况
   if(argc ==1)
      copy(STDIN_FILENO, STDOUT_FILENO);
      
    return 0;
}

(2)fcntl函数

头文件

#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, struct flock);

返回值

若成功则依赖于cmd,出错为-1。

功能

可以改变己经打开文件的性质常见的功能:

(1)复制一个现存的描述符,新文件描述符作为函数返回值(cmd=F_DUP)

(2)获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD)

(3)获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)

(4)获取/设置文件锁(cmd=F_SETLK、F_GETL或F_SETKLW),此时第3个参数为struct flock结构体。

参数

cmd的常见取值:

(1)F_DUPFD:复制文件描述符,新的文件描述符作为函数返回值返回。

(2)F_GETFD/F_SETFD:获取/设置文件描述符,通过第3个参数设置。

(3)F_GETFL/F_SETFL:获取/设置文件状态标志,通过第3个参数设置。可以更改的标志有:O_APPEND、O_NONBLOCK、O_SYNC、O_ASYNC但要注意,O_RDONLY、O_WRONLY和O_RDWR不适用。

【编程实验】设置文件状态标志

//io.h和io.c文件与上例相同

//file_append_flag.c

#include "io.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> //exit
#include <string.h> //strlen
#include <fcntl.h>  //O_WRONLY

int main(int argc, char* argv[]){
    if( argc < 3){
        printf("usage: %s content destfile\n", argv[0]);
        exit(1);
    }

    int fd = open(argv[2], O_WRONLY); //注意,这里没设置为追加
    
    if(fd < 0){
        perror("open error");
        exit(1);
    }

    //设置追加文件标志
    set_fl(fd, O_APPEND);
    
    sleep(10); //为了把定位与写入过程隔开,以便演示多进程同时写入同一文件
               //时会出现后启动进程格覆盖之前进程写过的内容。
               
    //往文件尾部追加内容
    size_t size = strlen(argv[1])*sizeof(char);
    if(write(fd, argv[1], size)!=size){
        perror("write error");
        exit(1);
    }

    close(fd);

    return 0;
}

(3)ioctl函数

头文件

#include<unistd.h>

#include <sys/ioctl.h>

函数

int ioctl(int fd, int request,…);

返回值

成功为0,出错为-1

功能

控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段

备注

I/O操作的杂物箱。不能用本章中其他函数表示的I/O操作通常都能用ioctl表示。终端I/O是ioctl的最大使用方面,主要用于设置的I/O控制。

【编程实验】读取键盘内容

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>  //for ioctl  
#include <linux/input.h> //keyboard

//说明本程序须在虚拟机,而不能在远程终端上运行!

int main(int argc, char * argv[])
{
    int fd = -1;
    char name[256]="unknow";
    struct input_event event;

    int ret = 0;

    if(( fd = open("/dev/input/event2", O_RDONLY)) < 0){
        perror("open error");
        exit(1);
    }

    //通用EVIOCGNAME命令获取设备名字
    if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0){
        perror("evdev ioctl error\n");
        exit(1);
    }

    printf("The device‘s name is %s\n", name);
    
    while(1)
    {   
        ret = read(fd, &event, sizeof(event));
        if(ret < 0){
            printf("read event error\n");
        }

        if(EV_KEY == event.type) //EV_KEY表示按键类事件,类似的有EV_PWR(电源)、EV_SND(声音)事件
        {
            //if the event is a key code
            printf(" key code is %d \n", event.code);

            //quit,press ‘q‘ to quit this application.
            if(event.code == 16){
                break;
            }
        }
    }

    return 0;
}

第3章 文件I/O(4)_dup、dup2、fcntl和ioctl函数