本节中,我们继续讲解,在linux2.4内核下,如果通过一些列函数从路径名找到目标节点。


3.3.1)接下来查看chached_lookup()的代码(namei.c


[path_walk()>> cached_lookup()]/*
*Internal lookup() using the new generic dcache.
*SMP-safe
*给定name和parent进行查找,如果找到而且合法,那么返回找到的dentry;否则,返回NULL
*/staticstruct dentry * cached_lookup(struct dentry * parent, struct qstr *name, int flags)
{
structdentry * dentry = d_lookup(parent, name);if(dentry && dentry->d_op &&dentry->d_op->d_revalidate) { //注意此处最后一项是判断函数指针非空
if(!dentry->d_op->d_revalidate(dentry, flags) &&!d_invalidate(dentry)) { //此处是对dentry的有效性进行验证,对于NTFS非常重要,因为内存中找到的dentry信息可能已经过时
dput(dentry);
dentry= NULL;
}
}
return dentry;
}

      该函数主要通过d_lookup(),代码在fs/dcache.c之中

[path_walk>> cached_lookup() >> d_lookup()]
/**
*d_lookup - search for a dentry
*@parent: parent dentry
*@name: qstr of name we wish to find
*
*Searches the children of the parent dentry for the name in question.If
*the dentry is found its reference count is incremented and the dentry
*is returned. The caller must use d_put to free the entry when it has
*finished using it. %NULL is returned on failure.
*这个是真正的查找函数,给定parent,查找这个parent对应的包含name的dentry目录下的子文件,如果成功,计数器加1并返回;否则,返回NULL指针。这个函数在实际上是一个hash表的查找过程
*/structdentry * d_lookup(struct dentry * parent, struct qstr * name)
{
unsignedint len = name->len;
unsignedint hash = name->hash;
constunsigned char *str = name->name;
struct list_head *head = d_hash(parent,hash);//
struct list_head *tmp;spin_lock(&dcache_lock);
tmp= head->next;//
for(;;) {//由于hash冲突,找到位置以后,需要精确定位dentry所在地址
struct dentry * dentry = list_entry(tmp, struct dentry, d_hash);//tmp是指针,d_hash是成员名字
if(tmp == head)
break;//双向循环链表,解决完毕
tmp= tmp->next; //这是一个循环变量,用于改变tmp的值
if(dentry->d_name.hash != hash)//d_name is the type of qstr
continue;//checkthe hash value,不同名字hash到同一个地址
if(dentry->d_parent != parent)
continue;//checkthe parent value 不同文件夹具有同名的子文件,需要比较patrent
if(parent->d_op && parent->d_op->d_compare) {//checkthe name
if(parent->d_op->d_compare(parent, &dentry->d_name, name))
continue;
}else {
if(dentry->d_name.len != len)
continue;
if(memcmp(dentry->d_name.name, str, len))
continue;
}
__dget_locked(dentry);
dentry->d_vfs_flags|= DCACHE_REFERENCED;
spin_unlock(&dcache_lock);
return dentry;//find the dentry in mem
}
spin_unlock(&dcache_lock);
return NULL;//not find the dentry in mem
}
      首先,本来应该直接根据hash值进行相应的地址定位,但在这里我们选择进一步hash,函数如下

[path_walk()>> cashed_lookup() >> d_lookup() >> d_hash()]

//结合parent进行hash,返回hash以后对应的地址,而不是index。意义:根据父节点的地址进行进一步hash,增强hash值的特异性。

注意:此处的d_hash返回值不是int类型,不是index是地址!!!

staticinline struct list_head * d_hash(struct dentry * parent, unsignedlong hash)
{
hash+= (unsigned long) parent / L1_CACHE_BYTES;
hash= hash ^ (hash >> D_HASHBITS);
return dentry_hashtable + (hash & D_HASHMASK);
}

      list_entry的相应代码如下:

141#define list_entry(ptr, type, member) \

142 ((type *)((char *)(ptr)-(unsigned long)(&((type*)0)->member)))

      这是一个节点地址索引函数,比较难以理解,可以参考这里: 

节点地址的函数list_entry()原理详解

      找到相应的队列头部以后,利用for循环来查找比较简单,唯一的特殊之处在于具体的文件系统可能通过其dentry_operation提供自己的节点名称对比函数,没有的话就用memcmp().

      接下来继续看cached_lookup(),具体的文件系统通过其dentry_operations提供一个对找到的dentry进行验证和处理的函数,如果验证失败,就要通过d_invaliate()将这个数据结构从hash表中删除。原因:在NFS之中,如果一个远程进程是其唯一用户,而且很长时间没有访问它了,那么此时就需要通过访问磁盘上的父目录内容来进行重新构造。具体的函数有dentry_operations结构中通过函数指针d_revaliate提供,最后则根据验证的结果返回一个dentry指针或者出错代码。而很多文件系统,包括ext2,并不提供dentry_operations结构。至此,cached_lookup()就完成了。

3.3.2)接下来查看real_lookup()的代码:

[path_walk()>> real_lookup()]
/*
*This is called when everything else fails, and we actually have
*to go to the low-level filesystem to find out what we should do..
*
*We get the directory semaphore, and after getting that we also
*make sure that nobody added the entry to the dcache in the meantime..
*SMP-safe*/
staticstruct dentry * real_lookup(struct dentry * parent, struct qstr *name, int flags)
{//thefunction: given the parent and the name and the flags, then find thedentry ,add it to the mem of the queue and return it!!
struct dentry * result;
struct inode *dir = parent->d_inode;down(&dir->i_sem);//进入临界区,cannotbe interupted by other process
/*
*First re-do the cached lookup just in case it was created
*while we waited for the directory semaphore..
*
*FIXME! This could use version numbering or similar to
*avoid unnecessary cache lookups.
*/
result= d_lookup(parent, name);// maybe during the sleep, the dentry isbuilt by others
if(!result) {
struct dentry * dentry = d_alloc(parent, name);
result= ERR_PTR(-ENOMEM);
if(dentry) {
lock_kernel();
result= dir->i_op->lookup(dir, dentry);//从磁盘上父节点的目录项中寻找相应目录项并且设置相关信息
unlock_kernel();
if(result)
dput(dentry);//寻找失败,撤销已经分配的dentry
else
result= dentry;//寻找成功
}
up(&dir->i_sem);
return result;//
}/*
*Uhhuh! Nasty case: the cache was re-populated while
* we waited on the semaphore. Need to revalidate.
*/
up(&dir->i_sem);
if(result->d_op && result->d_op->d_revalidate) {
if(!result->d_op->d_revalidate(result, flags) &&!d_invalidate(result)) {
dput(result);
result= ERR_PTR(-ENOENT);
}
}
return result;
}

说明:关于downup函数,互斥信号量的使用,可以参考这里:


I)其中,要建立一个dentry结果,首先要为之分配存储空间并初始化,这是d_alloc()完成的,代在dcache.c之中


#define NAME_ALLOC_LEN(len) ((len+16) & ~15)码/**
*d_alloc - allocate a dcache entry
*@parent: parent of entry to allocate
*@name: qstr of the name
*
*Allocates a dentry. It returns %NULL if there is insufficient memory
*available. On a success the dentry is returned. The name passed in is
*copied and the copy passed in may be reused after this call.
*/struct dentry * d_alloc(struct dentry * parent, const struct qstr *name)
{//alloc the room for the dentry_cache,init the dentry and return it
char* str;
struct dentry *dentry;dentry= kmem_cache_alloc(dentry_cache,GFP_KERNEL);//从专用的slab分配器中分配的,关于内存分配,我们以后再进行解释
if(!dentry)
return NULL;if(name->len > DNAME_INLINE_LEN-1) {
str= kmalloc(NAME_ALLOC_LEN(name->len), GFP_KERNEL);
if(!str) {
kmem_cache_free(dentry_cache,dentry);
return NULL;
}
}else
str= dentry->d_iname;//节点名很短,直接用d_iname保存memcpy(str,name->name, name->len);//d_name.name总是指向这个字符串
str[name->len]= 0;//22 #define atomic_read(v) ((v)->counter)
//23#define atomic_set(v,i) ((v)->counter = (i))atomic_set(&dentry->d_count,1);
dentry->d_vfs_flags= 0;
dentry->d_flags= 0;
dentry->d_inode= NULL;
dentry->d_parent= NULL;
dentry->d_sb= NULL;
dentry->d_name.name= str;
dentry->d_name.len= name->len;
dentry->d_name.hash= name->hash;
dentry->d_op= NULL;
dentry->d_fsdata= NULL;
dentry->d_mounted= 0;
INIT_LIST_HEAD(&dentry->d_hash);
INIT_LIST_HEAD(&dentry->d_lru);
INIT_LIST_HEAD(&dentry->d_subdirs);
INIT_LIST_HEAD(&dentry->d_alias);//对结构体进行初始化就是对结构体所有成员递归的进行初始化
if(parent) {
dentry->d_parent= dget(parent);
dentry->d_sb= parent->d_sb;//从父目录继承sb
spin_lock(&dcache_lock);
list_add(&dentry->d_child,&parent->d_subdirs);
spin_unlock(&dcache_lock);
}else
INIT_LIST_HEAD(&dentry->d_child);dentry_stat.nr_dentry++;
return dentry;
}



II)从磁盘上寻找文件系统,是通过i_op来中的相关结构来实现的,就ext2而言,对目录节点的函数跳转结构是ext2_dir_inode_operations,定义见fs/ext2/namei.c:ext2_lookup()

struct inode_operations ext2_dir_inode_operations = {

create: ext2_create,

lookup: ext2_lookup,

link: ext2_link,

unlink: ext2_unlink,

symlink: ext2_symlink,

mkdir: ext2_mkdir,

rmdir: ext2_rmdir,

mknod: ext2_mknod,

rename: ext2_rename,

};

      具体的函数是ext2_lookup(),仍然在fs/ext2/namei.c

/*
*Methods themselves.
*/
[path_walk()>> real_lookup() >> ext2_lookup()]
163static struct dentry *ext2_lookup(struct inode * dir, struct dentry*dentry)
164{//从磁盘父节点的目录项中寻找相关的目录项,并设置相关信息
165 struct inode * inode;
166 struct ext2_dir_entry_2 * de;
167 struct buffer_head * bh;
168
169 if (dentry->d_name.len > EXT2_NAME_LEN)
170 return ERR_PTR(-ENAMETOOLONG);
171
172 bh = ext2_find_entry (dir, dentry->d_name.name,dentry->d_name.len, &de);//?
173 inode = NULL;
174 if (bh) {
175 unsigned long ino = le32_to_cpu(de->inode);//?
176 brelse (bh);//?
177 inode = iget(dir->i_sb,ino);//根据索引节点号从磁盘读入相应索引节点并在内存中建立相对的inode结构?
178
179 if (!inode)
180 return ERR_PTR(-EACCES);
181 }
182 d_add(dentry, inode);//dentry结构的设置并挂入相应的某个队列?
183 return NULL;
184}

[[path_walk()>> real_lookup() >> ext2_lookup() >> >>ext2_find_entry ]

//这一部分涉及文件读写和设备驱动,我们以后再分析

52/*

53 * ext2_find_entry()

54 *

55 * finds an entry in the specified directory with the wanted name. It

56 * returns the cache buffer in which the entry was found, and theentry

57 * itself (as a parameter - res_dir). It does NOT read the inode ofthe

58 * entry - you'll have to do that yourself if you want to.

59 */

60static struct buffer_head * ext2_find_entry (struct inode * dir,
61 const char * const name,int namelen,
62 struct ext2_dir_entry_2** res_dir)
63{//在指定目录下,查找某一个指定name对应的目录项,返回这个dentry对应的cachebuffer ,但是并不读取这个dentry对应的inode
64 struct super_block * sb;
65 struct buffer_head * bh_use[NAMEI_RA_SIZE];
66 struct buffer_head * bh_read[NAMEI_RA_SIZE];
67 unsigned long offset;
68 int block, toread, i, err;
69
70 *res_dir = NULL;
71 sb = dir->i_sb;
72
73 if (namelen > EXT2_NAME_LEN)
74 return NULL;
75
76 memset (bh_use, 0, sizeof (bh_use)); //关于指针大小与指向指针的指针
77 toread = 0;
78 for (block = 0; block < NAMEI_RA_SIZE; ++block) {
79 struct buffer_head * bh;
80
81 if ((block << EXT2_BLOCK_SIZE_BITS (sb)) >=dir->i_size)
82 break;
83 bh = ext2_getblk (dir, block, 0, &err);
84 bh_use[block] = bh;
85 if (bh && !buffer_uptodate(bh))
86 bh_read[toread++] = bh;
87 }
88
89 for (block = 0, offset = 0; offset < dir->i_size;block++) {
90 struct buffer_head * bh;
91 struct ext2_dir_entry_2 * de;
92 char * dlimit;
93
94 if ((block % NAMEI_RA_BLOCKS) == 0 && toread){
95 ll_rw_block (READ, toread, bh_read);
96 toread = 0;
97 }
98 bh = bh_use[block % NAMEI_RA_SIZE];
99 if (!bh) {
100#if 0
101 ext2_error (sb, "ext2_find_entry",
102 "directory #%lu contains ahole at offset %lu",
103 dir->i_ino, offset);
104#endif
105 offset += sb->s_blocksize;
106 continue;
107 }
108 wait_on_buffer (bh);
109 if (!buffer_uptodate(bh)) {
110 /*
111 * read error: all bets are off
112 */
113 break;
114 }
115
116 de = (struct ext2_dir_entry_2 *) bh->b_data;
117 dlimit = bh->b_data + sb->s_blocksize;
118 while ((char *) de < dlimit) {
119 /* this code is executed quadratically often*/
120 /* do minimal checking `by hand' */
121 int de_len;
122
123 if ((char *) de + namelen <= dlimit &&
124 ext2_match (namelen, name, de)) {
125 /* found a match -
126 just to be sure, do a full check*/
127 if(!ext2_check_dir_entry("ext2_find_entry",
128 dir, de,bh, offset))
129 goto failure;
130 for (i = 0; i < NAMEI_RA_SIZE;++i) {
131 if (bh_use[i] != bh)
132 brelse (bh_use[i]);
133 }
134 *res_dir = de;
135 return bh;
136 }
137 /* prevent looping on a bad block */
138 de_len = le16_to_cpu(de->rec_len);
139 if (de_len <= 0)
140 goto failure;
141 offset += de_len;
142 de = (struct ext2_dir_entry_2 *)
143 ((char *) de + de_len);
144 }
145
146 brelse (bh);
147 if (((block + NAMEI_RA_SIZE) <<EXT2_BLOCK_SIZE_BITS (sb)) >=
148 dir->i_size)
149 bh = NULL;
150 else
151 bh = ext2_getblk (dir, block + NAMEI_RA_SIZE,0, &err);
152 bh_use[block % NAMEI_RA_SIZE] = bh;
153 if (bh && !buffer_uptodate(bh))
154 bh_read[toread++] = bh;
155 }
156
157failure:
158 for (i = 0; i < NAMEI_RA_SIZE; ++i)
159 brelse (bh_use[i]);
160 return NULL;
161}

      继续回到ext2_lookup()的代码,下一步是根据查的索引节点通过iget()找到对应的inode结构,这里的iget()inline函数,定义在inlude/linux/fs.h

staticinline struct inode *iget(struct super_block *sb, unsigned long ino)

{

return iget4(sb, ino, NULL, NULL);

}

      继续追寻iget4(),在fs/inode.c:

962struct inode *iget4(struct super_block *sb, unsigned long ino,find_inode_t find_actor, void *opaque)
963{//根据sb和索引节点号,返回inode结构;如果对应inode结构不存在内存中,就从磁盘上读取相关信息,然后在内存中建立对应的inode结构
964 struct list_head * head = inode_hashtable +hash(sb,ino);//索引节点号在同一设备上才是唯一的,所以计算hash的时候,要加入superblock的地址
965 struct inode * inode;
966
967 spin_lock(&inode_lock);
968 inode = find_inode(sb, ino, head, find_actor,opaque);//在hash表中查找该inode是否在内存中?
969 if (inode) {
970 __iget(inode);//?
971 spin_unlock(&inode_lock);
972 wait_on_inode(inode);//?
973 return inode;
974 }
975 spin_unlock(&inode_lock);
976
977 /*
978 * get_new_inode() will do the right thing, re-trying thesearch
979 * in case it had to block at any point.
980 */
981 return get_new_inode(sb, ino, head, find_actor,opaque);//内存中找不到inode结构,需要从磁盘上读入
982}
983

      inode也涉及内存缓存,inode结构有个hashinode_hashtable,已经建立的inode结构需要通过inode结构中的i_hash(也是一个list_head)挂在hash表中的队列中,首先通过find_inode进行查找,找到以后通过iget进行递增共享计数。

下面来分析iget4所调用的函数:

(1)find_inode:

static struct inode * find_inode(struct super_block * sb, unsigned long ino, struct list_head *head, find     _inode_t find_actor, void *opaque)

 556 /*557  * Called with the inode lock held.558  * NOTE: we are not increasing the inode-refcount, you must call __iget()559  * by hand after calling find_inode now! This simplifies iunique and won't560  * add any additional branch in the common code.561  */562 static struct inode * find_inode(struct super_block * sb, unsigned long ino, struct list_head *head, find_inode_t find_actor, void *opaque)563 {//accually,it is a search in hash table564         struct list_head *tmp;565         struct inode * inode;566 567         tmp = head;568         for (;;) {569                 tmp = tmp->next;570                 inode = NULL;571                 if (tmp == head)572                         break;573                 inode = list_entry(tmp, struct inode, i_hash);574                 if (inode->i_ino != ino)575                         continue;576                 if (inode->i_sb != sb)577                         continue;578                 if (find_actor && !find_actor(inode, ino, opaque))579                         continue;580                 break;581         }582         return inode;583 }

  关于find_inode_t:

    typedef int (*find_inode_t)(struct inode *, unsigned long, void *);

注意此处复杂变量的定义,先将find_inode_t换成一个,普通变量,然后可以理解成,find_inode_t等于这个普通变量所代表的类型。这个例子中,它是一个函数指针。

(2)__iget:

 180 static inline void __iget(struct inode * inode)181 {182         if (atomic_read(&inode->i_count)) {//i_count!=0183                 atomic_inc(&inode->i_count);184                 return;185         }186         atomic_inc(&inode->i_count);//i_count==0187         if (!(inode->i_state & I_DIRTY)) {188                 list_del(&inode->i_list);//将这个节点删除189                 list_add(&inode->i_list, &inode_in_use);//将此节点加入到另外一个链表之中190         }191         inodes_stat.nr_unused--;192 }

i_state字段的意义,可以参考我的系列博文:inode结构体成员详解

(3)wait_on_inode:

 167 static inline void wait_on_inode(struct inode *inode)168 {169         if (inode->i_state & I_LOCK)170                 __wait_on_inode(inode);171 }


继续深入:

152 static void __wait_on_inode(struct inode * inode)153 {154         DECLARE_WAITQUEUE(wait, current);//当前进程进入等待状态155 156         add_wait_queue(&inode->i_wait, &wait);157 repeat:158         set_current_state(TASK_UNINTERRUPTIBLE);159         if (inode->i_state & I_LOCK) {160                 schedule();161                 goto repeat;162         }163         remove_wait_queue(&inode->i_wait, &wait);164         current->state = TASK_RUNNING;165 }

这一部分涉及进程控制,以后再回来看。
(4)get_new_inode

      下面看代码fs/inode.c:

[path_walk()>> real_lookup() >> ext2_lookup() >> iget() >>iget4() >> get_new_inode()]

649/*
650 * This is called without the inode lock held.. Be careful.
651 *
652 * We no longer cache the sb_flags in i_flags - see fs.h
653 * -- rmk@arm.uk.linux.org
654 */
655static struct inode * get_new_inode(struct super_block *sb, unsignedlong ino, struct list_head *head, fi nd_inode_t find_actor, void*opaque)
656{//这段代码的理解可以参考前面dentry的空间分配和内存结构的建立,其中包含了初始化的部分
657 struct inode * inode;
658
659 inode = alloc_inode();//分配空间?
660 if (inode) {
661 struct inode * old;
662
663 spin_lock(&inode_lock);//加锁
664 /* We released the lock, so.. */
665 old = find_inode(sb, ino, head, find_actor,opaque);//sb和ino结合起来,才能在全系统内定位一个索引节点,见前面关于这个函数的解释
666 if (!old) {//找不到old节点,就是说我们从磁盘上调入inode节点
667 inodes_stat.nr_inodes++;
668 list_add(&inode->i_list,&inode_in_use);//把para1加入到para2后面
669 list_add(&inode->i_hash, head);//
670 inode->i_sb = sb;
671 inode->i_dev = sb->s_dev;
672 inode->i_ino = ino;
673 inode->i_flags = 0;
674 atomic_set(&inode->i_count, 1);
675 inode->i_state = I_LOCK;//处于磁盘传送中
676 spin_unlock(&inode_lock);
677
678 clean_inode(inode);//
679 sb->s_op->read_inode(inode);//具体从磁盘山读取inode信息,可能会涉及磁盘驱动
680
681 /*
682 * This is special! We do not need thespinlock
683 * when clearing I_LOCK, because we'reguaranteed
684 * that nobody else tries to do anythingabout the
685 * state of the inode when it is locked, aswe
686 * just created it (so there can be no oldholders
687 * that haven't tested I_LOCK).
688 */
689 inode->i_state &= ~I_LOCK;//IO传送完毕
690 wake_up(&inode->i_wait);
691
692 return inode;
693 }
694
695 /*
696 * Uhhuh, somebody else created the same inode under
697 * us. Use the old inode instead of the one we just
698 * allocated.
699 */
700 __iget(old);//和dget类似
701 spin_unlock(&inode_lock);
702 destroy_inode(inode);//别人已经建立了
703 inode = old;
704 wait_on_inode(inode);
705 }
706 return inode;
707}

      调用函数分析:

      (4.1)alloc_inode:

 

 78 #define alloc_inode() \79          ((struct inode *) kmem_cache_alloc(inode_cachep, SLAB_KERNEL))
涉及内存,以后再看。

       (4.2)clean_inode:

 585 /*586  * This just initializes the inode fields587  * to known values before returning the inode..588  *589  * i_sb, i_ino, i_count, i_state and the lists have590  * been initialized elsewhere..591  */592 static void clean_inode(struct inode *inode)593 {594         static struct address_space_operations empty_aops;595         static struct inode_operations empty_iops;596         static struct file_operations empty_fops;597         memset(&inode->u, 0, sizeof(inode->u));598         inode->i_sock = 0;599         inode->i_op = &empty_iops;600         inode->i_fop = &empty_fops;601         inode->i_nlink = 1;602         atomic_set(&inode->i_writecount, 0);603         inode->i_size = 0;604         inode->i_generation = 0;605         memset(&inode->i_dquot, 0, sizeof(inode->i_dquot));606         inode->i_pipe = NULL;607         inode->i_bdev = NULL;608         inode->i_data.a_ops = &empty_aops;609         inode->i_data.host = inode;610         inode->i_mapping = &inode->i_data;611 }
需要解释的不多。


   (4.3)对有的文件系统来说,是“读入”索引节点,对有的文件系统来说,是将磁盘上的相关信息变成一个索引节点。

      对于节点的读入,具体的函数是通过函数跳转表super_operations结构中的函数指针read_inode提供的。每个设备的super_block结构都有一个指针s_o,指向具体的跳转表。对于ext2来说,这个跳转表就是ext2_sops,具体的函数是ext2_read_inode()(见文件fs/ext2/super.c)

150static struct super_operations ext2_sops = {

151 read_inode: ext2_read_inode,

152 write_inode: ext2_write_inode,

153 put_inode: ext2_put_inode,

154 delete_inode: ext2_delete_inode,

155 put_super: ext2_put_super,

156 write_super: ext2_write_super,

157 statfs: ext2_statfs,

158 remount_fs: ext2_remount,

159};

      函数ext2_read_inode的代码在fs/ext2/inode.c

[path_walk()> real_lookup() > ext2_lookup() > iget() >get_new_inode() >ext2_read_inode()]

961void ext2_read_inode (struct inode * inode)
962{//从磁盘上读取相应的信息,在内存中建立相应的inode节点信息,但是这个函数的输入是什么?
963 struct buffer_head * bh;
964 struct ext2_inode * raw_inode;
965 unsigned long block_group;
966 unsigned long group_desc;
967 unsigned long desc;
968 unsigned long block;
969 unsigned long offset;
970 struct ext2_group_desc * gdp;
971
972 if ((inode->i_ino != EXT2_ROOT_INO && inode->i_ino!= EXT2_ACL_IDX_INO &&
973 inode->i_ino != EXT2_ACL_DATA_INO &&
974 inode->i_ino < EXT2_FIRST_INO(inode->i_sb)) ||
975 inode->i_ino >le32_to_cpu(inode->i_sb->u.ext2_sb.s_es->s_inodes_count)) {//对inode进行合法性check
976 ext2_error (inode->i_sb, "ext2_read_inode",
977 "bad inode number: %lu",inode->i_ino);
978 goto bad_inode;
979 }
980 block_group = (inode->i_ino - 1) /EXT2_INODES_PER_GROUP(inode->i_sb);//计算块组号=ino/每组包含的inode数目
981 if (block_group >= inode->i_sb->u.ext2_sb.s_groups_count){//块的组号非法
982 ext2_error (inode->i_sb, "ext2_read_inode",
983 "group >= groups count");
984 goto bad_inode;
985 }
986 group_desc = block_group >>EXT2_DESC_PER_BLOCK_BITS(inode->i_sb);//该组的组描述符在组描述符表中的位置
987 desc = block_group & (EXT2_DESC_PER_BLOCK(inode->i_sb)- 1);//组描述符具体是该块中第几个描述符
988 bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc];//在高速缓存中找这个组描述符
989 if (!bh) {//没有buffer head
990 ext2_error (inode->i_sb, "ext2_read_inode",
991 "Descriptor not loaded");
992 goto bad_inode;
993 }
994
995 gdp = (struct ext2_group_desc *) bh->b_data;
996 /*
997 * Figure out the offset within the block group inode table
998 */
999 offset = ((inode->i_ino - 1) %EXT2_INODES_PER_GROUP(inode->i_sb)) *
1000 EXT2_INODE_SIZE(inode->i_sb);//计算该索引节点在块中的偏移位置
1001 block = le32_to_cpu(gdp[desc].bg_inode_table) +
1002 (offset >> EXT2_BLOCK_SIZE_BITS(inode->i_sb));
1003 if (!(bh = bread (inode->i_dev, block,inode->i_sb->s_blocksize))) {
1004 ext2_error (inode->i_sb, "ext2_read_inode",
1005 "unable to read inode block - "
1006 "inode=%lu, block=%lu",inode->i_ino, block);
1007 goto bad_inode;
1008 }
1009 offset &= (EXT2_BLOCK_SIZE(inode->i_sb) - 1);
1010 raw_inode = (struct ext2_inode *) (bh->b_data + offset);
1011
1012 inode->i_mode = le16_to_cpu(raw_inode->i_mode);
1013 inode->i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
1014 inode->i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
1015 if(!(test_opt (inode->i_sb, NO_UID32))) {
1016 inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high)<< 16;
1017 inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high)<< 16;
1018 }
1019 inode->i_nlink = le16_to_cpu(raw_inode->i_links_count);
1020 inode->i_size = le32_to_cpu(raw_inode->i_size);
1021 inode->i_atime = le32_to_cpu(raw_inode->i_atime);
1022 inode->i_ctime = le32_to_cpu(raw_inode->i_ctime);
1023 inode->i_mtime = le32_to_cpu(raw_inode->i_mtime);
1024 inode->u.ext2_i.i_dtime = le32_to_cpu(raw_inode->i_dtime);
1025 /* We now have enough fields to check if the inode was activeor not.
1026 * This is needed because nfsd might try to access deadinodes
1027 * the test is that same one that e2fsck uses
1028 * NeilBrown 1999oct15
1029 */
1030 if (inode->i_nlink == 0 && (inode->i_mode == 0|| inode->u.ext2_i.i_dtime)) {
1031 /* this inode is deleted */
1032 brelse (bh);
1033 goto bad_inode;
1034 }
1035 inode->i_blksize = PAGE_SIZE; /* This is the optimal IOsize (for stat), not the fs block size */
1036 inode->i_blocks = le32_to_cpu(raw_inode->i_blocks);
1037 inode->i_version = ++event;
1038 inode->u.ext2_i.i_flags = le32_to_cpu(raw_inode->i_flags);
1039 inode->u.ext2_i.i_faddr = le32_to_cpu(raw_inode->i_faddr);
1040 inode->u.ext2_i.i_frag_no = raw_inode->i_frag;
1041 inode->u.ext2_i.i_frag_size = raw_inode->i_fsize;
1042 inode->u.ext2_i.i_file_acl =le32_to_cpu(raw_inode->i_file_acl);
1043 if (S_ISDIR(inode->i_mode))
1044 inode->u.ext2_i.i_dir_acl =le32_to_cpu(raw_inode->i_dir_acl);
1045 else {
1046 inode->u.ext2_i.i_high_size =le32_to_cpu(raw_inode->i_size_high);
1047 inode->i_size |=((__u64)le32_to_cpu(raw_inode->i_size_high)) << 32;
1048 }
1049 inode->i_generation =le32_to_cpu(raw_inode->i_generation);
1050 inode->u.ext2_i.i_block_group = block_group;
1051
1052 /*
1053 * NOTE! The in-memory inode i_data array is in little-endianorder
1054 * even on big-endian machines: we do NOT byteswap the blocknumbers!
1055 */
1056 for (block = 0; block < EXT2_N_BLOCKS; block++)
1057 inode->u.ext2_i.i_data[block] =raw_inode->i_block[block];

 找到该组的组描述符在组描述符表中的位置。因为组描述符表可能占多个数据块,所以需要确定组描述符在组描述符表的哪一块以及是该块中第几个组描述符。即:group_desc = block_group >> Ext2_DESC_PER_BLOCK_BITS(inode->i_sb) 表示块组号整除每块中组描述符数,计算出该组的组描述符在组描述符表中的哪一块。我们知道,每个组描述符是32字节大小,在一个1K大小的块中可存储32个组描述符。

·      块组号与每块中组的描述符数进行“与”运算,得到这个组描述符具体是该块中第几个描述符。即desc = block_group & (Ext2_DESC_PER_BLOCK(inode->i_sb) - 1)。

·      有了group_desc和desc,接下来在高速缓存中找这个组描述符就比较容易了:

即:bh = inode->i_sb->u.ext2_sb.s_group_desc[group_desc],首先通过s_group_desc[]数组找到这个组描述符所在块在高速缓存中的缓冲区首部;然后通过缓冲区首部找到数据区,即gdp = (struct ext2_group_desc *) bh->b_data。

·      找到组描述符后,就可以通过组描述符结构中的bg_inode_table找到索引节点表首块在高速缓存中的地址:

    offset = ((inode->i_ino - 1) % Ext2_INODES_PER_GROUP(inode->i_sb)) *

        Ext2_INODE_SIZE(inode->i_sb), 计算该索引节点在块中的偏移位置;

block = le32_to_cpu(gdp[desc].bg_inode_table)  +

(offset >> Ext2_BLOCK_SIZE_BITS(inode->i_sb)),计算索引节点所在块的地址;

·      代码中le32_to_cpu()、le16_to_cpu()按具体CPU的要求进行数据的排列,在i386处理器上访问Ext2文件系统时这些函数不做任何事情。因为不同的处理器在存取数据时在字节的排列次序上有所谓“big ending”和“little ending”之分。例如,i386就是“little ending”处理器,它在存储一个16位数据0x1234时,实际存储的却是0x3412,对32位数据也是如此。这里索引节点号与块的长度都作为32位或16位无符号整数存储在磁盘上,而同一磁盘既可以安装在采用“littleending”方式的CPU机器上,也可能安装在采用“big ending”方式的CPU机器上,所以要选择一种形式作为标准。事实上,Ext2采用的标准为“little ending”,所以,le32_to_cpu()、le16_to_cpu()函数不作任何转换。

·      计算出索引节点所在块的地址后,就可以调用sb_bread()通过设备驱动程序读入该块。从磁盘读入的索引节点为ext2_Inode数据结构,前面我们已经看到它的定义。磁盘上索引节点中的信息是原始的、未经加工的,所以代码中称之为raw_ inode,即         raw_inode = (struct ext2_inode *) (bh->b_data + offset)

·      与磁盘索引节点ext2_ inode相对照,内存中VFS的inode结构中的信息则分为两部分,一部分是属于VFS层的,适用于所有的文件系统;另一部份则属于具体的文件系统,这就是inode中的那个union,因具体文件系统的不同而赋予不同的解释。对Ext2来说,这部分数据就是前面介绍的ext2_inode_info结构。至于代表着符号链接的节点,则并没有文件内容(数据),所以正好用这块空间来存储链接目标的路径名。ext2_inode_info结构的大小为60个字节。虽然节点名最长可达255个字节,但一般都不会太长,因此将符号链接目标的路径名限制在60个字节不至于引起问题。代码中inode->u.*设置的就是Ext2文件系统的特定信息。

·      接着,根据索引节点所提供的信息设置inode结构中的inode_operations结构指针和file_operations结构指针,完成具体文件系统与虚拟文件系统VFS之间的连接。

·      目前2.4版内核并不支持存取控制表ACL,因此,代码中只是为之留下了位置,而暂时没做任何处理。

·      另外,通过检查inode结构中的mode域来确定该索引节点是常规文件(S_ISREG)、目录(S_ISDIR)、符号链接(S_ISLNK)还是其他特殊文件而作不同的设置或处理。例如,对Ext2文件系统的目录节点,就将i_op和i_fop分配设置为ext2_dir_inode_operations和ext2_dir_operations。而对于Ext2常规文件,则除i_op和i_fop以外,还设置了另一个指针a_ops,它指向一个address_apace_operation结构,用于文件到内存空间的映射或缓冲。对特殊文件,则通过init_special_inode()函数加以检查和处理。 

从这个读索引节点的过程可以看出,首先要寻找指定的索引节点 ,要找索引节点,必须先找组描述符,然后通过组描述符找到索引节点表,最后才是在这个索引节点表中找索引节点。当从磁盘找到索引节点以后,就要把其读入内存,并存放在VFS索引节点相关的域中。从这个实例的分析,读者可以仔细体会前面所介绍的各种数据结构的具体应用。

未完待续

      在ext2格式的磁盘上,有些索引节点是有特殊用途的,在include/linux/ext2_fs.h中有这些节点的定义:

55/*
56 * Special inode numbers
57 */
58#define EXT2_BAD_INO 1 /* Bad blocks inode */
59#define EXT2_ROOT_INO 2 /* Root inode */
60#define EXT2_ACL_IDX_INO 3 /* ACL inode */
61#define EXT2_ACL_DATA_INO 4 /* ACL inode */
62#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */
63#define EXT2_UNDEL_DIR_INO 6 /* Undelete directory inode*/
64
65/* First non-reserved inode for old ext2 filesystems */
66#define EXT2_GOOD_OLD_FIRST_INO 11
67
68/*
69 * The second extended file system magic number
70 */
71#define EXT2_SUPER_MAGIC 0xEF53
72
73/*
74 * Maximal count of links to a file
75 */
76#define EXT2_LINK_MAX 32000
77
78/*
79 * Macro-instructions used to manage several block sizes
80 */
81#define EXT2_MIN_BLOCK_SIZE 1024
82#define EXT2_MAX_BLOCK_SIZE 4096
83#define EXT2_MIN_BLOCK_LOG_SIZE 10


      这些索引节点都是为系统保留的,对它们的访问不同过目录项而通过定义的节点号进行。磁盘设备的super_block结构中提供磁盘上第一个供常规用途的索引节点的节点号以及索引节点的总数,这两项参数都被用于对节点号的范围检查。

      从概念上,ext2格式的磁盘设备上,除了引导块和超级块以外,剩下部分是索引节点和数据。而实际上分区先划分成若干“记录块组”,然后再将每个记录块组分成索引节点和数据两个部分,而关于“记录块组”的信息记录在超级块中。所以,给定索引节点,访问文件的流程是索引节点号——所在记录块组号+组内偏移——节点所在的记录块号——通过设备驱动读取信息。从磁盘上读取的是ext2-inode数据结构信息,是原始的,代码中称为raw_inode;内存中的inode结构中的信息有两个部分,一个是VFS层面,另外一个是具体的文件系统,就是那个union,对于ext2来说,这部分数据形成一个ext2_inode_info结构,这是在inlude/linux/ext2_fs_i.h定义的:

22struct ext2_inode_info {

23 __u32 i_data[15];

24 __u32 i_flags;

25 __u32 i_faddr;

26 __u8 i_frag_no;

27 __u8 i_frag_size;

28 __u16 i_osync;

29 __u32 i_file_acl; //acess control list

30 __u32 i_dir_acl;

31 __u32 i_dtime;

32 __u32 not_used_1; /* FIX: not used/ 2.2 placeholder */

33 __u32 i_block_group;

34 __u32 i_next_alloc_block;

35 __u32 i_next_alloc_goal;

36 __u32 i_prealloc_block;

37 __u32 i_prealloc_count;

38 __u32 i_high_size;

39 int i_new_inode:1; /* Is a freshly allocated inode */

40};

      结构中的idata存放着一些指针,直接或者间接指向磁盘上保存着这些文件的记录块。至于代表着符号连接的节点,并没有实际的内容,所以正好可以用这块空间存储链接目标的路径名。代码中通过for循环将15个整数复制到inode结构的union中。

      在ext2_read_inode()的代码中继续往下看(fs/ext2/inode.c:

[path_walk()> real_lookup() > ext2_lookup() > iget() >get_new_inode() >ext2_read_inode()]1059 if (inode->i_ino == EXT2_ACL_IDX_INO ||
1060 inode->i_ino == EXT2_ACL_DATA_INO)
1061 /* Nothing to do */ ;
1062 else if (S_ISREG(inode->i_mode)) {
1063 inode->i_op = &ext2_file_inode_operations;
1064 inode->i_fop = &ext2_file_operations;
1065 inode->i_mapping->a_ops = &ext2_aops;
1066 } else if (S_ISDIR(inode->i_mode)) {
1067 inode->i_op = &ext2_dir_inode_operations;
1068 inode->i_fop = &ext2_dir_operations;
1069 } else if (S_ISLNK(inode->i_mode)) {
1070 if (!inode->i_blocks)
1071 inode->i_op =&ext2_fast_symlink_inode_operations;
1072 else {
1073 inode->i_op =&page_symlink_inode_operations;
1074 inode->i_mapping->a_ops = &ext2_aops;
1075 }
1076 } else
1077 init_special_inode(inode, inode->i_mode,
1078 le32_to_cpu(raw_inode->i_block[0]));
1079 brelse (bh);
1080 inode->i_attr_flags = 0;
1081 if (inode->u.ext2_i.i_flags & EXT2_SYNC_FL) {
1082 inode->i_attr_flags |= ATTR_FLAG_SYNCRONOUS;
1083 inode->i_flags |= S_SYNC;
1084 }
1085 if (inode->u.ext2_i.i_flags & EXT2_APPEND_FL) {
1086 inode->i_attr_flags |= ATTR_FLAG_APPEND;
1087 inode->i_flags |= S_APPEND;
1088 }
1089 if (inode->u.ext2_i.i_flags & EXT2_IMMUTABLE_FL) {
1090 inode->i_attr_flags |= ATTR_FLAG_IMMUTABLE;
1091 inode->i_flags |= S_IMMUTABLE;
1092 }
1093 if (inode->u.ext2_i.i_flags & EXT2_NOATIME_FL) {
1094 inode->i_attr_flags |= ATTR_FLAG_NOATIME;
1095 inode->i_flags |= S_NOATIME;
1096 }
1097 return;
1098
1099bad_inode:
1100 make_bad_inode(inode);
1101 return;
1102}

      接着,就是根据句由索引节点所提供的信息设置inode结构中的inode_operations结构指针和file_operations结构指针,完成具体文件系统和虚拟文件系统之间的连接。

      通过检查inode结构中的字段来判断节点是否是常规文件(S_ISREG)目录等而做不同的设置或者处理。例如:目录节点的i_op&i_fop分别设置成指向ext2_dir_inode_operationsext2_dir_operations,对于常规文件,还有两外一个指针a_ops,指向一个address_space_operations数据结构,用于文件到内存空间的映射或者缓冲;对于特殊文件需要用init_special_inode()加以检查和处理。

      找到或者建立了inode结构之后,返回到ext2_lookup(),在那里还要通过d_add()inode结构和dentry结构挂上钩,并将dentry结构挂入hash表的某个队列。这里的d_add()定义在include/linux/dcache.h之中:

200static __inline__ void d_add(struct dentry * entry, struct inode *inode)
201{
202 d_instantiate(entry, inode); //将dentry与inode挂钩(是内存中的吗)
203 d_rehash(entry); //将dentry挂入内存中的某个队列
204}

函数d_instantiate()使得dentry结构和inode结构互相挂钩,代码在fs/dcache.c中:

663void d_instantiate(struct dentry *entry, struct inode * inode)
664{//多个dentry可以对应一个inode
665 spin_lock(&dcache_lock); //进程控制中,用来防止SMP并发的自旋锁
666 if (inode) //
667 list_add(&entry->d_alias, &inode->i_dentry);//将inode对应的dentry加入到dentry的d_alias链表之中,一个inode可以对应多个dentry
668 entry->d_inode = inode;//一个dentry对应一个inode
669 spin_unlock(&dcache_lock);
670}

      两个数据结构的关系是双向的,一方面是dentry结构中的指针d_inode指向inode结构,这是11的关系,但是从inodedentry可以是1对多的关系。

      至于d_rehash()则将dentry结构挂入hash表,代码在同一文件中

847/**
848 * d_rehash - add an entry back to the hash
849 * @entry: dentry to add to the hash
850 *
851 * Adds a dentry to the hash according to its name.
852 */
853
854void d_rehash(struct dentry * entry)
855{
856 struct list_head *list = d_hash(entry->d_parent,entry->d_name.hash);
857 spin_lock(&dcache_lock);
858 list_add(&entry->d_hash, list);//加入d_hash链表之中
859 spin_unlock(&dcache_lock);
860}

      回到real_lookup()的代码,现在已经找到或者建立了所需的dentry结构,接着就返回到path_walk()的代码中(fs/namei.cline497

当前节点的dentry结构有了,需要调用d_mountpoint()检查它是不是一个安装点。 


本文来源:谁不小心的CSDN博客 ext2 源代码解析之 “从路径名到目标结点” (二)

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

相关文章

  1. 菜鸟学Java——如何更好的进行单元测试——JUnit

    测试在软件生命周期中的重要性,不用我多说想必大家也都非常清楚。软件测试有很多分类,从测试的方法上可分为:黑盒测试、白盒测试、静态测试、动态测试等;从软件开发的过程分为:单元测试、集成测试、确认测试、验收、回归等。在众多的分类中,与开发人员关系最紧密的莫过于…...

    2024/4/16 14:46:48
  2. Python生成黑客字典程序(一)

    Python生成黑客字典,首先需要字符串来源 可以使用string包中的printable,具体作用如下: string.printable为所有字符的集合,包含数字,大小写字母,符号包含空格制表符回车等; 使用string.printable[:-9]可获得: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR…...

    2024/4/19 13:43:47
  3. 使用Remix IDE快速部署你的第一个智能合约

    引言 在上一篇博客中点击打开链接,我们已经搭建好了自己的以太坊私有链开发环境,接下介绍一下如何快速的部署一个智能合约。最初开始学习的时候,有很多博客都是直接在geth命令行中进行的编译和部署智能合约,对新人来说不太友好,容易出错。所以本文直接跳过这些繁琐的步骤…...

    2024/4/19 4:40:00
  4. Dynamic Social Network Analysis using Latent Space Models

    Dynamic Social Network Analysis using Latent Space Models 原文地址:https://www.ml.cmu.edu/research/dap-papers/sarkar-kdd_project.pdf这篇文章研究了社交网络建模的两个问题1) 可以应用于对朋友关系建模的动态模型2)方便地从数据中构建这个模型实体在潜在空间中的距…...

    2024/4/16 8:56:00
  5. 嵌入式C语言完全学习笔记进阶篇

    1、数据类型 1.1、基本数据类型 数据类型分2类:基本数据类型+复合类型 基本类型:char short int long float double 复合类型:数组 结构体 共用体 类(C语言没有类,C++有)1.1.1、内存占用与sizeof运算符 数据类型就好像一个一个的模子,这个模子实例化出C语言的变量。变量…...

    2024/4/25 7:14:30
  6. 终端测试 概要

    终端测试引自链接: http://www.atatech.org/articles/15479 一、终端测试简介和传统的PC端测试相比,因为移动终端自身的特性影响,导致终端应用的测试和常见的系统测试有很多不同点。在介绍终端测试之前,我们有必要先熟悉一下移动终端以及终端应用的特性。移动终端,或许换用…...

    2024/4/16 3:14:48
  7. matlab随机网络

    1)求一个网络的平均集聚系数:所有节点的CC之和 / 节点数目N 2)一个节点的CC=邻居实际相连的边 / 邻居间应该相连的边=邻居实际相连的边/(di*(di-1)/2)。其中,di为节点i的度 所以,算节点CC的方法二:以节点i的邻居们为节点,构造邻接矩阵的子图。子图中1的数目的一半就…...

    2024/4/14 22:32:27
  8. java开发 IDE安装一些插件的地址以及MAVEN的安装配置

    一、集成spring环境的IDE下载地址:http://www.springsource.org/downloads/sts-ggts 二、插件下载地址在线安装virgo插件 virgo - http://download.eclipse.org/virgo/milestone/tooling在线安装runjetty插件 runjetty - http://run-jetty-run.googlecode.com/svn/trunk/updat…...

    2024/4/18 22:26:32
  9. 国外著名黑客站点

    allhack.com 本网站提供了图书馆及下载专区。该图书馆为初学者提供了黑客知识和计算机技术基础知识。下载区包括了扫描工具,FLOOD工具,解密工具,拒绝服务攻击等。 alw.nih,gov 在安全目录下有大量的安全工具 anticode.com 入侵攻击,拒绝服务攻击,密钥记录器,邮件炸弹,最…...

    2024/4/14 22:04:42
  10. Intel x86 cache 层次

    Intel x86 cache 层次overviewcache 结构 overview L1Dcache、L1Icache、L2cache,LLC(last level cache)多个处理器核集成到同一个CPU芯片上,增加访存带宽. cache 结构 cache 没有独立的编程空间,是内存的一个子集。cache 单元在不同时刻存储不同内存单元。所以需要一种机制…...

    2024/5/2 22:05:24
  11. 六年换俩皮 雷达币死灰复燃

    作者|凯尔编辑|文刀近日,连云港市警方将贝尔链定性为传销,将对涉案人员移交检察院审查起诉。这一涉案金额巨大的涉币传销项目终将迎来法律审判,但仍有类似的骗局在民间活跃。今年6月10日,沉寂了一段时间的雷达币(VBC)异动,在其官网上,雷达币价格从275元快速起涨,12天时…...

    2024/4/14 22:04:39
  12. 数据库测试规范

    特别说明:本文虽归类于原创,但并非原创,实收集整理于网络!一、 数据库测试概论 随着软件业的迅猛发展,我们的开发也从以前的单层结构进入了三层架构甚至现在多层架构的设计,而数据库从以前一个默默无闻的后台仓库,逐渐成为了数据库系统,而数据库开发设计人员成为了炙手…...

    2024/4/16 7:23:02
  13. 初学python:Ubuntu安装一个可视编程IDE

    Ubuntu 下python可视化编程环境spyder安装和代码小试对于初学linux的很多同学来说,命令行运行程序不是很习惯,很多教程安装eclipse,个人也安装过,但是配置很繁琐,就用spyder有啥不可?spyder:是Python(x,y)的作者为它开发的一个简单的集成开发环境。和其他的Python开发环境…...

    2024/4/14 22:04:44
  14. 从黑客到极客——hacker文化的演化

    说到黑客文化,就不能不提到技术。黑客是一个复杂的文化,不同的人,不同的时代有不同的解读。黑客的一端是技术,另一端是文化。如同武侠一般,一端是武,一端是俠。金大侠说过,武好写,侠难写。同样,要讲述技术,虽然艰涩,但总是可以表达的,难以表达的是黑客文化中的种种…...

    2024/4/17 11:43:32
  15. 社交数据在征信领域的应用探索

    由51CTO举办的WOT”互联网+”时代大数据技术峰会上,来自腾讯数据挖掘高级工程师刘黎春做了以《社交数据在征信领域的应用探索》为主题的演讲,主要内容由社交征信背景、腾讯社交网络数据、个体用户画像研究、社团圈子研究、模型建设及应用这五部分构成,下面我们就逐一为大家…...

    2024/4/21 14:59:03
  16. 这才是你需要的C语言、C++学习路线!请一定看到最后!

    先聊几个有趣的问题这几个问题都是私信里常被问到的,也是很多人当时学习过程中的一些疑惑。问:为啥我学完了C语言或者C++,却还是啥东西也做不出来?答:编程语言学完了就能做出东西那也真是天才哇!应该说语言学得就算再精通,它其实也只代表完成了“最小的”那一部分,和实…...

    2024/4/14 22:32:26
  17. Sodility配置本地IDE和共享目录

    Sodility配置本地IDE和共享目录 如同其他编程工作,我们第一步要配置本地的IDE,我们使用的是Mist浏览器中的remixIDE Mist==》 开发==》 Open Remix IDE显示上面的界面。这个IDE可以创建文件,但是,我们在本地看不到(右键,看不到文件目录),为了解决这个问题,我们要安装…...

    2024/4/14 22:32:26
  18. 黑客是否光顾过你的电脑

    黑客是否光顾过你的电脑 □湖北 魏利频黑客病毒发作时什么样?很难说,因为它们发作时的情况多种多样。但是如果你的计算机有以下表现,就很可能染上黑客病毒了。计算机有时死机,有时又重新启动;在没有执行什么操作的时候,却在拼命读写硬盘;系统莫明其妙地对软驱进行搜索;…...

    2024/4/17 4:23:36
  19. 3500万海外用户加持,NAUS解锁区块链社交新姿势

    x点击上方“蓝色字”可关注我们!编辑:铅笔盒区块链至今还没有任何一个应用真正实现覆盖大规模主流人群,想要快速切入主流人群,社交领域无疑是一个好的方向。社交网络是互联网时代最基础和重要的应用之一。因其对用户强大的吸引力和刚性需求,据第三方机构统计,2017年全球社…...

    2024/5/2 5:58:41
  20. [配置]VUE中通过process.env判断开发,测试和生产环境,并分环境配置不同的URL HOST

    需求: VUE开发前端项目,需要分环境配置URL和做相关处理 思路:通过process.env做判断和处理 实现一:环境区分 1,找到项目配置文件夹: config文件夹 2,进入如下三个文件: dev.env.js 对应开发环境 test.env.js 对应测试环境 prod.env.js 对应生产环境 找到如下代码: //…...

    2024/4/18 12:05:41

最新文章

  1. 基于springboot实现公司日常考勤系统项目【项目源码+论文说明】

    基于springboot实现公司日常考勤系统演示 摘要 目前社会当中主要特征就是对于信息的传播比较快和信息内容的安全问题&#xff0c;原本进行办公的类型都耗费了很多的资源、传播的速度也是相对较慢、准确性不高等许多的不足。这个系统就是运用计算机软件来完成对于企业当中出勤率…...

    2024/5/2 22:36:24
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. golang的引用和非引用总结

    目录 概述 一、基本概念 指针类型&#xff08;Pointer type&#xff09; 非引用类型&#xff08;值类型&#xff09; 引用类型&#xff08;Reference Types&#xff09; 解引用&#xff08;dereference&#xff09; 二、引用类型和非引用类型的区别 三、golang数据类型…...

    2024/4/30 7:48:08
  4. vscode安装通义灵码

    作为vscode的插件&#xff0c;直接使用 通义灵码-灵动指间&#xff0c;快码加编&#xff0c;你的智能编码助手 通义灵码&#xff0c;是一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成、代码解释、研…...

    2024/5/1 13:43:53
  5. 面试算法-140-接雨水

    题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2…...

    2024/5/1 13:55:50
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57