open O_CREAT 시 생성되는 dentry 가 언제 커널 hashtable (dentry_hashtable) 에
들어가는지 궁금
새로 생성되는 dentry 의 hash 값을 직접 계산하고, hash bucket 을 구해서,
안에 새로 생성된 dentry 가 잘 들어가 있는지 따라가봤습니다.
hash 계산은 hash_name 함수가 수행하고, link_path_walk 시 호출하고 있습니다.
함수 입력으로는 name 과 'parent' dentry 가 사용됩니다.
두 개의 task 에서 동일 name, 'parent' dentry 에 대해 d_alloc_parallel 함수를 부르면
중복 dentry 가 생성될수 있으므로, 같은 hash 값 을 사용하는 in_lookup_hashtable 를 이용해
dentry 중복생성을 방지하고 있습니다.
hash -- d_hash() --> dentry hash bucket
hash -- in_lookup_hash() --> in_lookup dentry hash bucket
d_alloc_parallel 함수는 in_lookup_hashtable 에서 hash 값이 일치하는 dentry 를 발견하면,
만들었던 dentry 는 해제시키고, 발견한 dentry 를 리턴합니다.
1) name 은 "8바이트 단위" 로 나눠가며 0x00(NUL), 0x2f('/') 가 있는지 체크
2) 둘다 없으면 HASH_MIX 수행, 둘중 하나라도 있으면 루프 종료
3) 0x00(NUL), 0x2f('/') 가 있는 8바이트는 마스크를 씌워서 0x00, 0x2f 를 제거하고 xor
4) 마지막으로 fold_hash
name "test2.txt" 와 parent dentry 0xffff9e6a4dfd9480 를 주면
hash 값은 0xc748277 가 나오게 됩니다.
root@user-virtual-machine:~/hash_name# ./hash_name
0x8080808080808000
bits : 0x7fff
adata : 0x8080808080808000, bdata : 0x0, mask : 0xff
x : 0xca6ed79f6fe6390f, y : 0xd033187d266340b9, hash : 0xc748277 <-- test2.txt 의 hash 값
root@user-virtual-machine:~/hash_name#
현재 열려있는 test2.txt 의 hash 값을 확인하면 동일한걸 알수 있습니다.
crash> struct dentry.d_parent ffff9e6a4dde6b40
d_parent = 0xffff9e6a4dfd9480, <-- parent dentry
crash> struct dentry.d_name ffff9e6a4dde6b40 -x
d_name = {
{
{
hash = 0xc748277, <-- hash 값 동일
len = 0x9
},
hash_len = 0x90c748277
},
name = 0xffff9e6a4dde6b78 "test2.txt"
},
crash>
hash 값을 d_hash 함수에 넣으면, hash bucket 을 구할수 있으며, 25508 번째 entry 입니다.
crash> p dentry_hashtable
dentry_hashtable = $1 = (struct hlist_bl_head *) 0xffff9e6b79740000
crash> p d_hash_shift
d_hash_shift = $2 = 13
crash> eval (0xc748277 >> 13)
hexadecimal: 63a4
decimal: 25508
dcache hashtable entries 갯수는 524288 개로, 허용범위 내입니다.
Oct 2 16:24:04 user-virtual-machine kernel: [ 0.071023] Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes, linear)
Oct 2 16:24:04 user-virtual-machine kernel: [ 0.071023] Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes, linear)
Oct 2 16:24:04 user-virtual-machine kernel: [ 0.071023] mem auto-init: stack:off, heap alloc:on, heap free:off
Oct 2 16:24:04 user-virtual-machine kernel: [ 0.071023] software IO TLB: area num 128.
Oct 2 16:24:04 user-virtual-machine kernel: [ 0.101919] Memory: 3880820K/4193716K available (20480K kernel code, 4150K rwdata, 12696K rodata, 4664K init, 17644K bss, 312636K reserved, 0K cma-reserved)
hash bucket 은 ffff9e6b79771d20 입니다.
crash> struct hlist_bl_head -o -x
struct hlist_bl_head {
[0x0] struct hlist_bl_node *first;
}
SIZE: 0x8
crash> eval 8 * 25508
hexadecimal: 31d20
decimal: 204064
crash> eval 0xffff9e6b79740000 + 0x31d20
hexadecimal: ffff9e6b79771d20
hash bucket 에 test2.txt dentry 가 잘 들어있음이 확인됬습니다.
crash> struct hlist_bl_head ffff9e6b79771d20
struct hlist_bl_head {
first = 0xffff9e6a4dde6b48
}
crash> struct dentry.d_hash -o -x
struct dentry {
[0x8] struct hlist_bl_node d_hash; <-- anchor (container_of)
}
crash> eval 0xffff9e6a4dde6b48 - 0x8
hexadecimal: ffff9e6a4dde6b40
crash> struct dentry.d_name ffff9e6a4dde6b40 -x
d_name = {
{
{
hash = 0xc748277,
len = 0x9
},
hash_len = 0x90c748277
},
name = 0xffff9e6a4dde6b78 "test2.txt"
},
crash>
hash 계산에 사용한 예제코드입니다.
https://gitlab.com/feather973/hash_name
hash_name 이후 link_path_walk 는 다음 동작을 수행합니다.
1) 계산된 hash 값은 nd->last.hash_len 에 담습니다.
2) name 이 끝났으므로(NUL byte) walk_component 를 불러서 nd->last 에 대해 lookup 을 수행합니다.
3) dentry 가 없으므로 lookup_fast 는 실패하고, lookup_slow 을 탑니다.
4) lookup_slow 는 d_alloc_parallel 를 불러서 3가지 동작을 수행합니다.
struct dentry *d_alloc_parallel(struct dentry *parent,
const struct qstr *name,
wait_queue_head_t *wq)
{
unsigned int hash = name->hash;
struct hlist_bl_head *b = in_lookup_hash(parent, hash); <-- 이미 만든 dentry 인지 체크
....
/*
* No changes for the parent since the beginning of d_lookup().
* Since all removals from the chain happen with hlist_bl_lock(),
* any potential in-lookup matches are going to stay here until
* we unlock the chain. All fields are stable in everything
* we encounter.
*/
hlist_bl_for_each_entry(dentry, node, b, d_u.d_in_lookup_hash) {
if (dentry->d_name.hash != hash)
continue;
if (dentry->d_parent != parent)
continue;
if (!d_same_name(dentry, parent, name))
continue;
hlist_bl_unlock(b);
/* now we can try to grab a reference */
if (!lockref_get_not_dead(&dentry->d_lockref)) {
rcu_read_unlock();
goto retry;
}
rcu_read_unlock();
/*
* somebody is likely to be still doing lookup for it;
* wait for them to finish
*/
spin_lock(&dentry->d_lock);
d_wait_lookup(dentry);
/*
* it's not in-lookup anymore; in principle we should repeat
* everything from dcache lookup, but it's likely to be what
* d_lookup() would've found anyway. If it is, just return it;
* otherwise we really have to repeat the whole thing.
*/
if (unlikely(dentry->d_name.hash != hash))
goto mismatch;
if (unlikely(dentry->d_parent != parent))
goto mismatch;
if (unlikely(d_unhashed(dentry)))
goto mismatch;
if (unlikely(!d_same_name(dentry, parent, name)))
goto mismatch;
/* OK, it *is* a hashed match; return it */
spin_unlock(&dentry->d_lock);
dput(new); <-- 새로 만든건 해제
return dentry; <-- 이미 만든 dentry 이므로 새로 만들지 말고, 기존것을 리턴
}
rcu_read_unlock();
/* we can't take ->d_lock here; it's OK, though. */
new->d_flags |= DCACHE_PAR_LOOKUP;
new->d_wait = wq;
hlist_bl_add_head_rcu(&new->d_u.d_in_lookup_hash, b); <-- 만든적이 없는 dentry, in_lookup 에 등록한다.
hlist_bl_unlock(b);
return new;
link_path_walk 리턴후, open_lask_lookups 에서 수행하게 됩니다.
lookup 상태인 dentry 가 확인되면
test-4285 [000] ...1. 15273.716792: myprobe: (__d_rehash+0x0/0xc0)
test-4285 [000] ...1. 15273.716813: <stack trace>
=> __d_rehash <-- 5) dentry->d_inode 에 inode 넣고, dentry hashtable 에 등록
=> d_splice_alias <-- 4) d_add
=> ext4_lookup.part.0
=> ext4_lookup <-- 3) negative dentry (new dentry) 를 위한 inode 만들고 d_splice_alias
=> lookup_open.isra.0
=> open_last_lookups <-- 2) link_path_walk 에서 생성된 dentry 를 갖고 lookup_open
=> path_openat <-- 1) link_path_walk 와 open_last_lookups 가 번갈아서 호출
=> do_filp_open
=> do_sys_openat2
=> __x64_sys_openat
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
const struct open_flags *op,
bool got_write)
{
....
if (d_in_lookup(dentry)) { <-- lookup 상태인 dentry
struct dentry *res = dir_inode->i_op->lookup(dir_inode, dentry,
nd->flags); <-- ext4_lookup 호출
d_lookup_done(dentry); <-- in_lookup_hashtable 에서 제거
if (unlikely(res)) {
if (IS_ERR(res)) {
error = PTR_ERR(res);
goto out_dput;
}
dput(dentry);
dentry = res;
}
}
rcu-walk and dentry scaling - Nick Piggin
https://lkml.kernel.org/linux-fsdevel/AANLkTim=aqwYsyR9fb=DTj2BNA5BmwsXRAPz-+OvUZPV@mail.gmail.com/