博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux文件系统(七)---系统调用之open操作(三) 之 open_namei函数
阅读量:4054 次
发布时间:2019-05-25

本文共 23891 字,大约阅读时间需要 79 分钟。

Open系统调用

下面看看open_namei函数:

这个函数的基本的功能是:

首先我们知道filename,也就是知道路径了,那么我们可以根据上级目录项对象,查询下一级的目录项对象,如果在目录项缓存找到下一级的目录项对象,则直接返回,并填充nd的挂载点对象和目录项对象。否则,构建一个子目录项对象,并分配一个新的inode结构,将子目录项对象和inode结构相关联。这样,一直循环到最后一个路径分量。最后返回的是最后一个路径分量的目录项对象和挂载点对象。

分成两点:

第一:如果单纯是打开一个已有的文件,那么直接跟了lookup函数(细节:注意如果这个名字分量打开的是“符号链接,那么需要二次查找实际的位置,下面会说到”)找到打开即可

第二:如果是需要创建一个新的文件,那么需要得到父目录的目录项对象和挂载点,然后再执行创建过程

细节:对于我们的输入路径,可以是绝对路径(从'/'开始),也可以是相对路径,那么下面的处理也会根据是绝对路径还是相对路径进行不同的处理。另外一些查找的细节后面再说。

总之,下面的函数执行完成之后,就会得到

1006 /*1007  *      open_namei()1008  *1009  * namei for open - this is in fact almost the whole open-routine.1010  *1011  * Note that the low bits of "flag" aren't the same as in the open1012  * system call - they are 00 - no permissions needed1013  *                        01 - read permission needed1014  *                        10 - write permission needed1015  *                        11 - read/write permissions needed1016  * which is a lot more logical, and also allows the "no perm" needed1017  * for symlinks (where the permissions are checked later).1018  * SMP-safe1019  */1020 int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)1021 {1022         int acc_mode, error = 0;1023         struct inode *inode;1024         struct dentry *dentry;1025         struct vfsmount *mnt;1026         struct dentry *dir;1027         int count = 0;1028 1029         acc_mode = ACC_MODE(flag);1030 1031         /*1032          * The simplest case - just a plain lookup.1033          */1034         if (!(flag & O_CREAT)) {   /* 如果不是创建新的文件,那么仅仅根据提供的路径去寻找文件即可 */1035                 error = path_lookup(pathname, lookup_flags(flag), nd);  /* 寻找文件函数 */1036                 if (error)1037                         return error;1038                 dentry = nd->dentry;  /* 目录项:这个目录项就是之前path_lookup找到的路径最后一个分量也就是具体文件的对应的目录项对象 */1039                 goto ok;  1040         }1041 1042         /*1043          * Create - we need to know the parent.(需要创建文件时候,需要知道parent目录,所以要先找parent)1044          */1045         error = path_lookup(pathname, LOOKUP_PARENT, nd);   /* nd中的dentry是父目录的目录项对象,last_type是最后一个分量也就是需要创建的文件的类型 */1046         if (error)1047                 return error;1048 1049         /*1050          * We have the parent and last component. First of all, check1051          * that we are not asked to creat(2) an obvious directory - that1052          * will not do.1053          */1054         error = -EISDIR; /* 错误的类型(不是正常文件)不给创建文件 */1055         if (nd->last_type != LAST_NORM || nd->last.name[nd->last.len])1056                 goto exit;1057 1058         dir = nd->dentry; /* 获得父目录的目录项 */1059         down(&dir->d_inode->i_sem);1060         dentry = lookup_hash(&nd->last, nd->dentry); /* 在父目录下找这个last-name名字的dentry是不是存在,如果不存在需要创建一个新的目录项 */1061 1062 do_last:1063         error = PTR_ERR(dentry);1064         if (IS_ERR(dentry)) {1065                 up(&dir->d_inode->i_sem);1066                 goto exit;1067         }1068 1069         /* Negative dentry, just create the file */1070         if (!dentry->d_inode) { /* 目录项是之前创建的,如果d_inode不存在,那么说明文件时不存在的,那么直接需要创建一个新的文件 */1071                 error = vfs_create(dir->d_inode, dentry,    /* 创建这个文件对应的inode */1072                                    mode & ~current->fs->umask);1073                 up(&dir->d_inode->i_sem);1074                 dput(nd->dentry);1075                 nd->dentry = dentry;1076                 if (error)1077                         goto exit;1078                 /* Don't check for write permission, don't truncate */1079                 acc_mode = 0;1080                 flag &= ~O_TRUNC;1081                 goto ok;   /* 文件创建完成 */1082         }1083 1084         /*1085          * It already exists.:这个说明上面找到的对应的文件,也就是说存在同名文件1086          */1087         up(&dir->d_inode->i_sem);1088 1089         error = -EEXIST;1090         if (flag & O_EXCL)1091                 goto exit_dput;1092         /* 找到最底层的文件系统,没有挂在其他文件系统 */1093         if (d_mountpoint(dentry)) {1094                 error = -ELOOP;1095                 if (flag & O_NOFOLLOW)1096                         goto exit_dput;1097                 while (__follow_down(&nd->mnt,&dentry) && d_mountpoint(dentry));1098         }1099         error = -ENOENT;1100         if (!dentry->d_inode)1101                 goto exit_dput;1102         if (dentry->d_inode->i_op && dentry->d_inode->i_op->follow_link)1103                 goto do_link; /* 做符号链接 */1104 1105         dput(nd->dentry);1106         nd->dentry = dentry;1107         error = -EISDIR;1108         if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))1109                 goto exit;1110 ok:1111         error = -ENOENT;1112         inode = dentry->d_inode; /* 文件inode */1113         if (!inode)1114                 goto exit;1115 1116         error = -ELOOP;1117         if (S_ISLNK(inode->i_mode))1118                 goto exit;1119         1120         error = -EISDIR;1121         if (S_ISDIR(inode->i_mode) && (flag & FMODE_WRITE))1122                 goto exit;1123 1124         error = permission(inode,acc_mode);1125         if (error)1126                 goto exit;1127 1128         /* 下面处理一些特殊的文件FIFO,socket文件,设备文件1129          * FIFO's, sockets and device files are special: they don't1130          * actually live on the filesystem itself, and as such you1131          * can write to them even if the filesystem is read-only.1132          */1133         if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {1134                 flag &= ~O_TRUNC;1135         } else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) {1136                 error = -EACCES;1137                 if (nd->mnt->mnt_flags & MNT_NODEV)1138                         goto exit;1139 1140                 flag &= ~O_TRUNC;1141         } else {1142                 error = -EROFS;1143                 if (IS_RDONLY(inode) && (flag & 2))1144                         goto exit;1145         }1146         /*1147          * An append-only file must be opened in append mode for writing.1148          */1149         error = -EPERM; /* 追加模式 */1150         if (IS_APPEND(inode)) {1151                 if  ((flag & FMODE_WRITE) && !(flag & O_APPEND))1152                         goto exit;1153                 if (flag & O_TRUNC)1154                         goto exit;1155         }1156 1157         /*1158          * Ensure there are no outstanding leases on the file.1159          */1160         error = get_lease(inode, flag);1161         if (error)1162                 goto exit;1163 1164         if (flag & O_TRUNC) {1165                 error = get_write_access(inode);  /* 写权限 */1166                 if (error)1167                         goto exit;1168 1169                 /*1170                  * Refuse to truncate files with mandatory locks held on them.1171                  */1172                 error = locks_verify_locked(inode);1173                 if (!error) {1174                         DQUOT_INIT(inode);1175                         1176                         error = do_truncate(dentry, 0); /* 执行截断操作 */1177                 }1178                 put_write_access(inode); /* 释放写权限 */1179                 if (error)1180                         goto exit;1181         } else1182                 if (flag & FMODE_WRITE)1183                         DQUOT_INIT(inode);1184 1185         return 0;1186 1187 exit_dput:1188         dput(dentry);1189 exit:1190         path_release(nd);1191         return error;1192 1193 do_link:1194         error = -ELOOP;1195         if (flag & O_NOFOLLOW)1196                 goto exit_dput;1197         /*1198          * This is subtle. Instead of calling do_follow_link() we do the1199          * thing by hands. The reason is that this way we have zero link_count1200          * and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT.1201          * After that we have the parent and last component, i.e.1202          * we are in the same situation as after the first path_walk().1203          * Well, almost - if the last component is normal we get its copy1204          * stored in nd->last.name and we will have to putname() it when we1205          * are done. Procfs-like symlinks just set LAST_BIND.1206          */1207         UPDATE_ATIME(dentry->d_inode);1208         mnt = mntget(nd->mnt);1209         error = dentry->d_inode->i_op->follow_link(dentry, nd);1210         dput(dentry);1211         mntput(mnt);1212         if (error)1213                 return error;1214         if (nd->last_type == LAST_BIND) {1215                 dentry = nd->dentry;1216                 goto ok;1217         }1218         error = -EISDIR;1219         if (nd->last_type != LAST_NORM)1220                 goto exit;1221         if (nd->last.name[nd->last.len]) {1222                 putname(nd->last.name);1223                 goto exit;1224         }1225         error = -ELOOP;1226         if (count++==32) {1227                 putname(nd->last.name);1228                 goto exit;1229         }1230         dir = nd->dentry;1231         down(&dir->d_inode->i_sem);1232         dentry = lookup_hash(&nd->last, nd->dentry);1233         putname(nd->last.name);1234         goto do_last;1235 }

看一下path_lookup函数:

755 /* SMP-safe */756 int fastcall path_lookup(const char *path, unsigned flags, struct nameidata *nd)757 {758         int error = 0;759         if (path_init(path, flags, nd))       /* 这个函数获得当前的目录项对象和安装点,同时在绝对路径情况下回探测能不能到达最后一个分量,如果可以,那么下面的path_walk就可以不需要执行了,如果失败了,那么需要执行下面的path_walk进行循环找到最后一个路径分量的目录项好安装点 */760                 error = path_walk(path, nd);  /* 找到最后一个名字路径分量的目录项对象和挂载点 */761         return error;762 }

这个函数涉及到path_init和path_walk函数,下面分别看一下:

path_init函数主要是获取最初的开目录项(路径开始点)

765 /* SMP-safe */766 int fastcall path_init(const char *name, unsigned int flags, struct nameidata *nd)767 {768         nd->last_type = LAST_ROOT; /* if there are only slashes... */769         nd->flags = flags;770         if (*name=='/')    /* 如果路径第一个字符是'/',那么说明是绝对路径,从根目录开始搜索的路径 */771                 return walk_init_root(name,nd); /* 执行这个函数,下面会分析 */772         read_lock(&current->fs->lock);         /* 如果不是绝对路径,那么就是相对路径了,那么直接获得当前的进程执行目录项即可 */773         nd->mnt = mntget(current->fs->pwdmnt); /* 如果需要打开的不是根目录,那么找到当前进程执行目录安装点 */774         nd->dentry = dget(current->fs->pwd);   /* 找到进程当前执行目录 */775         read_unlock(&current->fs->lock); 776         return 1;    /* 这个地方仅仅是得到了当前的目录项对象,我们需要得到的是最后一个路径分量的对象,所以返回之后还需要walk_path,return 1 */777 }

如果是根目录的话,看一下这个函数walk_init_root:

736 /* SMP-safe */737 static inline int738 walk_init_root(const char *name, struct nameidata *nd)739 {740         read_lock(&current->fs->lock);741         if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {  /* 如果用户有自己设置替换目录,那么使用这个 & 允许使用替换目录 */742                 nd->mnt = mntget(current->fs->altrootmnt);743                 nd->dentry = dget(current->fs->altroot);744                 read_unlock(&current->fs->lock);745                 if (__emul_lookup_dentry(name,nd)) /* 这个函数里面其实就是封装了walk_path进行路径的寻找,如果返回1,那么说明找到了最后一个了 */746                         return 0;                  /* 既然找到了,说明nd被填充好了,那么后期返回的时候无需再寻找了~~~~~~~ */747                 read_lock(&current->fs->lock);748         }/* 这下面仅仅是保存了当前的目录,还没有循环到最后一个目录分量,所以,返回后需要继续walk_path,所以return 1 */749         nd->mnt = mntget(current->fs->rootmnt); /* 如果没有设置替换目录,那么使用进程根目录即可 */750         nd->dentry = dget(current->fs->root);   751         read_unlock(&current->fs->lock);752         return 1;753 }

OK,回到上面,继续看path_walk函数:
668 int fastcall path_walk(const char * name, struct nameidata *nd)669 {670         current->total_link_count = 0;   /* 初始化符号链接总数 */671         return link_path_walk(name, nd); /* 调用实际文件系统的路径分解函数 */672 }

注意这个link_path_walk函数主要是根据给定的路径,找到最后一个路径分量的目录项对象和安装点~

这个函数主要思路:打开一个文件,如果是仅仅打开,那么沿着路径分量不断往下找,最后返回最后一个名字分量的目录项对象就OK,如果是创建文件,那么返回的是倒数第二个分量也就是需要创建的文件的父目录的目录项对象。

细节:

第一:如果是打开的路径不是符号链接路径,那么按照路径(每个路径分量)层层去找到,直到找到最后一个路径分量代表的目录项对象,也就说最终返回的就是最后一个分量的目录项对象和挂载点。

第二:存在有些目录项对象对应的是符号链接,遇到这种情况,需要二次寻找这个真实的的路径分量,然后再继续往下找

450 /*451  * Name resolution.452  *453  * This is the basic name resolution function, turning a pathname454  * into the final dentry.455  *456  * We expect 'base' to be positive and a directory.457  */458 int fastcall link_path_walk(const char * name, struct nameidata *nd)459 {460         struct dentry *dentry;461         struct inode *inode;462         int err;463         unsigned int lookup_flags = nd->flags;464         /* 从根目录之后的目录项开始 */465         while (*name=='/')466                 name++;467         if (!*name)468                 goto return_reval;469         /* 获得之前保存的开始位置的目录项中对象的inode节点 */470         inode = nd->dentry->d_inode;471         if (current->link_count)472                 lookup_flags = LOOKUP_FOLLOW;473 474         /* At this point we know we have a real path component. */475         for(;;) {   /* 注意下面是一个词一个词的处理,例如home/tt/desktoop/xxx.txt,那么先处理home,在处理tt,在处理。。。 */476                 unsigned long hash;477                 struct qstr this;478                 unsigned int c;479                 /* 检测权限问题 */480                 err = permission(inode, MAY_EXEC);481                 dentry = ERR_PTR(err);482                 if (err)483                         break;484 485                 this.name = name;486                 c = *(const unsigned char *)name;487                 488                 hash = init_name_hash();   /* 默认=0 */489                 do {490                         name++;   /* 指向下一个字符,做准备 */491                         hash = partial_name_hash(c, hash);   /* 计算部分hash值,例如上面home的hash值 */492                         c = *(const unsigned char *)name;493                 } while (c && (c != '/'));494                 this.len = name - (const char *) this.name;   /* 中间一个名字分量长度 */495                 this.hash = end_name_hash(hash);              /* 这个分量的hash值 */496 		497                 /* remove trailing slashes? */498                 if (!c)   /* 如果已经是最后一个名字分量 */499                         goto last_component; 500                 while (*++name == '/');501                 if (!*name)502                         goto last_with_slashes;503 504                 /*505                  * "." and ".." are special - ".." especially so because it has506                  * to be able to know about the current root directory and507                  * parent relationships.508                  *//* 注意下面是处理当前目录'.'和父目录'..' */509                 if (this.name[0] == '.') switch (this.len) {510                         default:511                                 break;512                         case 2: /* 注意这个地方存在两种情况,第一种是父目录,第二种是隐藏文件,前面也是'.'开始 */513                                 if (this.name[1] != '.')   /* 这种情况就是隐藏文件 */514                                         break;515                                 follow_dotdot(nd);   /* 否则,这样情况就是父目录情况,需要回溯到父目录 '..' */516                                 inode = nd->dentry->d_inode;517                                 /* fallthrough */518                         case 1: /* 当前目录开始 */519                                 continue;520                 }521                 /*522                  * See if the low-level filesystem might want523                  * to use its own hash..524                  */  /* 使用底层文件系统自己的计算hash值函数 */525                 if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {526                         err = nd->dentry->d_op->d_hash(nd->dentry, &this);527                         if (err < 0)528                                 break;529                 }530                 /* This does the actual lookups.. */531                 dentry = cached_lookup(nd->dentry, &this, LOOKUP_CONTINUE);  /* 注意这个函数是在dentry的缓冲区去寻找是不是存在这个dentry缓存 */532                 if (!dentry) {                                               /* 这个在前面的dentry缓冲具体说过 */533                         dentry = real_lookup(nd->dentry, &this, LOOKUP_CONTINUE); /* 如果在目录项缓存中不存在,那么,需要分配一个新的dentry缓冲区加入到里面,这个在前面也说过,同样的链接:http://blog.csdn.net/shanshanpt/article/details/39829281 */534                         err = PTR_ERR(dentry);535                         if (IS_ERR(dentry))536                                 break;537                 }538                 /* Check mountpoints.. */ /* 检测挂载点然后继续向下寻找 */539                 while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))540                         ;541 			542                 err = -ENOENT;543                 inode = dentry->d_inode;  /* 获得inode */544                 if (!inode)545                         goto out_dput;546                 err = -ENOTDIR; 547                 if (!inode->i_op)548                         goto out_dput;549                 /* 如果当前解析的分量指向的是一个符号链接,那么转到处理符号链接 */550                 if (inode->i_op->follow_link) {551                         struct vfsmount *mnt = mntget(nd->mnt);552                         err = do_follow_link(dentry, nd);    /* 处理符号链接函数 */553                         dput(dentry);554                         mntput(mnt);555                         if (err)556                                 goto return_err;557                         err = -ENOENT;558                         inode = nd->dentry->d_inode;   /* 获得真实inode */559                         if (!inode)560                                 break;561                         err = -ENOTDIR; 562                         if (!inode->i_op)563                                 break;564                 } else {565                         dput(nd->dentry);566                         nd->dentry = dentry;    /* 新的dentry赋值给nd的dentry,然后回到上面继续下一个分量处理 */567                 }568                 err = -ENOTDIR; 569                 if (!inode->i_op->lookup)570                         break;571                 continue;572                 /* here ends the main loop */573 574 last_with_slashes:575                 lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;576 last_component: /* 对于路径最后一个名字分量的处理 */577                 if (lookup_flags & LOOKUP_PARENT)  /* 如果是创建文件,那么是存在这个标识 */578                         goto lookup_parent;579                 if (this.name[0] == '.') switch (this.len) {   /* 关于'.'和'..'情况和上面说的一样 */580                         default:581                                 break;582                         case 2: 583                                 if (this.name[1] != '.')584                                         break;585                                 follow_dotdot(nd);586                                 inode = nd->dentry->d_inode;587                                 /* fallthrough */588                         case 1:589                                 goto return_reval;590                 }/* 底层自己的hash函数 */591                 if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {592                         err = nd->dentry->d_op->d_hash(nd->dentry, &this);593                         if (err < 0)594                                 break;595                 }596                 dentry = cached_lookup(nd->dentry, &this, nd->flags); /* 同样是在dentry缓存中中寻找dentry */597                 if (!dentry) {598                         dentry = real_lookup(nd->dentry, &this, nd->flags); /* 没有dentry换成就会创建一个新的dentry缓存 */599                         err = PTR_ERR(dentry);600                         if (IS_ERR(dentry))601                                 break;602                 }603                 while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry)) /* 检测挂载点,继续寻找 */604                         ;605                 inode = dentry->d_inode;606                 if ((lookup_flags & LOOKUP_FOLLOW)   /* 处理符号链接 */607                     && inode && inode->i_op && inode->i_op->follow_link) {608                         struct vfsmount *mnt = mntget(nd->mnt);609                         err = do_follow_link(dentry, nd);  /* 二次寻找~~~ */610                         dput(dentry);611                         mntput(mnt);612                         if (err)613                                 goto return_err;614                         inode = nd->dentry->d_inode;615                 } else {616                         dput(nd->dentry);617                         nd->dentry = dentry;618                 }619                 err = -ENOENT;620                 if (!inode)   /* 没有存在的inode,那么另外处理 */621                         goto no_inode;622                 if (lookup_flags & LOOKUP_DIRECTORY) {   /* 如果打开的文件时文件夹,那么error */623                         err = -ENOTDIR; 624                         if (!inode->i_op || !inode->i_op->lookup)625                                 break;626                 }627                 goto return_base;628 no_inode:629                 err = -ENOENT;630                 if (lookup_flags & (LOOKUP_POSITIVE|LOOKUP_DIRECTORY))631                         break;632                 goto return_base;633 lookup_parent:  /* 如果是创建文件,那么需要先找到父目录,在处理!需要注意的是,父目录就是之前访问的那个目录,所以在这个地方,没有必要将nd中的dentry换成现在孩子的dentry,想换也没有啊,后来需要创建的,所以这个时候nd中保存的就已经是父目录的dentry了~ */634                 nd->last = this;  /* 最后一个分量名 */635                 nd->last_type = LAST_NORM; /* 最后一个分量类型 */636                 if (this.name[0] != '.')  /* 不是'.'说明是正常的文件 */637                         goto return_base;638                 if (this.len == 1)  /* 否则是'.'情况 */639                         nd->last_type = LAST_DOT;640                 else if (this.len == 2 && this.name[1] == '.') /* '..'情况 */641                         nd->last_type = LAST_DOTDOT;642                 else643                         goto return_base;644 return_reval:645                 /*646                  * We bypassed the ordinary revalidation routines.647                  * Check the cached dentry for staleness.648                  */649                 dentry = nd->dentry;650                 if (dentry && dentry->d_op && dentry->d_op->d_revalidate) {651                         err = -ESTALE;652                         if (!dentry->d_op->d_revalidate(dentry, 0)) {653                                 d_invalidate(dentry);654                                 break;655                         }656                 }657 return_base:658                 return 0;659 out_dput:660                 dput(dentry);661                 break;662         }663         path_release(nd);664 return_err:665         return err;666 }

注意这里面有一个函数叫follow_dotdot,这个函数是回溯到父目录的函数

412  413 static inline void follow_dotdot(struct nameidata *nd)414 {415         while(1) {416                 struct vfsmount *parent;417                 struct dentry *dentry;418                 read_lock(&current->fs->lock);419                 if (nd->dentry == current->fs->root &&  /* 如果回溯到的是进程的根目录。不允许 */420                     nd->mnt == current->fs->rootmnt)  {421                         read_unlock(&current->fs->lock);422                         break;423                 }424                 read_unlock(&current->fs->lock);425                 spin_lock(&dcache_lock);426                 if (nd->dentry != nd->mnt->mnt_root) {  /*如果目录项对象不是根目录,则返回上一级目录项对象*/  427                         dentry = dget(nd->dentry->d_parent);  /* 得到parent目录项 */ 428                         spin_unlock(&dcache_lock);429                         dput(nd->dentry);430                         nd->dentry = dentry;431                         break;432                 }433                 parent=nd->mnt->mnt_parent;   /* 否则得到挂载点parent */434                 if (parent == nd->mnt) {435                         spin_unlock(&dcache_lock);436                         break;437                 }438                 mntget(parent);439                 dentry=dget(nd->mnt->mnt_mountpoint);440                 spin_unlock(&dcache_lock);441                 dput(nd->dentry);442                 nd->dentry = dentry;443                 mntput(nd->mnt);444                 nd->mnt = parent;445         }/* 下面是一直回溯到没有挂载其它文件系统的挂载点,mnt指向这个最底层的挂载点 */446         while (d_mountpoint(nd->dentry) && __follow_down(&nd->mnt, &nd->dentry))447                 ;448 }
/************至此,open_namei函数完成***************************************************************************************/

你可能感兴趣的文章
Java大数据开发做什么?Java大数据开发成长路线
查看>>
Java大数据:关于分布式、高并发与多线程
查看>>
Java大数据:数据库开发从入门到精通
查看>>
Java大数据:大数据开发必须掌握的四种数据库
查看>>
Java大数据:分布式存储Redis初级入门
查看>>
Java大数据:MongoDB数据库入门基础
查看>>
Java大数据:Hbase分布式存储入门
查看>>
Java大数据:全文搜索引擎Elasticsearch入门
查看>>
大数据学习:Hadoop入门学习书单
查看>>
大数据学习:Spark SQL入门简介
查看>>
大数据学习:Spark RDD操作入门
查看>>
大数据框架:Spark 生态实时流计算
查看>>
大数据入门:Hive和Hbase区别对比
查看>>
大数据入门:ZooKeeper工作原理
查看>>
大数据入门:Zookeeper结构体系
查看>>
大数据入门:Spark RDD基础概念
查看>>
大数据入门:SparkCore开发调优原则
查看>>
大数据入门:Java和Scala编程对比
查看>>
大数据入门:Scala函数式编程
查看>>
大数据入门:Spark持久化存储策略
查看>>