精华内容
下载资源
问答
  • linux open函数打开文件

    千次阅读 2013-12-02 16:24:29
    Linux打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数: SYSCALL_DEFINE3...

    Linux中打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数:

    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)

    {

             long ret;

     

             if (force_o_largefile())

                       flags |= O_LARGEFILE;

     

             ret = do_sys_open(AT_FDCWD, filename, flags, mode);

             /* avoid REGPARM breakage on x86: */

             asmlinkage_protect(3, ret, filename, flags, mode);

             return ret;

    }

     

     

    long do_sys_open(int dfd, const char __user *filename, int flags, int mode)

    {

    /*获取文件名称,由getname()函数完成,其内部首先创建存取文件名称的空间,然后*从用户空间把文件名拷贝过来*/

             char *tmp = getname(filename);

             int fd = PTR_ERR(tmp);

     

             if (!IS_ERR(tmp)) {

    /*获取一个可用的fd,此函数调用alloc_fd()函数从fd_table中获取一个可用fd,并做些简单初始化,此函数内部实现比较简单,此次分析不细看*/

                       fd = get_unused_fd_flags(flags);

                       if (fd >= 0) {

    /*fd获取成功则开始打开文件,此函数是主要完成打开功能的函数,在此先放一放,下面详细分析*/

                                struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);

                                if (IS_ERR(f)) {

                                         /*打开失败,释放fd*/

                                         put_unused_fd(fd);

                                         fd = PTR_ERR(f);

                                } else {

                                         /*文件如果已经被打开了,调用fsnotify_open()函数*/

                                         fsnotify_open(f->f_path.dentry);

                                         /*将文件指针安装在fd数组中*/

                                         fd_install(fd, f);

                                }

                       }

                       /*释放放置从用户空间拷贝过来的文件名的存储空间*/

                       putname(tmp);

             }

             return fd;

    }

     

    接下来即将进入到打开功能的真正实现功能的函数do_filp_open()函数:

    struct file *do_filp_open(int dfd, const char *pathname,

                       int open_flag, int mode, int acc_mode)

    {

             /*

             *…若干变量声明

             */

             /*改变参数flag的值,具体做法是flag+1*/

             int flag = open_to_namei_flags(open_flag);

             int force_reval = 0;

     

             /*根据__O_SYNC标志来设置O_DSYNC 标志,用以防止恶意破坏程序*/

             if (open_flag & __O_SYNC)

                       open_flag |= O_DSYNC;

             /*设置访问权限*/

             if (!acc_mode)

                       acc_mode = MAY_OPEN | ACC_MODE(open_flag);

     

             /*根据 O_TRUNC标志设置写权限 */

             if (flag & O_TRUNC)

                       acc_mode |= MAY_WRITE;

     

             /* 设置O_APPEND 标志*/

             if (flag & O_APPEND)

                       acc_mode |= MAY_APPEND;

     

             /*如果不是创建文件*/

             if (!(flag & O_CREAT)) {

                       /*返回特定的file结构体指针*/

                       filp = get_empty_filp();

     

                       if (filp == NULL)

                                return ERR_PTR(-ENFILE);

                       /*填充nameidata 结构*/

                       nd.intent.open.file = filp;

                       filp->f_flags = open_flag;

                       nd.intent.open.flags = flag;

                       nd.intent.open.create_mode = 0;

    /*当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs里面是由path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inodefile结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构*/

                       error = do_path_lookup(dfd, pathname,

                                                   lookup_flags(flag)|LOOKUP_OPEN, &nd);

                       if (IS_ERR(nd.intent.open.file)) {

                                if (error == 0) {

                                         error = PTR_ERR(nd.intent.open.file);

                                         /*减少dentry和vsmount得计数*/

                                         path_put(&nd.path);

                                }

                       } else if (error)

                                /*如果查找失败则释放一些资源*/

                                release_open_intent(&nd);

                       if (error)

                                return ERR_PTR(error);

                       goto ok;

             }

     

             /*到此则是要创建文件*/

    reval:

             /* path-init为查找作准备工作,path_walk真正上路查找,这两个函数联合起来根据一段路径名找到对应的dentry */

             error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);

             if (error)

                       return ERR_PTR(error);

             if (force_reval)

                       nd.flags |= LOOKUP_REVAL;

    /*这个函数相当重要,就如源代码注释的那样,是整个NFS的名字解析函数,其实也是NFS得以构筑的函数。这里作一重点分析。这里先作一个综述。该函数采用一个for循环,对name路径根据目录的层次,一层一层推进,直到终点或失败。在推进的过程中,一步步建立了目录树的dentry和对应的inode */

             error = path_walk(pathname, &nd);

             if (error) {

                       if (nd.root.mnt)

                                path_put(&nd.root);

                       return ERR_PTR(error);

             }

             if (unlikely(!audit_dummy_context()))

                       /*保存inode节点信息*/

                       audit_inode(pathname, nd.path.dentry);

     

             /*父节点信息*/

             error = -EISDIR;

             if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])

                       goto exit_parent;

     

             error = -ENFILE;

             /*获取文件指针*/

             filp = get_empty_filp();

             if (filp == NULL)

                       goto exit_parent;

             /*填充nameidata 结构*/

             nd.intent.open.file = filp;

             filp->f_flags = open_flag;

             nd.intent.open.flags = flag;

             nd.intent.open.create_mode = mode;

             dir = nd.path.dentry;

             nd.flags &= ~LOOKUP_PARENT;

             nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;

             if (flag & O_EXCL)

                       nd.flags |= LOOKUP_EXCL;

             mutex_lock(&dir->d_inode->i_mutex);

             /*从哈希表中查找nd对应的dentry*/

             path.dentry = lookup_hash(&nd);

             path.mnt = nd.path.mnt;

     

    do_last:

             error = PTR_ERR(path.dentry);

             if (IS_ERR(path.dentry)) {

                       mutex_unlock(&dir->d_inode->i_mutex);

                       goto exit;

             }

     

             if (IS_ERR(nd.intent.open.file)) {

                       error = PTR_ERR(nd.intent.open.file);

                       goto exit_mutex_unlock;

             }

     

             /*如果此dentry结构没有对应的inode节点,说明是无效的,应该创建文件节点 */

             if (!path.dentry->d_inode) {

                       /*write权限是必需的*/

                       error = mnt_want_write(nd.path.mnt);

                       if (error)

                                goto exit_mutex_unlock;

                       /*按照namei格式的flag open*/

                       error = __open_namei_create(&nd, &path, flag, mode);

                       if (error) {

                                mnt_drop_write(nd.path.mnt);

                                goto exit;

                       }

                       /*根据nameidata 得到相应的file结构*/

                       filp = nameidata_to_filp(&nd);

                       /*放弃写权限*/

                       mnt_drop_write(nd.path.mnt);

                       if (nd.root.mnt)

                                /*计数减一*/

                                path_put(&nd.root);

                       if (!IS_ERR(filp)) {

                                error = ima_file_check(filp, acc_mode);

                                if (error) {

                                         fput(filp);

                                         filp = ERR_PTR(error);

                                }

                       }

                       return filp;

             }

     

             /*要打开的文件已经存在*/

             mutex_unlock(&dir->d_inode->i_mutex);

             /*保存inode节点*/

             audit_inode(pathname, path.dentry);

     

    /*

             *省略若干flag标志检查代码

             */

     

             /*路径装化为相应的nameidata 结构*/

             path_to_nameidata(&path, &nd);

             error = -EISDIR;

             /*如果是文件夹*/

             if (S_ISDIR(path.dentry->d_inode->i_mode))

                       goto exit;

    ok:

             /*检测是否截断文件标志*/

             will_truncate = open_will_truncate(flag, nd.path.dentry->d_inode);

             if (will_truncate) {

             /*要截断的话就要获取写权限*/

                       error = mnt_want_write(nd.path.mnt);

                       if (error)

                                goto exit;

             }

             //may_open执行权限检测、文件打开和truncate的操作

             error = may_open(&nd.path, acc_mode, flag);

             if (error) {

                       if (will_truncate)

                                mnt_drop_write(nd.path.mnt);

                       goto exit;

             }

             filp = nameidata_to_filp(&nd);

             if (!IS_ERR(filp)) {

                       error = ima_file_check(filp, acc_mode);

                       if (error) {

                                fput(filp);

                                filp = ERR_PTR(error);

                       }

             }

             if (!IS_ERR(filp)) {

                       if (acc_mode & MAY_WRITE)

                                vfs_dq_init(nd.path.dentry->d_inode);

     

                       if (will_truncate) {

                                //处理截断

                                error = handle_truncate(&nd.path);

                                if (error) {

                                         fput(filp);

                                         filp = ERR_PTR(error);

                                }

                       }

             }

             //安全的放弃写权限

             if (will_truncate)

                       mnt_drop_write(nd.path.mnt);

             if (nd.root.mnt)

                       path_put(&nd.root);

             return filp;

     

    exit_mutex_unlock:

             mutex_unlock(&dir->d_inode->i_mutex);

    exit_dput:

             path_put_conditional(&path, &nd);

    exit:

             if (!IS_ERR(nd.intent.open.file))

                       release_open_intent(&nd);

    exit_parent:

             if (nd.root.mnt)

                       path_put(&nd.root);

             path_put(&nd.path);

             return ERR_PTR(error);

    //允许遍历连接文件,则手工找到连接文件对应的文件

    do_link:

             error = -ELOOP;

             if (flag & O_NOFOLLOW)

                       //不允许遍历连接文件,返回错误

                       goto exit_dput;

             /*

              以下是手工找到链接文件对应的文件dentry结构代码

              */

             // 设置查找LOOKUP_PARENT标志

             nd.flags |= LOOKUP_PARENT;

             //判断操作是否安全

             error = security_inode_follow_link(path.dentry, &nd);

             if (error)

                       goto exit_dput;

             // 处理符号链接

             error = __do_follow_link(&path, &nd);

             path_put(&path);

             if (error) {

            

                       release_open_intent(&nd);

                       if (nd.root.mnt)

                                path_put(&nd.root);

                       if (error == -ESTALE && !force_reval) {

                                force_reval = 1;

                                goto reval;

                       }

                       return ERR_PTR(error);

             }

             nd.flags &= ~LOOKUP_PARENT;

             // 检查最后一段文件或目录名的属性情况

             if (nd.last_type == LAST_BIND)

                       goto ok;

             error = -EISDIR;

             if (nd.last_type != LAST_NORM)

                       goto exit;

             if (nd.last.name[nd.last.len]) {

                       __putname(nd.last.name);

                       goto exit;

             }

             error = -ELOOP;

             // 出现回环标志: 循环超过32次

             if (count++==32) {

                       __putname(nd.last.name);

                       goto exit;

             }

             dir = nd.path.dentry;

             mutex_lock(&dir->d_inode->i_mutex);

             // 更新路径的挂接点和dentry

             path.dentry = lookup_hash(&nd);

             path.mnt = nd.path.mnt;

             __putname(nd.last.name);

             goto do_last;

    }

    分析完上述主要函数以后,我们来看一下整个打开流程是如何做到的:

    在内核中要打开一个文件,首先应该找到这个文件,而查找文件的过程在vfs里面是由do_path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构。

    基本函数流程及调用方式如下所示:

    打开过程首先是open系统调用访问SYSCALL_DEFINE3函数,然后调用do_sys_open 函数完成主要功能,再调用函数do_filp_open完成主要的打开功能,下面详细看下do_filp_open中调用的do_path_lookup主要过程:

    staic int  do_path_lookup(int dfd,const char

    *name,unsigned int flags,strucy nameidata *nd)

    {

               int retval=path_init(dfd,name,flags,nd);

    //设置nd->root=根路径(绝对地址)或者当前工作目录(相对地址) 。

    //这一步做完了后,内核会建立一些数据结构(dentry,inode)来初始化查找的起点

               if(!retval)

                           retval = path_walk(name,nd);

    //path_walk,会遍历路径的每一份量,也就是用“/”分隔开的每一部分,

    //最中找到name指向的文件,walk的意思就是walk path的每一个组分(component)

            }

     

    我们进一步看看path_walk

    int path_walk(const char *name,struct nameidata *nd)

    {

                return link_path_walk(name,nd);

    //path_walk其实相当于直接调用link_path_walk来完成工作

    }

    link_path_walk的主要工作是有其内部函数__link_path_walk 来完成的

              result = __link_path_walk(name,nd)

            

    至此我们转向最重要的代码__link_walk_path,该函数把传进来的字符串name,也就是用户指定的路径,按路径分隔符分解成一系列小的component。比如用户说,我要找/path/to/dest这个文件,那么我们的文件系统就会按path,to,dest一个一个来找,知道最后一个分量是文件或者查找完成。他找的时候,会先用path_init初始化过的根路径去找第一个分量,也就是path。然后用path的dentry->d_inode去找to,这样循环到最后一个。注意,内核会缓存找到的路径分量,所以往往只有第一次访问一个路径的时候,才会去访问磁盘,后面的访问会直接从缓存里找,下面会看到,很多与页告诉缓存打交道的代码。但不管怎样,第一遍查找总是会访问磁盘的。

    static int __link_path_walk(const char *name,strucy

    nameidata *nd)

    {

    }

    至此,按照每一个component查找完成之后,就会找到相应的文件,然后相应的打开工作就基本完成了。


    转自:http://blog.csdn.net/f413933206/article/details/5701913

    展开全文
  • linux open打开U盘中的文件后,U盘被拔掉,会怎么样?最近在几台linux嵌入式设备上测试,读写打开U盘中的文件后,在没有关闭的情况下拔掉U盘,有的只提示读写失败,有的直接应用退出,请问拔掉设备后倒地会怎么样?
  • linux c用open打开(创建)一个文件

    万次阅读 2017-01-07 17:18:25
    Linux操作系统内核(kernel)利用文件描述符来访问文件,文件描述符大于等于0,是一个用户描述被打开文件的索引值,它指向该文件的相关信息记录表,当文件打开或者创建新文件的时,就会返回一个文件描述符,当读写...

    1、文件描述符

    Linux操作系统内核(kernel)利用文件描述符来访问文件,文件描述符大于等于0,是一个用户描述被打开文件的索引值,它指向该文件的相关信息记录表,当文件打开或者创建新文件的时,就会返回一个文件描述符,当读写文件时,也需要使用文件描述符来指定特写文件。

     

    2、讲解open函数

    文件需要先打开或则创建

    如果返回是-1, 则出错,如果成功,则返回文件描述符

     

    第一个参数是文件路径

    第二个参数是flags

    展开全文
  • int open(constchar*pathname,intflags,mode_tmode);  打开一个文件,获得一个文件描述符 int ftruncate(int fd, off_t length)  ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开...

    1、函数简介

    int open(constchar*pathname,intflags,mode_tmode); 
    打开一个文件,获得一个文件描述符

    int ftruncate(int fd, off_t length) 
    ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。如果原来的文件件大小比参数length大,则超过的部分会被删去

    2、实例

    /* 打开一个文件 */
    fd = open("file.txt",O_RDWR);
    if(fd < 0)
    {
        printf("open file.txt failed\n");
    }
    else
    {
        printf("open file.txt successful\n");
    
        /* 清空文件 */
        ftruncate(fd,0);
    
        /* 重新设置文件偏移量 */
        lseek(fd,0,SEEK_SET);
    
        close(fd);
    }
    展开全文
  • linux open 文件时打开失败的查找方法

    千次阅读 2019-10-24 17:27:30
    最近在一个Linux项目的反馈中,有一个open失败的问题。场景是客户在识别很多次文件并导出时,发生了错误,根据我们的错误码定位到问题是出在一个open的地方,打开文件的时候失败,造成了不能正常生成文件,最后导致...

     最近在一个Linux项目的反馈中,有一个open失败的问题。场景是客户在识别很多次文件并导出时,发生了错误,根据我们的错误码定位到问题是出在一个open的地方,打开文件的时候失败,造成了不能正常生成文件,最后导致的错误。

            定位到问题后,根据open联想到是不是有open的文件没有关闭,导致句柄泄露的。因为用命令

    ulimit -n

     


    可以看到,在系统中默认的一个应用程序可以占用的句柄数是1024。然后就开始排查open的次数和close调用的次数是否一致,但运行起来之后发现调用次数完全一样,但还是识别到1000多次就挂掉了。正好是1000多次,说明确实是有别的地方在占用句柄,没有释放。用命令lsof确认了每执行一次识别导出就会造成一次句柄的泄露,所以继续定位。(命令的详细介绍参见 lsof)

    lsof -n |awk '{print $2}'|sort|uniq -c |sort -nr|more


            我知道一般在C语言中有open、close、opendir、closedir对目录文件的操作会对文件句柄有影响。最终在查找的时候发现有opendir的操作之后,没有正常调用closedir。原来的代码逻辑是用opendir的方式打开目录,如果非空则递归调用opendir直到不能打开为止,这样的逻辑最终删除空文件夹时会造成opendir之后不能对应的closedir,最终导致的句柄泄露(幸亏open的是临时文件夹的1深度的目录)。

            找到问题之后修改了下代码逻辑,句柄泄露的问题也就解决了!
    ————————————————
    版权声明:本文为CSDN博主「n大橘为重n」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/buknow/article/details/95309823

    展开全文
  • linux使用open无法打开驱动解决方式

    千次阅读 2017-11-04 14:05:22
    最近测试自己写的字符设备驱动例子(这里以test.ko为实例), 用户层多次调用open(“/dev/test”,O_RDWR)返回值为-1,根据返回状态捕获到错误(”No such device or address”),偶尔打开字符设备驱动也无法访问驱动...
  • linux open函数详解

    万次阅读 多人点赞 2018-06-15 10:28:15
    原文地址:https://blog.csdn.net/archyli/article/details/78937937一、open函数用来干什么open函数在Linux下一般用来打开或者创建一个文件,我们可以根据参数来定制我们需要的文件的属性和用户权限等各种参数。...
  • VSCode配置打开Linux远程目录

    千次阅读 2019-09-27 15:03:08
    VSCode配置打开Linux远程目录 首先安装一个 VSCode SFTP的插件。 快捷键: Ctrl+Shift+X vscode-sftp [install] 快捷键: Ctrl+Shift+P SFTP: Open Folder 添加一个json配置: sftpConfig.json 编写好后保存,...
  • os.open() 方法用于打开一个文件,并且设置需要的打开选项,模式参数mode参数是可选的,默认为 0777。 语法 open()方法语法格式如下: os.open(file, flags[, mode]); 参数 file – 要打开的文件 flags – 该参数...
  • 修改linux进程可打开的文件数的限制open files linux中一切都是文件 默认linux一个进程可打开1024个文件 大并发需要达到5万个文件描述符FD,你必须修改该参数值才行
  • open Linux

    千次阅读 2016-02-05 00:10:41
    本文基于Linux 4.2.0内核手册。...open function用于打开一个文件并返回文件描述符。#include #include #include <fcntl.h>int open(const char *pathname, int flags); int open(const char *pathname, int flags, m
  • open函数打开需要的头文件: #include &lt;sys/types.h&gt; #include &lt;sys/stat.h&gt; #include &lt;fcntl.h&gt; open使用原型: int open( const char * pathname, int flags); int ...
  • Linux 文件打开过多 (Too many open files)

    千次阅读 2018-03-02 15:12:06
    如图是程序运行了一段时间后抛出来的一个bug, 刚开始看这个bug的时候各种网上找答案, 无外乎教你怎么改ulimit(就是linux最大打开文件数), 当然不是说改这个没有用, 作为程序开发者来说, 如果程序运行出现了bug则必然...
  • Linux open系统调用流程

    千次阅读 2017-09-11 17:08:34
    在操作文件之前,首先必须打开文件,打开文件的函数是通过open系统调用来实现的。而简单的文件打开操作,在Linux内核实现却是非常的复杂。open函数打开原理就是将进程files_struct结构体和文件对象file相关联。那么...
  • linux open命令

    万次阅读 2018-05-24 20:27:33
    open命令用于打开文件,使用时需要引用头文件&lt;fcntl.h&gt;,若失败,返回-1,否则返回正整数,0是标准输入流,1是标准输出流,2是标准错误流,其他文件从3开始递增。函数定义如下__extern_always_inline ...
  • Linux open/close函数

    千次阅读 2020-08-24 16:52:09
    打开文件可能创建并打开文件(open and possibly create a file) 头文件 前两个 unistd.h中包含 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>// 主要定义宏 函数签名 // ...
  • 在运行某些命令或者 tomcat等...查看每个用户最大允许打开文件数量ulimit -a其中 open files (-n) 8192 表示每个用户最大允许打开的文件数量是8192 。 默认是1024。1024很容易不够用。查看当前系统打开的文件数量l
  • linux open 系统调用过程

    千次阅读 2013-10-13 11:38:45
    linux中系统掉用内核部分会调用sys_***(open ,read, write ,close.......) sys_openlinux系统调用open的内核部部分的函数。 asmlinkage long sys_open(const char __user *filename, int flags, int mode) ...
  • 现象描述:由于生产系统网络的升级,导致F5负载均衡器无法使用。系统中正常情况下是起的双节点前置(A节点、B节点),所有客户端都访问F5的地址,然后通过F5转...linux系统默认的open files是1024.Linux 打开文件...
  • 报错信息:24 :Too many open files主要是Linux程序级别的限制为1024。而一个socket连接就是一个file,连接过多,导致accept报错。最后通过ulimit命令把文件限制改到65535。就没有再报错了。...
  • LinuxOpen函数

    万次阅读 2018-10-29 15:55:01
    文章目录Open 介绍参数案例输出结果:文件描述符fd Open 介绍  Open函数用来打开一个文件,建立一个文件描述符到文件路径的映射,建立文件标识。  open函数原型如下所示: #include &amp;lt;fcntl.h&amp;...
  • 以前使用linux mint 13的时候,从命令行(终端)进入某个目录后,想以图形界面的方式打开目录的方法是:gnome-open ./,打开当前目录(在/前面有个点号)。 但是换到fedora之后,就不能用这个命令实现功能了。 ...
  • linux open()函数各参数说明

    万次阅读 2018-04-13 17:04:08
    打开文件、新建文件和关闭文件操作打开文件操作使用系统调用函数open(),该函数的作用是建立一个文件描述符,其他的函数可以通过文件描述符对指定文件进行读取与写入的操作。打开文件的一般形式是:open(文件路径, ...
  • open函数返回的文件描述符一定是最小的未用描述符数字,这一点被很多应用程序用来在标准输入、标准输出或标准出错上打开一个新的文件。 例如,一个应用程序可以先关闭标准输出(通常是文件描述符1),然后...
  • Linux打开了太多文件(Too many open files)的三种解决方法 回答一、 [root@lxadmin nginx]# cat /proc/sys/fs/file-max 8192 文件系统最大可打开文件数 [root@lxadmin nginx]# ulimit -n 1024 程序限制只能...
  • linux openfile参数修改

    千次阅读 2019-04-10 14:08:31
    cat /proc/14028/limits 查看当前进程配的open files数 Using username "hms". Last login: Wed Mar 27 16:06:34 2019 from 10.35.60.156 hms@linux-198x:~> su root ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 249,387
精华内容 99,754
关键字:

linuxopen打开目录

linux 订阅