ext2 源代码解析之 “从路径名到目标结点” (一)


 

    两个主要函数,path_init和path_walk,他们结合在一起根据给定的文件路径名称在内存中找到或者建立代表着目标文件或目录的dentry和inode结构。注意,最终是信息是读取到内存中的。其中有个函数__user_walk()将path_init()和path_walk()包装在一起。本节中的所有代码在fs/namei.c中。

 

1.分析外包装“__user_walk()”

 

778/*779 *      namei()780  *781  *is used by most simple commands to get the inode of a specified name.782  *Open, link etc use their own routines, but this is enough for things783  *like 'chmod' etc.784  *785  *namei exists in two versions: namei/lnamei. The only difference is786  *that namei follows links, while lnamei does not.787  *SMP-safe788  */789int __user_walk(const char *name, unsigned flags, struct nameidata *nd)790 {791        char *tmp;792        int err;793794        tmp = getname(name);795         err = PTR_ERR(tmp);//即使没有出错,仍然会进行转化,所以下文要将err=0796        if (!IS_ERR(tmp)) {797                 err = 0;798                 if (path_init(tmp, flags, nd))799                         err = path_walk(tmp,nd);800                 putname(tmp);801        }802        return err;803 }


 

 

1.1对函数参数的解释:

   name是用户空间的路径名称:flags内容是一些标志位,定义在/include/linux/fs.h,如下:

 

1323 /*
1324 * The bitmask for a lookup event:
1325 *  - follow links at the end
1326 *  - require a directory
1327 *  - ending slashes ok even fornonexistent files
1328 *  - internal "there are morepath compnents" flag
1329 */
1330 #define LOOKUP_FOLLOW           (1)
//symble link
1331 #define LOOKUP_DIRECTORY        (2)
// the target must be a directory
1332 #define LOOKUP_CONTINUE         (4)
1333 #define LOOKUP_POSITIVE         (8)
1334 #define LOOKUP_PARENT           (16)
1335 #define LOOKUP_NOALT            (32)


 

    注意第一项,符号链接可以跨越设备,而普通链接不可以。内核提供了不同的系统调用link和symlink,分别用于两种链接的建立关键数据结构.由于符号链接是可以跨越设备的,所以终点有可能悬空;而普通链接的终点是落实的。路径中包含符号链接的时候,是否继续往下搜索,规定如下:

  

82/* [Feb-Apr 2000 AV] Complete rewrite. Rules for symlinks:83  *      inside the path - always follow.//路径内部84  *      in the last component increation/removal/renaming - never follow.//创建,删除,重命名操作85  *      if LOOKUP_FOLLOW passed - follow.86  *      if the pathname has trailing slashes -follow.87  *      otherwise - don't follow.88  * (applied in that order).89  */

 

 

     最后一个参数,是临时性的,仅仅用于存放返回结果。

 

655struct nameidata {656        struct dentry *dentry;657        struct vfsmount *mnt;658        struct qstr last;659        unsigned int flags;660        int last_type;661};

 

 

  dentry记录找到的内存中的dentry和inode信息,mnt记录文件系统安装信息,例如文件系统的安装点,根节点等。

 

总结:一个函数与外界的信息交互,在数学模型中形如y=f(x),其中形参(一般形参和指针)和全局变量是输入变量;函数返回值,全局变量和指针所指向的变量都是函数输出。对于不改变指向值的指针,往往需要用const关键字。


1.2 执行流程分析:


   getname()在系统空间分配一个页面,从用户空间将文件名复制到这个页面中,所以整个路径名字可以长达4K,由于这块空间是动态分配的,所以用完以后要通过putname释放。 

 

关于err的内联函数和解释如下:

 

1300 /*
1301 * Kernel pointers have redundant information, so we can use a
1302 * scheme where we can return either an error code or a dentry
1303 * pointer with the same return value.
1304 *
1305 * This should be a per-architecture thing, to allow different
1306 * error and pointer decisions.
1307 */
1308 static inline void *ERR_PTR(long error)
1309 {
1310        return (void *) error;
1311 }
1312
1313 static inline long PTR_ERR(const void*ptr)
1314 {
1315        return (long) ptr;
1316 }
1317
1318 static inline long IS_ERR(const void*ptr)
1319 {
1320        return (unsigned long)ptr > (unsigned long)-1000L;
1321 }


 

注:函数返回值大于0xfffffc18的时候(就是-1000L),就说明出错了。


查看getname函数:

 

108 static inline int do_getname(const char *filename, char *page)109 {110         int retval;111         unsigned long len = PATH_MAX + 1;112 113         if ((unsigned long) filename >= TASK_SIZE) {114                 if (!segment_eq(get_fs(), KERNEL_DS))115                         return -EFAULT;116         } else if (TASK_SIZE - (unsigned long) filename < PAGE_SIZE)117                 len = TASK_SIZE - (unsigned long) filename;118 119         retval = strncpy_from_user((char *)page, filename, len);120         if (retval > 0) {121                 if (retval < len)122                         return 0;123                 return -ENAMETOOLONG;124         } else if (!retval)125                 retval = -ENOENT;126         return retval;127 }

 

 

 

129 char * getname(const char * filename)130 {131         char *tmp, *result;132 133         result = ERR_PTR(-ENOMEM);134         tmp = __getname();135         if (tmp)  {136                 int retval = do_getname(filename, tmp);137 138                 result = tmp;139                 if (retval < 0) {140                         putname(tmp);141                         result = ERR_PTR(retval);142                 }143         }144         return result;145 }


 #define ENOMEM 12

 

 #define __getname()     kmem_cache_alloc(names_cachep, SLAB_KERNEL) 

涉及内存管理的部分,我们以后再来研究。


2 path_init函数(在fs/namei.c).


 

[path_init()]/*SMP-safe */691int path_init(const char *name, unsigned int flags, struct nameidata *nd)692 {// 这个函数的作用是:得到路径起点,设置了dentry和mnt两个变量,并进行初始化的相关设置693        nd->last_type = LAST_ROOT; /* if there are only slashes... */694        nd->flags = flags;695        if (*name=='/')696                 return walk_init_root(name,nd);697        read_lock(current->fs->lock);//698        nd->mnt = mntget(current->fs->pwdmnt);699        nd->dentry = dget(current->fs->pwd);700         read_unlock(current->fs->lock);//701        return 1;702 }

 

 

 函数调用关系图如下: 

 

疑问:flags从何而来?

 

2.1 nameidata结构中的last_type字段:


    首先将nameidata结构中的last_type字段设置成LAST_ROOT,这个字段可能的值定义在fs.h中,如下:

 

1336 /*
1337 * Type of the last component on LOOKUP_PARENT
1338 */
1339 enum {LAST_NORM, LAST_ROOT, LAST_DOT,LAST_DOTDOT, LAST_BIND};


    在搜索过程中,这个字段的值会随着当前搜索结果而改变。例如,如果成功到了目标文件,它就变成LAST_NORM;如果停留在最后一个"."上面,则变成LAST_DOT.

 

2.2 其他字段的赋值

 

I)相对路径

 if后面的语句,是相对路径的情况:nd->dentry = dget(current->fs->pwd)这句代码有两层意思:1)将nameidata的dentry设置成当前工作目录的dentry结构,表示虚拟绝对路径中,这个节点之前的节点都已经解决了 2)这个具体的dentry结构多了一个用户,所以调用dget递增它的共享计数,即是dentry中d_count的值。关于,Vfsmount结构pwdmnt,可以参考以后的文件系统的安装和拆卸。

 

233 static __inline__ struct dentry *dget(struct dentry *dentry)
234 {
235        if (dentry) {
236                 if(!atomic_read(&dentry->d_count))
237                         BUG();//
238                atomic_inc(&dentry->d_count);
239        }
240        return dentry;
241 }

  #define atomic_read(v)          ((v)->counter) 

d_count 是atomic_t 类型

 typedef struct { volatile int counter; } atomic_t;


 #define atomic_inc(v) atomic_add(1,(v))


 

II)绝对路径


    if引导的是绝对路径下的情况,通过walk_init_root()从根节点开始查找(fs/namei.c):

 

[path_init() >>> walk_init_root()]722/* SMP-safe */723static inline int724walk_init_root(const char *name, struct nameidata *nd)725 {726        read_lock(current->fs->lock);727         if (current->fs->altroot &&!(nd->flags & LOOKUP_NOALT)) {728                 nd->mnt =mntget(current->fs->altrootmnt);729                 nd->dentry =dget(current->fs->altroot);730                read_unlock(¤t->fs->lock);731                 if(__emul_lookup_dentry(name,nd))732                         return 0;733                read_lock(¤t->fs->lock);734        }735        nd->mnt = mntget(current->fs->rootmnt);736        nd->dentry = dget(current->fs->root);737        read_unlock(currentt->fs->lock);738        return 1;739 }


 35 static inline struct vfsmount *mntget(struct vfsmount *mnt)

 36 {
 37         if (mnt)
 38                 atomic_inc(&mnt->mnt_count);
 39         return mnt;
 40 }

 

    代码解析:如果当前进程没有通过chroot设置自己的根目录,current->fs->altroot是0,所以nameidata中的两个指针就进行if之外的相关设置;反之,要查看当初调用path_init()的参数flags中的标志位LOOKUP_NOTLT是否为1.通常情况下,这个标志为0,所以如果current->fs->altroot是1,就会通过__emul_lookup_dentry(name,nd)将nameidata中的指针设置成指向“替换”根目录指针。因为linux 在i386处理器上不支持替换根目录,所以current->fs->altroot总是NULL,我们也不在此介绍相关内容,读者可以查看相关书籍。

 

    从path_init()成功返回的时候,nameidata结构中的指针dentry指向当前搜索路径的起点,接下来通过path_walk()顺着路径名的指引进行搜索。下面分段解析这个函数(fs/namei.c):

 

3.path_walk()函数解析

 

3.1)这个函数比较长(大概200行),我们进行分段解析,它的完整代码,我们可以查看附录部分。

 

442 /*443  *Name resolution.444  *445  *This is the basic name resolution function, turning a pathname446  *into the final dentry.447  *448  *We expect 'base' to be positive and a directory.449  */
450 int path_walk(const char * name, structnameidata *nd)451 {452        struct dentry *dentry;453        struct inode *inode;454        int err;455        unsigned int lookup_flags = nd->flags;456457        while (*name=='/')// 跳过多个连续的“/”458                 name++;459        if (!*name)//name仅仅包含一个"/",则解析完成460                 goto return_base;461462        inode = nd->dentry->d_inode;//注意dentry已经在init中设置完成463         if (current->link_count)// current is the type of task_struct464                 lookup_flags = LOOKUP_FOLLOW;

 

return_base;//return 0;

 

 

   link_count是task_struct的成员之一,用于防止链接导致的循环搜索。另外,因为path_walk()的起点是一个目录,所以一定有相应的inode存在,而不是空。

小结:以上代码跳过路径前面若干个“/”,并且给inode和flag初始化


3.2)接下来是对路径节点所作的for循环,我们将其拆开来看


 

466        /* At this point we know we have a real path component. *//*beforethis point ,we only parse the / or the co*/467        for(;;) {468                 unsigned long hash;469                 struct qstr this;470                 unsigned int c;471472                 err = permission(inode,MAY_EXEC);//检查对当前节点的访问权限,注意此处是可执行!!!473                 dentry =ERR_PTR(err);//translate long err into pointer474                 if (err)475                        break;476477                 this.name = name;478                 c = *(const unsigned char*)name;//name was a pointer to char,c is char name[0] >>> assci479
//#define init_name_hash() 0480                 hash = init_name_hash();481                 do {482                         name++;483                         hash =partial_name_hash(c, hash);//对c和hash进行2维hash484                         c = *(const unsignedchar *)name;485                 } while (c && (c !='/'));486                 this.len = name - (const char*) this.name;487                 this.hash =end_name_hash(hash);488/* remove trailing slashes? */490                 if (!c)491                         goto last_component;492                 while (*++name == '/');493                 if (!*name)494                         gotolast_with_slashes;495


 40 /* Finally: cut down the number of bits to a int value (and try to avoid losing bits) */
 41 static __inline__ unsigned long end_name_hash(unsigned long hash)
 42 {
 43         if (sizeof(hash) > sizeof(unsigned int))
 44                 hash += hash >> 4*sizeof(hash);
 45         return (unsigned int) hash;
 46 }

 

循环体中的局部变量this是一个qstr结构,用来存放路径名中当前节点的hash值和节点名的长度,定义在include/linux/dcache.h之中

 

 

struct qstr {unsigned int hash;unsigned int len;const unsigned char *name;
};


 

其中,481-485 就是逐个字符计算出当前节点的hash值(hash的过程是,给定一个raw和一个hash函数,返回一个hash值),具体函数如下:

 

/* partial hash update function. Assumeroughly 4 bits per character */34static __inline__ unsigned long partial_name_hash(unsigned long c, unsignedlong prevhash)35 {36        prevhash = (prevhash << 4) | (prevhash >> (8*sizeof(unsignedlong)-4));37        return prevhash ^ c;38 }
最终的hash值,index的计算如下:40 /*Finally: cut down the number of bits to a int value (and try to avoid losingbits) */41static __inline__ unsigned long end_name_hash(unsigned long hash)42 {43        if (sizeof(hash) > sizeof(unsigned int))44                 hash += hash >>4*sizeof(hash);45        return (unsigned int) hash;46 }


 

路径名的分隔符是“\”,这里紧跟着当前路径名称的字符有两种可能

(1)是“/0”,表示结尾

(2)是“/”,这里又包含几种情况,/后面有字符路径和没有字符路径(比如ls usr/inlude/)

小结:以上代码,对当前的节点进行hash值的计算,并且将指向路径的指针移动到下一个节点所在的地方,如果节点解析完成,将进行跳转。

 

3.3)接下来查看当前节点

 

I)当前节点是. or ..,需要进行特殊处理

 

从前面的分析知道,当前节点一定是目录节点

 

496                 /*497                  * "." and".." are special - ".." especially so because it has498                  * to be able to know aboutthe current root directory and499                  * parent relationships.500                  */501                 if (this.name[0] == '.')switch (this.len) {502                         default:503                                 break;// is itfor the hiden file?no! if the node is directory, it cannot be named like .name504                         case 2:505                                 if(this.name[1] != '.')506                                         break;507                                follow_dotdot(nd);//如果当前就是“..”,那么需要向上跑到已经到达节点nd->dentry的父目录中去508                                 inode =nd->dentry->d_inode;509                                 /* fallthrough*///nomatter it is .. or ., we go to parse the next node510                         case 1:511                                 continue;512                 }


 

下面,我们来看看 follow_dotdot(nd)

 

[path_walk >> follow_dotdot]
405 static inline void follow_dotdot(structnameidata *nd)406 {407        while(1) {408                 struct vfsmount *parent;409                 struct dentry *dentry;410                read_lock(current->fs->lock);411                 if (nd->dentry ==current->fs->root &&412                     nd->mnt ==current->fs->rootmnt)  {//nd指向的fsmnt结构是否代表根设备413                         read_unlock(¤t->fs->lock);414                         break;415                 }//当前节点就是root节点,所以父节点还是本身,保持不变
 416                read_unlock(¤t->fs->lock);417                 spin_lock(&dcache_lock);418                 if (nd->dentry != nd->mnt->mnt_root){//没有进行设备之间的跳跃419                         dentry =dget(nd->dentry->d_parent);//完成了递增计数器和赋值420                        spin_unlock(&dcache_lock);421                         dput(nd->dentry);422                         nd->dentry =dentry;423                         break;424                 }//下面是跨越了设备的情况
  425                parent=nd->mnt->mnt_parent;
 426                 if (parent == nd->mnt) {427                        spin_unlock(&dcache_lock);428                         break;429                 }430                 mntget(parent);431                dentry=dget(nd->mnt->mnt_mountpoint);432                 spin_unlock(&dcache_lock);433                 dput(nd->dentry);434                 nd->dentry = dentry;435                 mntput(nd->mnt);436                 nd->mnt = parent;437        }438        while (d_mountpoint(nd->dentry) &&__follow_down(&nd->mnt, &nd->dentry))439                 ;440 }441

 

 

这里分三种情况,

 第一种,已到达节点就是本进程的根节点,此时,保持不变。对应第一个if语句。

 第二种,已到达节点的nd->dentry和它的父节点在同一个设备上。这种情况下,父节点的dentry结构必然已经建立在内存中,而且dentry结构中的指针d_patent就指向其父节点,所以往上走一层是简单的事情。第二个if语句

 第三种,已经到达节点nd->dentry就是其所在设备的根节点,再往上就到达另外一个设备中。从文件系统的角度看,安装点和所安装设备的根目录是等价的,而目录树的根目录不等同于设备的根目录,我们在当前设备的根目录中,从这里往上一层,是目录树的上一层目录而不是安装点本身。当将一个存储设备“安装”到另外一个设备的某个节点的时候,内核会分配和设置一个fsmount结构,通过这个结构将两个设备和两个节点连接起来(参见后面“文件系统的安装和拆卸”)。所以,每个安装的设备都有一个fsmount结构,结构中有一个mnt_parent指向父设备,但是根设备的这个指针指向它自己,因为它已经没有父设备了;另外一个指针mnt_mountpoint, 指向代表着安装点的dentry。

 先检查当前Vfs结构是否代表着根设备;如果是的话,立即通过423行的break语句结束while循环;反之,进行相关处理。(这里没有解释清楚,我们在学习了下一节以后再回头看)。

  回到path_walk的代码之中,我们注意到“case2”没有break语句, 所以会通过case 1的continue语句回到for循环开始的地方执行,继续处理下一个节点。大多数节点不是以.命名的,我们来看看正常节点的处理流程。

 


我们来看以下dput:

 

  113  * @dentry: dentry to release 114  *115  * Release a dentry. This will drop the usage count and if appropriate116  * call the dentry unlink method as well as removing it from the queues and117  * releasing its resources. If the parent dentries were scheduled for relea     se118  * they too may now get deleted.119  *120  * no dcache lock, please.121  */123 void dput(struct dentry *dentry)124 {125         if (!dentry)126                 return;127 128 repeat:129         if (!atomic_dec_and_lock(&dentry->d_count, &dcache_lock))130                 return;131 132         /* dput on a free dentry? */133         if (!list_empty(&dentry->d_lru))134                 BUG();135         /*136          * AV: ->d_delete() is _NOT_ allowed to block now.137          */138         if (dentry->d_op && dentry->d_op->d_delete) {139                 if (dentry->d_op->d_delete(dentry))140                         goto unhash_it;141         }142         /* Unreachable? Get rid of it */143         if (list_empty(&dentry->d_hash))144                 goto kill_it;145         list_add(&dentry->d_lru, &dentry_unused);146         dentry_stat.nr_unused++;147         /*148          * Update the timestamp149          */150         dentry->d_reftime = jiffies;151         spin_unlock(&dcache_lock);152         return;153 154 unhash_it:155         list_del_init(&dentry->d_hash);156 157 kill_it: {158                 struct dentry *parent;159                 list_del(&dentry->d_child);160                 /* drops the lock, at that point nobody can reach this dentry */161                 dentry_iput(dentry);162                 parent = dentry->d_parent;163                 d_free(dentry);164                 if (dentry == parent)165                         return;166                 dentry = parent;167                 goto repeat;168         }169 }

 

 

子函数解析:

 

 13 int atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock)14 {15         int counter;16         int newcount;17 18 repeat:19         counter = atomic_read(atomic);//读取atomic指向的值20         newcount = counter-1;21 22         if (!newcount)23                 goto slow_path;24 25         asm volatile("lock; cmpxchgl %1,%2"26                 :"=a" (newcount)27                 :"r" (newcount), "m" (atomic->counter), "0" (counter));//what?28 29         /* If the above failed, "eax" will have changed */30         if (newcount != counter)//why?31                 goto repeat;32         return 0;33 34 slow_path:35         spin_lock(lock);36         if (atomic_dec_and_test(atomic))37                 return 1;38         spin_unlock(lock);39         return 0;40 }

 

 22 #define atomic_read(v)          ((v)->counter)23 #define atomic_set(v,i)         ((v)->counter = (i))

 

100 #define atomic_dec_return(v) atomic_sub_return(1,(v))
101 #define atomic_inc_return(v) atomic_add_return(1,(v))
102 
103 #define atomic_sub_and_test(i,v) (atomic_sub_return((i), (v)) == 0)
104 #define atomic_dec_and_test(v) (atomic_sub_return(1, (v)) == 0)
105 
106 #define atomic_inc(v) atomic_add(1,(v))
107 #define atomic_dec(v) atomic_sub(1,(v))

 


 82 static __inline__ long atomic_sub_return(int i, atomic_t * v)83 {84         long temp, result;85         __asm__ __volatile__(86         "1:     ldl_l %0,%1\n"87         "       subl %0,%3,%2\n"88         "       subl %0,%3,%0\n"89         "       stl_c %0,%1\n"90         "       beq %0,2f\n"91         "       mb\n"92         ".subsection 2\n"93         "2:     br 1b\n"94         ".previous"95         :"=&r" (temp), "=m" (v->counter), "=&r" (result)96         :"Ir" (i), "m" (v->counter) : "memory");97         return result;98 }


II)当前节点是正常节点

 

/*514                  * See if the low-levelfilesystem might want515                  * to use its own hash..516                  */517                 if (nd->dentry->d_op&& nd->dentry->d_op->d_hash) {518                         err =nd->dentry->d_op->d_hash(nd->dentry, &this);//使用自己的hash函数进行hash519                         if (err < 0)520                                 break;521                 }//有些文件系统通过dentry_operations结构中指针d_hash提供专用的hash函数522                 /* This does the actuallookups.. */523                 dentry =cached_lookup(nd->dentry, &this, LOOKUP_CONTINUE);//在内存中寻找该节点已经建立的dentry结构524                 if (!dentry) {//没有在内存中找到相应dentry525                         dentry =real_lookup(nd->dentry, &this, LOOKUP_CONTINUE);//在磁盘上进行寻找526                         err = PTR_ERR(dentry);527                         if (IS_ERR(dentry))528                                 break;}530                 /* Check mountpoints.. */531                 while (d_mountpoint(dentry)&& __follow_down(&nd->mnt, &dentry))532                         ;//what?533534                 err = -ENOENT;535                 inode = dentry->d_inode;536                 if (!inode)537                         goto out_dput;538                 err = -ENOTDIR;539                 if (!inode->i_op)540                         goto out_dput;541542                 if(inode->i_op->follow_link) {543                         err =do_follow_link(dentry, nd);544                         dput(dentry);//删除对应dentry条目545                         if (err)546                                 gotoreturn_err;547                         err = -ENOENT;548                         inode =nd->dentry->d_inode;549                        if (!inode)550                                 break;551                         err = -ENOTDIR;552                         if (!inode->i_op)553                                 break;554                 } else {555                        dput(nd->dentry);556                         nd->dentry =dentry;557                 }558                 err = -ENOTDIR;559                 if(!inode->i_op->lookup)560                         break;561                 continue;562                 /* here ends the main loop */

 

 

       对当前节点的搜索是通过cached_lookup()&real_lookup()两个函数进行的。内核中有个杂凑表,dentry_hashtabel,是一个list_head指针数组,一旦在内存中建立目录节点的dentry结构,就根据其节点名称的hash值挂入hash表的某个队列。当路径名的某个节点变成path_walk()的当前节点时,位于其上游节点的dentry肯定都已经在内存的hash表中,如果在内存的相应结构中找不到相应节点的dentry结构,那么需要通过real_lookup()到磁盘目录寻找,然后载入内存。

 

      内存中还有一个队列dentry_unused,存放共享计数为0,不再使用的dentry结构(有时,也需要把还在使用的dentry结构强制脱链,强迫从磁盘上访问),已经脱链的那个数据结构由最后调用dput()使其共享计数变成0的进程负责将其释放。

 

    事实上,dentry中有6个list_head,d_vfsmnt,d_hash,d_lru,d_child,d_subdirs,d_alias.注意,list_head可以作为队列的头部,也可以将其所在的数据结构挂入到某个队列中。d_vfsmnt尽在dentry作为安装点的时候才使用,一个dentry一旦建立就通过d_hash挂入dentry_hashtable的某个队列里,当共享计数变成0的时候,通过d_lru挂入LRU队列dentry_unused之中,dentry通过d_child挂入父节点的d_subdir之中,同时通过d_patrent结构(这不是一个head_list)指向父目录的dentry结构,而它各自的子目录的dentry结构都在它的d_subdir队列中。

 

    一个dentry对应一个inode,但一个inode可能对应多个dentry。所以在inode中有个队列i_dentry,目录项功过dentry结构中的d_alias挂入相应的inode结构中的i_dentry队列。此外dentry结构中还有指针d_sb,指向其锁在设备超级快的super_block结构;指针d_op,指向特定系统的dentry_operations结构。

 

 

struct list_head {struct list_head *next, *prev;
};  //acttrually, it is only a doubly linked list with no content

 

(未完待续)

 

转载于:https://www.cnblogs.com/jiangu66/p/3187156.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/433370.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么要自学python_为什么那么多自学Python的后来都放弃了,总结起来就这些原因...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼目前信息化产业发展势头很好&#xff0c;互联网就成为了很多普通人想要涉及的行业&#xff0c;因为相比于传统行业&#xff0c;互联网行业涨薪幅度大&#xff0c;机会也多&#xff0c;所以就会大批的人想要转行来学习Python开发。目…

java内部类外部类_Java内部类:如何在内部类中返回外部类对象

Nasted Class 的介绍&#xff0c;请详见参考今天讨论的不是不是内部类的概念&#xff0c;而是具体使用的一个场景&#xff0d;如何在内部类中返回外部对象(孤猪:我在程序里也碰到过一次这样的状况&#xff0c;非静态内部类可以直接调用外部类实例的成员变量&#xff0c;但如何引…

linux 中 timeval结构体

网上很多人写到&#xff0c;timeval结构解释错误 ******************************************************************* 问题如下&#xff1a; 在debian linux的man页中对gettimeofday函数的说明中&#xff0c;有这样一个说明&#xff1a; DESCRIPTION The functions get…

[置顶] C语言中各种数据类型的长度 sizeof char, short, int, long, long long

这些数据类型的sizeof具体长度依赖于编译器和操作系统&#xff08;32-bit or 64-bit&#xff09; 1: 首先&#xff0c;参见c99标准 标准中没有定义这些数据类型的长度&#xff0c;而是定义了这些数据类型能表达的大小范围的最小极限。 C99链接&#xff1a; http://www.open-std…

关机时无人照管更新正在运行_了解iOS13.1后,在决定更新

苹果提前发布了首个重要更新的iOS 13.1&#xff0c;补充了多个 iOS 13 首发时缺失的重要新功能&#xff0c;可以说 iOS 13.1 才是【真正】的 iOS 13 系统。苹果发布iOS13.1,都更新了哪些内容&#xff1f;iOS13.1修复问题和改进问题&#xff1a;?信息中拟我表情可能无法正确跟踪…

java enum in class_Java 8需要一个转换,而Java 7没有 – enum.getClass/getDeclaringClass

我意识到Java 8仍然在测试版&#xff0c;但是这一点让我很奇怪&#xff1a;public class Fields> {public Fields(Set columns) {// A sample column used to find the universe of the enum of Columns.C sampleCol columns.iterator().next();// Java 8 needs a cast her…

linux C 中的volatile使用

一个定义为volatile的变量是说这变量可能会被意想不到地改变&#xff0c;这样&#xff0c;编译器就不会去假设这个变量的值了。精确地说就是&#xff0c;优化器在用到这个变量时必须每次都小心地重新读取这个变量的值&#xff0c;而不是使用保存在寄存器里的备份。下面是volati…

山西计算机网络技术专升本分数线_2020山西成考专升本招生补录第一批公告!附补录院校专业缺额表!...

☞回复【成绩】查询2020年成人高考成绩☞回复【录取】查询20成考录取结果☞回复【补录】查询最新院校缺额信息☞加入学历备考交流群 550985358 专升本第一阶段补录通知与院校缺额信息 达线未被录取&#xff1f;还有机会2020山西成考专升本招生征集志愿第一阶段公告发布12月9日1…

yii mysql 主从_mysql主从同步实践YII

1、两台服务器互联master、slave 2、master配置&#xff1a; server-id 1 master端ID号 log-bin/data/logbin/mysql-bin 日志路径及文件名 #binlog-do-db cacti 同步cacti&#xff0c;此处关闭的话&#xff0c;就是除不允许的&#xff0c;其它的库均同步。 binlog-ignore-db …

网页编码就是那点事

编码一直是让新手头疼的问题&#xff0c;特别是 GBK、GB2312、UTF-8 这三个比较常见的网页编码的区别&#xff0c;更是让许多新手晕头转向&#xff0c;怎么解释也解释不清楚。但是编码又是那么重要&#xff0c;特别在网页这一块。如果你打出来的不是乱码&#xff0c;而网页中出…

python最大正方形的面积_LeetCode 221. 最大正方形 | Python

221. 最大正方形题目在一个由 0 和 1 组成的二维矩阵内&#xff0c;找到只包含 1 的最大正方形&#xff0c;并返回其面积。示例:输入:1 0 1 0 01 0 1 1 11 1 1 1 11 0 0 1 0输出: 4解题思路思路&#xff1a;动态规划本篇幅使用动态规划的原理来解决该问题。我们用 dp(i, j) 表示…

查询学生选修课程管理系统java_JAVA数据库课程设计学生选课管理系统的

《JAVA数据库课程设计学生选课管理系统的》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《JAVA数据库课程设计学生选课管理系统的(59页珍藏版)》请在人人文库网上搜索。1、一、课程设计目的通过这次的设计&#xff0c;主要是做出一个小型的管理系统&#xff0c;来加强…

Linux C 中字符串化操作符#

1 #include <stdio.h>2 3 #define dprint( expr ) printf( "%s %d \n", #expr , expr)4 5 int main(void)6 {7 int x 100;8 int y 2;9 10 dprint(x/y); 11 dprint( xy ); 12 dprint( xy2 ); 13 return 0; 14 } 打印信息&…

Poj1207 The 3n + 1 problem(水题(数据)+陷阱)

一、Description Problems in Computer Science are often classified as belonging to a certain class of problems (e.g., NP, Unsolvable, Recursive). In this problem you will be analyzing a property of an algorithm whose classification is not known for all poss…

python高级玩法_python pandas to_excel 高级玩法

DataFrame.to_excel(self, excel_writer, sheet_name‘Sheet1‘, na_rep‘‘,float_formatNone, columnsNone, headerTrue, indexTrue,index_labelNone, startrow0, startcol0, engineNone,merge_cellsTrue, encodingNone, inf_rep‘inf‘, verboseTrue,freeze_panesNone):1、f…

jquery对象和dom对象

我们都知道可以通过$(selector)的形式对所有页面的对象进行获取&#xff0c;但是获取到的对象和DOM对象有什么区别呢&#xff0c;下面探讨一下 1.DOM对象 var div1document.getElementById(div1); div1.innerHTMLtest; 这样表示获取一个dom对象&#xff0c;这个对象可以使用所有…

u-boot的Makefile分析

U&#xff0d;BOOT是一个LINUX下的工程&#xff0c;在编译之前必须已经安装对应体系结构的交叉编译环境&#xff0c;这里只针对ARM&#xff0c;编译器系列软件为arm-linux-*。 U&#xff0d;BOOT的下载地址&#xff1a; http://sourceforge.net/projects/u-boot 我下载的是1.1.…

Python学习入门基础教程(learning Python)--6.4 Python的list与函数

list是python下的一种数据类型&#xff0c;他和其他类型如整形、浮点型、字符串等数据类型一样也可作为函数的型参和实参来使用&#xff01; 1.list作为参数 list数据类型可以作为函数的参数传递给函数取做相应的处理&#xff0c;下例是统计“www.jeapedu.com”这个字符串里的非…

python 读取outlook_如何用 Python 读取 Outlook 中的电子邮件

从事电子邮件营销&#xff0c; 准入(opt-in)邮箱列表是必不可少的。你可能已经有了准入列表&#xff0c;同时还使用电子邮件客户端软件。如果你能从电子邮件客户端中导出准入列表&#xff0c;那这份列表想必是极好的。我使用一些代码来将 outlook 配置中的所有邮件写入一个临时…

Java代码服务器上下载图片_Java如何从服务器中下载图片

import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.URL;import java.net.URLConnection;import org.apache.commons.io.IOUtils;/*** 从服务器中下载图片** param fileName 图片地址* param response* return*/RequestMappi…