文件描述符 订阅
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。 展开全文
内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
信息
外文名
file descriptor
作    用
内核利用文件描述符来访问文件
中文名
文件描述符
形    式
非负整数
文件描述符提出前提
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。1.进程级的文件描述符表;2.系统级的打开文件描述符表;3.文件系统的i-node表。
收起全文
精华内容
下载资源
问答
  • 文件描述符

    千次阅读 2019-09-11 20:09:40
    文章目录文件描述符的概念常见的文件描述符类型需要使用文件描述符的函数 文件描述符的概念 文件描述符是什么? 文件描述符是一个非负的索引值(一般从3开始,0、1、2已经被使用),指向内核中的 “文件记录表”,...

    文件描述符的概念


    文件描述符是什么?
    文件描述符是一个非负的索引值(一般从3开始,0、1、2已经被使用),指向内核中的 “文件记录表”,内核为进程中要打开的文件维护者一个“文件记录表;

    • 当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符(内核记录表某一栏的索引);
    • 当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
    • Linux 下所有对设备和文件的操作都使用文件描述符来进行。

    常见的文件描述符类型


    一个进程启动时,会默认打开三个文件–标准输入、标准输出和标准出错处理。

    • 0:表示标准输入,对应宏为:STDIN_FILENO,函数 scanf() 使用的是标准输入;
    • 1:表示标准输出,对应宏为:STDOUT_FILENO, 函数 printf() 使用的是标准输出;
    • 2:表示标准出错处理,对应的宏为:STDERR_NO
    • 你也可以使用函数 fscanf()fprintf() 使用不同的 文件描述符 重定向进程的 I/O 到不同的文件。

    需要使用文件描述符的函数


    • 若要访问文件,而且调用的函数又是 writereadopenclose时,就必须用到文件描述符(一般从3开始)。
    • 若调用的函数为 fwritefreadfopenfclose时,就可以绕过直接控制文件描述符,使用的则是与文件描述符对应的文件流
    展开全文
  • 文件描述符文件描述符

    千次阅读 2017-05-16 21:26:52
    文件描述符文件描述符表前面我们介绍过Linux中有一个结构体task_sturct专门用来控制进程叫做进程描述符,在它的里面存放了各种关于进程的信息,其中有一个指针,源码中给出的定义为:struct file_struct *file ,它...

    文件描述符与文件描述符表

    前面我们介绍过Linux中有一个结构体task_sturct专门用来控制进程叫做进程描述符,在它的里面存放了各种关于进程的信息,其中有一个指针,源码中给出的定义为:struct file_struct *file ,它指向一个file_struct结构体,即文件描述符表,每个进程都有一个自己的文件描述符表

    文件描述符

    我们所说的文件描述符(fd)就被写在这个file_struct之中,那么什么是文件描述符呢?

    其实简单来说就是file_struct中的索引。文件描述符表的每一个表项都指向一个打开的文件,用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引 (即0、1、2、3这些数字),这些索引就称为文件描述符,用int 型变量保存。当调用open 打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read 或write ,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。每一个程序打开的时候都会自动打开文件描述符表中的0,1,2,他们分别是标准输入,标准输出和标准错误。

    默认情况下(没有重定向),每个进程的标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)都指向控制终端,因为在程序启动时(在main 函数还 没开始执行之前)会自动把控制终端打开三次,分别赋给三个FILE *指 针stdin 、stdout和stderr,这三个文件指针是libc 中定义的全局变量,这三个文件的描述符分别是0、1、2,保存在相应的FILE 结构体中。进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。

    文件表

    文件描述符里存放的内容就是一个指向文件表的指针。
    Linux中文件描述符的定义

    struct fd {
        struct file *file;//指向文件表的指针
        unsigned int flags;
    };

    它所指向的结构体file就是我们所说的文件表。下面我们来看一下file的源码

    struct file {
        union {
            struct llist_node   fu_llist;
            struct rcu_head     fu_rcuhead;
        } f_u;
        struct path     f_path;//文件的存放路径
        struct inode        *f_inode;   /* cached value */
        const struct file_operations    *f_op;//指向结构体file_operations的指针
    
        /*
         * Protects f_ep_links, f_flags.
         * Must not be taken from IRQ context.
         */
        spinlock_t      f_lock;
        atomic_long_t       f_count;//文件引用计数
        unsigned int        f_flags;//文件状态标记符
        fmode_t         f_mode;
        struct mutex        f_pos_lock;
        loff_t          f_pos;//文件读写位置
        struct fown_struct  f_owner;
        const struct cred   *f_cred;
        struct file_ra_state    f_ra;
    
        u64         f_version;
    #ifdef CONFIG_SECURITY
        void            *f_security;
    #endif
        /* needed for tty driver, and maybe others */
        void            *private_data;
    
    #ifdef CONFIG_EPOLL
        /* Used by fs/eventpoll.c to link all the hooks to this file */
        struct list_head    f_ep_links;
        struct list_head    f_tfile_llink;
    #endif /* #ifdef CONFIG_EPOLL */
        struct address_space    *f_mapping;
    } __attribute__((aligned(4)));  /* lest something weird decides that 2 is OK */

    这个结构中包含了文件的一些基本信息。
    其中几个比较重要的有
    struct path f_path:文件存放的路径,它通过目录表(dentry)找到inode的目录。
    const struct file_operations *f_op:指向文件操作表的指针,这个表里面存放了对文件的操作函数例如:read,write…..
    struct inode *f_inode:指向inode的指针,inode存放了从磁盘上读上来的信息。
    atomic_long_t f_count:文件的引用计数,当引用计数为零的时候文件就关闭了。所以,同时也允许多个多个进程打开一个文件。
    unsigned int f_flags:存放了文件的状态,例如只读,只写,或者可读可写。
    loff_t f_pos:存放当前文件读写的位置。

    展开全文
  • 就像windows中的句柄一样,学习linux经常会碰到文件描述符,我们都知道文件描述符是一个非负整数,每一个文件描述符都唯一对应了一个打开的文件,那么文件描述符在内核中到底是以什么形式存在的呢? 这个问...

    目录

    前言 

    打开文件描述符表

    文件表项

    inode节点

    总结


    前言 

          本文中涉及到的Linux源码来源于linux 2.6.39.4

           就像windows中的句柄一样,学习linux经常会碰到文件描述符,我们都知道文件描述符是一个非负整数,每一个文件描述符都唯一对应了一个打开的文件,那么文件描述符在内核中到底是以什么形式存在的呢?

           这个问题是我在读《UNIX环境高级编程》的时候感到疑惑的,在该书的3.10章节中,关于文件共享有以下一段描述:

            从这段文字中,可以得出一个结论:在内核中每一个打开的文件都需要由3种数据结构来进行维护

            根据文中内容,这三种数据结构分别为:

    1.每个进程对应一张打开文件描述符表,这是进程级数据结构,也就是每一个进程都各自有这样一个数据结构;

    2.内核维持一张打开文件表,文件表由多个文件表项组成,这是系统级数据结构,也就是说这样的数据结构是针对于整个内核而言的,每个进程都可共享的;

    3.每个打开的文件对应一个i节点(i-node)数据结构(Linux下只有i节点没有v节点),由于这是每一个打开的文件与之对应的,因此这也是一个系统级数据结构,存在于内核中,非进程所独有。

           那么,这三种数据结构到底是什么呢?

    打开文件描述符表

           首先来看第一个数据结构—— 打开文件描述符表。

           在Linux中,对于每一个进程,都会分配一个PCB数据结构,它其中包含了该进程的所有信息,而在代码实现上,这个数据结构名为task_struct,在linux源码的include/linux/sched.h中可以找到其定义,它是一个很庞大的结构体,部分定义如下:

    struct task_struct {
    	......
    /* CPU-specific state of this task */
    	struct thread_struct thread;
    /* filesystem information */
    	struct fs_struct *fs;
    /* open file information */
    	struct files_struct *files;
    /* namespaces */
    	struct nsproxy *nsproxy;
    /* signal handlers */
    	struct signal_struct *signal;
    	struct sighand_struct *sighand;
    	......
    };

            在task_struct中,有一个成员变量名为files,注释中提到这是一个描述打开文件信息的变量,其类型为struct files_struct *,那么现在再接着看files_struct,在linux源码的include/linux/fdtable.h中可以找到其定义,定义如下:

    struct files_struct {
      /*
       * read mostly part
       */
    	atomic_t count;
    	struct fdtable __rcu *fdt;
    	struct fdtable fdtab;
      /*
       * written part on a separate cache line in SMP
       */
    	spinlock_t file_lock ____cacheline_aligned_in_smp;
    	int next_fd;
    	struct embedded_fd_set close_on_exec_init;
    	struct embedded_fd_set open_fds_init;
    	struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //进程级打开文件描述符表
    };

           而这里所要找到的打开文件描述符表,实际上就是files_struct 中的成员struct file * fd_array[NR_OPEN_DEFAULT]它是一个指针数组,数组每一个元素都是一个指向file类型的指针,可想而知,这些指针都会指向一个打开的文件,并且file这一数据结构就是用来描述一个打开的文件的,而我们所说的文件描述符,实际上就是这个指针数组的索引。这也是为什么文件描述符是非负整数。

           再来看第二种数据结构——文件表项

    文件表项

           前面说了,每一个打开文件实际上就是用一个file结构体进行描述的,在linux源码的include/linux/fs.h中可以找到其定义,定义如下:

    struct file {
    	/*
    	 * fu_list becomes invalid after file_free is called and queued via
    	 * fu_rcuhead for RCU freeing
    	 */
    	union {
    		struct list_head	fu_list;
    		struct rcu_head 	fu_rcuhead;
    	} f_u;
    	struct path		f_path;     //文件路径,包括目录项以及i-node
    #define f_dentry	f_path.dentry
    #define f_vfsmnt	f_path.mnt
    	const struct file_operations	*f_op;
    	spinlock_t		f_lock;  /* f_ep_links, f_flags, no IRQ */
    #ifdef CONFIG_SMP
    	int			f_sb_list_cpu;
    #endif
    	atomic_long_t		f_count;  //文件打开次数
    	unsigned int 		f_flags;  //文件打开时的flag,对应于open函数的flag参数
    	fmode_t			f_mode;   //文件打开时的mode,对应于open函数的mode参数
    	loff_t			f_pos;    //文件偏移位置
    	struct fown_struct	f_owner;  
    	const struct cred	*f_cred;
    	struct file_ra_state	f_ra;
    
    	u64			f_version;
    #ifdef CONFIG_SECURITY
    	void			*f_security;
    #endif
    	/* needed for tty driver, and maybe others */
    	void			*private_data;
    
    #ifdef CONFIG_EPOLL
    	/* Used by fs/eventpoll.c to link all the hooks to this file */
    	struct list_head	f_ep_links;
    #endif /* #ifdef CONFIG_EPOLL */
    	struct address_space	*f_mapping;
    #ifdef CONFIG_DEBUG_WRITECOUNT
    	unsigned long f_mnt_write_state;
    #endif
    };

           在file结构体中,不得不再说一下它的f_path成员,这是一个struct path类型的变量,该类型定义于include/linux/path.h:

    struct path {
    	struct vfsmount *mnt;
    	struct dentry *dentry;
    };

            这里的dentry实际上就指向文件所在的目录项了,struct dentry的类型定义于include/linux/dcache.h:

    struct dentry {
    	/* RCU lookup touched fields */
    	unsigned int d_flags;		/* protected by d_lock */
    	seqcount_t d_seq;		/* per dentry seqlock */
    	struct hlist_bl_node d_hash;	/* lookup hash list */
    	struct dentry *d_parent;	/* parent directory */
    	struct qstr d_name;
    	struct inode *d_inode;		/* Where the name belongs to - NULL is
    					 * negative */
    	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */
    
    	/* Ref lookup also touches following */
    	unsigned int d_count;		/* protected by d_lock */
    	spinlock_t d_lock;		/* per dentry lock */
    	const struct dentry_operations *d_op;
    	struct super_block *d_sb;	/* The root of the dentry tree */
    	unsigned long d_time;		/* used by d_revalidate */
    	void *d_fsdata;			/* fs-specific data */
    
    	struct list_head d_lru;		/* LRU list */
    	/*
    	 * d_child and d_rcu can share memory
    	 */
    	union {
    		struct list_head d_child;	/* child of parent list */
    	 	struct rcu_head d_rcu;
    	} d_u;
    	struct list_head d_subdirs;	/* our children */
    	struct list_head d_alias;	/* inode alias list */
    };

           在dentry结构体中,描述了根结点、父节点等等信息,尤其还要注意的是struct inode *d_inode这一变量,它则是指向了一个i-node结点。

          再回到file结构体中,有一个struct file_operations *f_op变量,其类型定义在include/linux/fs.h中:

    struct file_operations {
    	struct module *owner;
    	loff_t (*llseek) (struct file *, loff_t, int);
    	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    	int (*readdir) (struct file *, void *, filldir_t);
    	unsigned int (*poll) (struct file *, struct poll_table_struct *);
    	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    	int (*mmap) (struct file *, struct vm_area_struct *);
    	int (*open) (struct inode *, struct file *);
    	int (*flush) (struct file *, fl_owner_t id);
    	int (*release) (struct inode *, struct file *);
    	int (*fsync) (struct file *, int datasync);
    	int (*aio_fsync) (struct kiocb *, int datasync);
    	int (*fasync) (int, struct file *, int);
    	int (*lock) (struct file *, int, struct file_lock *);
    	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    	int (*check_flags)(int);
    	int (*flock) (struct file *, int, struct file_lock *);
    	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    	int (*setlease)(struct file *, long, struct file_lock **);
    	long (*fallocate)(struct file *file, int mode, loff_t offset,
    			  loff_t len);
    };

          可见,在该成员中包含了所有文件操作相关的函数指针。

          每一个打开的文件都对应于一个file结构体,在该结构体中,f_flags描述了文件标志,f_pos描述了文件的偏移位置,而在f_path中有含有一个指向一个inode结点的指针,这也符合了《UNIX环境高级编程》中的以下描述:

           因此可以知道,文件表项的数据结构就是file结构体,而在实际上内核中也并不存在这样一张文件表,只是每个打开的文件都对应一个file结构体,也就是一个文件表项,打开文件描述符表struct file * fd_array[NR_OPEN_DEFAULT]数组中的每一项都会指向这样一个文件表项,如下图所示:

    inode节点

           第三种数据结构就是inode节点,在include/linux/fs.h中找到其定义如下:

    struct inode {
    	/* RCU path lookup touches following: */
    	umode_t			i_mode;     //权限
    	uid_t			i_uid;      //用户id
    	gid_t			i_gid;      //组id
    	const struct inode_operations	*i_op;
    	struct super_block	*i_sb;
    
    	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
    	unsigned int		i_flags;
    	struct mutex		i_mutex;
    
    	unsigned long		i_state;
    	unsigned long		dirtied_when;	/* jiffies of first dirtying */
    
    	struct hlist_node	i_hash;
    	struct list_head	i_wb_list;	/* backing dev IO list */
    	struct list_head	i_lru;		/* inode LRU list */
    	struct list_head	i_sb_list;
    	union {
    		struct list_head	i_dentry;
    		struct rcu_head		i_rcu;
    	};
    	unsigned long		i_ino;   //inode节点号
    	atomic_t		i_count;
    	unsigned int		i_nlink;
    	dev_t			i_rdev;
    	unsigned int		i_blkbits;
    	u64			i_version;
    	loff_t			i_size;   //文件大小
    #ifdef __NEED_I_SIZE_ORDERED
    	seqcount_t		i_size_seqcount;
    #endif
    	struct timespec		i_atime;  //最后一次访问(access)的时间
    	struct timespec		i_mtime;  //最后一次修改(modify)的时间
    	struct timespec		i_ctime;  //最后一次改变(change)的时间
    	blkcnt_t		i_blocks;    //块数
    	unsigned short          i_bytes;
    	struct rw_semaphore	i_alloc_sem;
    	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
    	struct file_lock	*i_flock;
    	struct address_space	*i_mapping;   //块地址映射
    	struct address_space	i_data;
    #ifdef CONFIG_QUOTA
    	struct dquot		*i_dquot[MAXQUOTAS];
    #endif
    	struct list_head	i_devices;
    	union {
    		struct pipe_inode_info	*i_pipe;
    		struct block_device	*i_bdev;
    		struct cdev		*i_cdev;
    	};
    
    	__u32			i_generation;
    
    #ifdef CONFIG_FSNOTIFY
    	__u32			i_fsnotify_mask; /* all events this inode cares about */
    	struct hlist_head	i_fsnotify_marks;
    #endif
    
    #ifdef CONFIG_IMA
    	atomic_t		i_readcount; /* struct files open RO */
    #endif
    	atomic_t		i_writecount;
    #ifdef CONFIG_SECURITY
    	void			*i_security;
    #endif
    #ifdef CONFIG_FS_POSIX_ACL
    	struct posix_acl	*i_acl;
    	struct posix_acl	*i_default_acl;
    #endif
    	void			*i_private; /* fs or device private pointer */
    };

           代码中只注释了一部分,通过inode结构,可以知道文件数据块的在磁盘上的位置以及文件大小等信息,这样才能使得进程能够通过file结构体来找到磁盘上相应文件的位置来进行文件读写。

           另外补充一点,关于inode结构体中的struct inode_operations *i_op成员,其数据结构定义在include/linux/fs.h中:

    struct inode_operations {
    	struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
    	void * (*follow_link) (struct dentry *, struct nameidata *);
    	int (*permission) (struct inode *, int, unsigned int);
    	int (*check_acl)(struct inode *, int, unsigned int);
    
    	int (*readlink) (struct dentry *, char __user *,int);
    	void (*put_link) (struct dentry *, struct nameidata *, void *);
    
    	int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    	int (*link) (struct dentry *,struct inode *,struct dentry *);
    	int (*unlink) (struct inode *,struct dentry *);
    	int (*symlink) (struct inode *,struct dentry *,const char *);
    	int (*mkdir) (struct inode *,struct dentry *,int);
    	int (*rmdir) (struct inode *,struct dentry *);
    	int (*mknod) (struct inode *,struct dentry *,int,dev_t);
    	int (*rename) (struct inode *, struct dentry *,
    			struct inode *, struct dentry *);
    	void (*truncate) (struct inode *);
    	int (*setattr) (struct dentry *, struct iattr *);
    	int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
    	int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
    	ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
    	ssize_t (*listxattr) (struct dentry *, char *, size_t);
    	int (*removexattr) (struct dentry *, const char *);
    	void (*truncate_range)(struct inode *, loff_t, loff_t);
    	int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
    		      u64 len);
    } ____cacheline_aligned;

            可见,在该成员变量所指向的数据结构中,包含了许多函数指针,这些函数指针大多针对于目录、文件操作。

    总结

           进程、打开文件描述符表、文件表项和i-node结点关系如图所示。(暂时忽略进程中0、1和2号文件描述符分别默认为标准输入、标准输出和标准错误的情况)

    通过以上分析,我们可以得出以下结论:

    1.每启动一个进程都会为其分配一个task_struct结构体,在task_struct结构体中含有一个file_struct结构体指针,其所指向的file_struct结构体中,含有一个file*的指针数组fd_array,它就是打开文件描述符表,其中每一个元素都指向一个文件表项,这个数组的索引就是文件描述符。此外,file_struct结构体中的next_fd保存的是下一个分配的文件描述符,它会在调用open和close改变,最终使得每次open返回的都是当前可用的最小文件描述符

    2.每次调用open或者create(内部实际上还是调用的open),都会对新打开的文件分配一个file结构体,并且将打开文件的标志、状态、权限等信息填入这个file结构体中。这个file结构体也叫文件表项;

    3.磁盘中的每个文件都对应一个i-node,每一个文件表项都会指向一个文件的i-node,但是同一文件的i-node可以对应多个文件表项(当多次调用open打开同一个文件时就会出现这种情况,不管是同一进程多次打开同一文件如图中A进程的0号和2号文件描述符对应两个文件表项,但是最终指向同一i-node即同一文件)还是不同进程多次打开同一文件如图中A进程3号文件描述符和B进程的3号文件描述符);

    4.同一进程下的不同文件描述符是可以指向同一文件表项,即最终指向同一文件(如图中A进程的0号文件描述符和1号文件描述符,使用dup函数即可实现)。

    5.子进程在创建时会拷贝父进程的打开文件描述符表,因此父子进程是共享文件表项的,如图所示:

           而相互独立的不同进程的打开文件描述符表是相互独立的,因此相互独立的多个进程之间的文件描述符可以相同,但是不同进程的文件描述符是不能指向同一文件表项的(除非这个文件描述符是从同一个祖先进程中继承得来的),但是这并不妨碍不同进程访问同一文件(如第3点结论);

    6.指向同一文件表项的不同文件描述符(不同进程相同数值的文件描述符也看做不同)共享文件标志、文件偏移等信息;

    7.每一个文件表项对应的file结构体中的f_count会记录通过该文件表项打开文件的次数,当f_count计数归0时这个文件表项才会被删除,因此,对于指向同一文件表项的两个不同文件描述符(如子进程所继承的父进程的文件描述符,或同一进程中dup的两个文件描述符指向同一个文件表项),即使其中一个文件描述符关闭了,只要仍然有文件描述符指向这个文件表项,那么就依然能通过这个文件表项访问文件,直到所有指向该文件表项的文件描述符都关闭了才不能再进行访问;

    最后再加一张图加以理解:

    展开全文
  • 文件描述符fd

    千次阅读 2018-10-21 15:34:19
    文件描述符 在linux中open函数调用成功后会返回一个非负整数,这个非负整数就是文件描述符。 Linux 默认情况下会缺省打开三个文件描述符 标准输入0 标准输出1 标准错误2 0 1 2 对应的物理设备一般是: 键盘 显示器 ...

    文件描述符

    在linux中open函数调用成功后会返回一个非负整数,这个非负整数就是文件描述符。

    Linux 默认情况下会缺省打开三个文件描述符 标准输入0 标准输出1 标准错误2

    0 1 2 对应的物理设备一般是: 键盘 显示器 显示器

    在这里插入图片描述

    文件描述符就是从0开始的小整数。当我们打开文件时,操作系统需要创建相应的数据结构来描述目标文件 file结构体 表示一个已经打开的文件对象。当进程执行open系统调用时必需要将文件和进程关联起来。每个进程都有一个指针files指向一张表files_struct* ,该表中有一个指针数组 file* fd_array[] ,这个数组中的每一个元素都指向一个已打开的文件,而文件描述符就是该指针数组的下标

    文件描述符的分配规则

    在files_struct数组当中,找到没有被使用的最小的一个下标作为文件描述符。

    在默认情况下,0 1 2 三个文件描述符是已经打开的,新创建的文件从3开始,当0或1,2有被关闭的从最小的开始

    重定向

    输出重定向

     #include <stdio.h>                                                               
        2 #include <sys/types.h>                                                           
        3 #include <sys/stat.h>                                                            
        4 #include <stdlib.h>                                                              
        5 #include <fcntl.h>                                                               
        6                                                                                  
        7 int main()                                                                       
        8 {                                                                                
        9   umask(0);                                                                      
       10   close(1);                                                                      
       11   int fd=open("myfile.txt",O_WRONLY|O_CREAT ,0644);                              
       12   if(fd<1)                                                                       
       13   {                                                                              
       14    perror("open");                                                               
       15     return 1;                                                                    
       16   }                                                                              
       17                                                                                  
       18   printf("fd: %d\n",fd);                                                         
       19   fflush(stdout);         //往显示器上刷新缓存区                                                       
       20                                                                                  
       21   close(fd);                                                                     
       22   return 0;                                                                      
       23 }                 
    
    [wens@localhost code2]$ make
    gcc -o test test.c
    [wens@localhost code2]$ ./test
    [wens@localhost code2]$ cat myfile.txt
    fd: 1
    

    本来应该输出到屏幕上的内容,输出到了文件myfile.txt中

    printf是c库中的io函数,一般往stdout输出,但是stdout底层访问文件时,找的还是fd=1;但是1被关闭后,mefile.txt被创建后,放入了下标为fd=1的位置,所以原本是stdout的位置,此时已将是myfile.txt的位置了,所以输出的消息都输出进了myfile.txt 进而完成输出重定项。

    追加重定项:
    关掉 1
    创建文件时 参数加入追加O_APPEND

    输入重定项: 关掉0 创建文件 再从标准输入读取 即完成

    FILE


    file 结构体是c库提供的文件结构体,其中包含了文件描述符fd,以及自带的缓冲区

    1 #include <stdio.h>                                                                 
      2 #include <sys/types.h>                                                             
      3 #include <sys/stat.h>                                                              
      4 #include <stdlib.h>                                                                
      5 #include <fcntl.h>                                                                 
      6 #include <string.h>                                                                
      7 #include <unistd.h>                                                                
      8 int main()                                                                         
      9 {                                                                                  
     10   const char* msg0="printf\n";                                                     
     11   const char* msg1="fwrite\n";                                                     
     12   const char* msg2="write\n";                                                      
     13                                                                                    
     14   printf("%s",msg0);                                                               
     15   fwrite(msg1,1,strlen(msg1),stdout);                                              
     16   write(1,msg2,strlen(msg2));                                                      
     17                                                                                    
     18   fork();                                                                          
     19   return 0;                                                                        
     20 }             
    

    输出结果

    wens@localhost code2]$ vim test.c
    [wens@localhost code2]$ make
    gcc -o test test.c
    [wens@localhost code2]$ ./test
    printf
    fwrite
    write
    [wens@localhost code2]$ touch file
    [wens@localhost code2]$ ./test >file   #对输出进行重定项
    [wens@localhost code2]$ cat file
    write
    printf
    fwrite
    printf
    fwrite
    [wens@localhost code2]$ 
    
    

    缓冲方式

    • 无缓冲:不刷新缓冲区
    • 行缓冲:按行进行刷新
    • 全缓冲:默认情况下只有把缓冲
      区写满时才刷新出去,除非强制新

    与文件类型有关:

    • 写入显示器对应行缓冲
    • 写入普通文件对应全缓冲

    printf 和fwrite 输出了两次write输出了一次。

    c库函数般自带缓冲区,当重定项到普通文件中时,行缓冲转换为全缓冲,此时缓冲区中的文件不会被刷新,直到调用fork之后,父子进程代码共享,因为缓冲区中的数据未被刷新,因此子进程写时拷贝私有一份,直到进程缓冲区刷新。产生两份数据。
    write未发生变化,说明write没有缓冲区

    printf fwrite C库函数自带缓冲区,write 系统调用接口路没有。


    实现简单的模拟shell程序
    添加输入 输出 追加 重定项

     1 #include <stdio.h>                                                        
        2 #include <unistd.h>                                                       
        3 #include <sys/wait.h>                                                     
        4 #include <stdlib.h>                                                       
        5 #include <string.h>                                                       
        6 #include <sys/stat.h>                                                     
        7 #include <fcntl.h>                                                        
        8                                                                           
        9                                                                           
       10 int main()                                                                
       11 {                                                                         
       12                                                                           
       13   char cmd[1024];                                                         
       14   char* myargv[16];                                                       
       15   int  fd=-1;                                                             
       16   while(1)                                                                
       17 {                                                                         
       18                                                                           
       19    printf("[wens@localhost myshell]# ");                                  
       20                                                                           
       21    fgets(cmd,sizeof(cmd),stdin);              
         22                                                                           
       23    int i=0;                                                               
       24                                                                           
       25    cmd[strlen(cmd)-1]='\0';                                               
       26                                                                           
       27    myargv[i++]=strtok(cmd," ");//将字符串分割解析                         
       28                                                                           
       29    char *ret=NULL;                                                        
       30                                                                           
    W> 31    while(ret=strtok(NULL," "))                                            
       32    {                                                                      
       33       myargv[i++]=ret;                                                    
       34    }                                                                      
       35                                                                           
       36    myargv[i]=NULL;                                                        
       37                       
        38 //  for( j=0;j<i;j++)                                                     
       39 //     printf("%s\n",myargv[j]);                                          
       40                                                                           
       41                                                                           
       42                                                                           
       43                                                                           
       44                                                                           
       45                                                                           
       46    int j=0;                                                               
       47    pid_t id=fork();                                                       
       48    if(id==0)                                                              
       49   {                                                                       
       50  //   printf("child\n");                          
       51                                                                           
       52      for(j=0;j<i;j++)                                                     
       53      {                                                                    
       54      if(strcmp(myargv[j],">")==0)  //    输出重定项                                 
       55      {                                                                    
       56         close(1);                                                         
       57         fd=open(myargv[j+1],O_WRONLY|O_CREAT,0644);                       
       58  //srv  printf("%d\n",fd);                                                
       59        if(fd<0)                                                           
       60        {                                                                  
       61          perror("open");                                                  
       62          exit(1);                                                         
       63        }                                                                  
       64        myargv[j]=NULL;
       65        i=j;
       66       break;
       67      }
       68      else if(strcmp(myargv[j],"<")==0)   //输入重定项
       69      {                                                                    
       70        close(0);                                                          
       71         fd=open(myargv[j+1],O_RDONLY,0644);                               
       72        if(fd<0)                                                           
       73        {                                                                  
       74          perror("open");                                                  
       75          exit(1);                                                         
       76        }                                                                  
       77                                                                           
       78        myargv[j]=NULL;                                                    
       79        i=j;                                                               
       80       break;                                                              
       81      }   else if(strcmp(myargv[j],">>")==0)             //追加重定项                  
       82      {
       83        close(1);
       84        fd=open(myargv[j+1],O_WRONLY|O_CREAT|O_APPEND,0644);
       85        if(fd<0)
       86        {
       87          perror("open");                                                  
       88          exit(1);                                                         
       89        }                                                                  
       90                                                                           
       91        myargv[j]=NULL;
       92        i=j;
       93       break;
       94      }
       95 
       96      }                                                                    
       97        execvp(myargv[0],myargv);                                          
       98          //进行程序替换                                                   
       99                                                                           
      100        exit(1);                                                           
      101                                                                           
      102     }else{                                                                
      103   //    printf("parent\n");                                               
      104      waitpid(id,NULL,0);  //等待子进程                                    
      105         }                                                                 
      106 }                                                                         
      107                                                                           
      108                                                                           
      109 return 0;
      110 
      111 }
    
    

    输出重定项

    [wens@localhost newshell]$ ./newshell
    [wens@localhost myshell]# ls
    Makefile  newshell  newshell.c
    [wens@localhost myshell]# ls > pp
    [wens@localhost myshell]# cat pp
    Makefile
    newshell
    newshell.c
    pp
    [wens@localhost myshell]# 
    
    

    输入重定项

    [wens@localhost myshell]# wc < pp
      9  14 116
    [wens@localhost myshell]# cat < pp
    Makefile
    newshell
    newshell.c
    pp
    newshell:newshell.c
    	gcc -o newshell newshell.c
    .PHONY:clean
    clean:
    	rm -f newshell
    [wens@localhost myshell]# 
     
    
    

    追加重定项

    [wens@localhost myshell]# cat Makefile >> pp
    [wens@localhost myshell]# cat pp
    Makefile
    newshell
    newshell.c
    pp
    newshell:newshell.c
    	gcc -o newshell newshell.c
    .PHONY:clean
    clean:
    	rm -f newshell
    [wens@localhost myshell]# 
    
    展开全文
  • 文件控制块(目录项/FCB):用来存放控制文件需要的各种信息的数据结构,以...文件描述符:open打开文件到的是一个文件描述符(索引号),存放在task_struct数据结构(进程控制块)中,将task_struct结构和file结构...
  • Linux 文件描述符

    2016-08-29 21:46:57
    文件描述符 1 什么是文件描述符  在C程序中,文件由文件指针或者文件描述符表示。ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open, close, ...
  • 文件描述符&文件指针

    千次阅读 2017-01-19 17:43:05
    我们知道在linux下一切皆文件,文件描述符是OS为了高效的管理已经被打开的文件所创建的一个索引,它是一个很小的正整数,所有执行I/O操作的系统调用都是通过文件描述符来实现的。  我们知道一个进程的创建一定会...
  • Linux文件描述符

    千次阅读 2018-08-02 17:18:05
    在Linux通用I/O模型中,I/O操作系列函数(系统调用)都是围绕一个叫做文件描述符的整数展开。这不禁让人产生疑问:这个整数代表什么?一个数值代表一个文件吗?随便传一个整数进去调用可以吗? 解答以上疑问,需要更...
  • 浅谈文件描述符及文件系统

    千次阅读 2017-12-17 14:57:01
    之前在讲IO操作的时候,其中系统级IO中的open,write,read,close都用到了文件描述符(file descriptor),其中open的返回值为文件描述符,write、read和close都是在传参的时候需要传文件的文件描述符。那么,文件...
  • linux文件描述符

    2019-10-08 10:20:32
    所有执行I/O操作的系统调用都以文件描述符,即一个非负整数来指代所打开的文件。文件描述符可以用来表示所有类型的已打开文件。同时,多个文件描述符可以指向同一个打开文件,因为有在不同进程中打开同一个文件的...
  • 文件描述符&&文件流指针

    千次阅读 2019-06-24 15:19:28
    文件描述符和文件流指针的关系 文件流指针是标准库操作句柄:FILE 文件描述符是系统调用接口句柄:int 文件流指针中包含了一个成员变量是—文件描述符 库函数内部封装的就是系统调用接口 缓冲区是用户态的缓冲区,...
  • 3-文件描述符和标准文件

    千次阅读 2018-06-24 23:41:43
    对于内核来说,所有打开的文件都通过文件描述符(file descriptor)来进行管理引用,文件描述符是一个非负整数,当打开一个文件或新建一个文件时,内核会向进程返回一个文件描述符,当所有对文件进行读写操作的read/...
  • 文件描述符与文件指针的区别

    千次阅读 2017-05-08 20:32:48
    文件描述符文件描述符表的一个索引,也就是说c语言的文件指针是Linux系统中对文件描述符的一种封装。  下面看FILE结构体里面都有那些成员 上面是文件指针指向的结构体,可以发现文件指针
  • 文件句柄和文件描述符 在我们跨平台开发的时候,经常会碰到这俩个概念 文件描述符: 本质上是一个索引号(非负整数),系统用户层可以根据它找到系统内核层的文件数据。这是一个POSIX标准下的概念,常见于Linux系统...
  • 文件描述符与socket连接

    千次阅读 2019-04-22 16:01:20
    文件描述符与socket连接 每个进程开启一个soeket连接,都会占用一个文件描述符。 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。 文件描述符(file ...
  • 文件描述符文件描述符表、打开文件表、目录项、索引节点之间的联系如下图所示:  每个进程在PCB(Process Control Block)中都保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 593,539
精华内容 237,415
关键字:

文件描述符