システムのオープンファイル数
Rev.1を表示中。最新版はこちら。
ファイルオープンは、get_unused_fd_flags()で/proc/sys/fs/nr_openをプロセス毎の上限として、current->files[]の空きインデックスを取得し、 do_filp_open()からget_empty_filp()で、/proc/sys/fs/file-maxを上限とするファイル構造体を取得し設定します。current->files[]はdup2システムコールで任意のインデックスが設定できます。通常ファイル取得ではインデックス0から走査して空きインデックスを取得しますので、ファイルIDの取得最大値と言えます。/proc/sys/fs/file-maxは、CPU単位の総合システムのファイル制約で、CAP_SYS_ADMINケーパビリティなら掛かる制約はありません。
ファイル構造体カウントはstruct percpu_counter nr_filesで管理され、struct percpu_counterは動的割当てられるCPU変数で、ファイル構造体取得毎に、percpu_counter_inc()でs32 nr_files.countersにアロケートされた領域に、CPU単位のカウントをインクリメントし、32/-32になると、そのカウント値はs64 countに加算され、s32 nr_files.counters内のカレントCPUのカウント値は0とし、以降の取得はs32 nr_files.countersをインクリメントする上記実装を繰り返し行います。
ファイルオープン取得では、get_nr_files()でnr_files.counterを取得し、files_stat.max_filesより大きいと、percpu_counter_sum_positive()で、CPU毎のnr_files.countersをも考慮した数を取得します。nr_files.counter>files_stat.max_filesなら、nr_files.counter+while(cpu){nr_files.counters}>files_stat.max_filesで掛かる処理は必要なさそうですが、これはファイルをクローズすると、CPU単位で-32までnr_files.countersがデクリメントされるためです。
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) { struct open_flags op; int lookup = build_open_flags(flags, mode, &op); char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { fd = get_unused_fd_flags(flags); if (fd >= 0) { struct file *f = do_filp_open(dfd, tmp, &op, lookup); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { fsnotify_open(f); fd_install(fd, f); } } putname(tmp); } return fd; } struct file *get_empty_filp(void) { const struct cred *cred = current_cred(); static long old_max; struct file * f; if (get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)) { if (percpu_counter_sum_positive(&nr_files) >= files_stat.max_files) goto over; } f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL); if (f == NULL) goto fail; percpu_counter_inc(&nr_files); f->f_cred = get_cred(cred); if (security_file_alloc(f)) goto fail_sec; INIT_LIST_HEAD(&f->f_u.fu_list); atomic_long_set(&f->f_count, 1); rwlock_init(&f->f_owner.lock); spin_lock_init(&f->f_lock); eventpoll_init_file(f); return f; over: if (get_nr_files() > old_max) { pr_info("VFS: file-max limit %lu reached\n", get_max_files()); old_max = get_nr_files(); } goto fail; fail_sec: file_free(f); fail: return NULL; }単一CPUならnr_files->count+1、マルチCPUなら(int *)(&*nr_files->counters + cpu_offse[cpu ID]) + 1となするビット単位での設定で、32以上/-32以下なら、nr_files->countに加算し、CPUのnr_files->countersのカウント値を0とします。
static inline void percpu_counter_inc(struct percpu_counter *fbc) { percpu_counter_add(fbc, 1); } #ifdef CONFIG_SMP int percpu_counter_batch __read_mostly = 32; EXPORT_SYMBOL(percpu_counter_batch); static void compute_batch_value(void) { int nr = num_online_cpus(); percpu_counter_batch = max(32, nr*2); } static inline void percpu_counter_add(struct percpu_counter *fbc, s64 amount) { __percpu_counter_add(fbc, amount, percpu_counter_batch); } #else percpu_counter_add(struct percpu_counter *fbc, s64 amount) { preempt_disable(); fbc->count += amount; preempt_enable(); } #endif void __percpu_counter_add(struct percpu_counter *fbc, s64 amount, s32 batch) { s64 count; preempt_disable(); count = __this_cpu_read(*fbc->counters) + amount; if (count >= batch || count <= -batch) { raw_spin_lock(&fbc->lock); fbc->count += count; __this_cpu_write(*fbc->counters, 0); raw_spin_unlock(&fbc->lock); } else { __this_cpu_write(*fbc->counters, count); } preempt_enable(); } static long get_nr_files(void) { return percpu_counter_read_positive(&nr_files); } static inline s64 percpu_counter_read_positive(struct percpu_counter *fbc) { s64 ret = fbc->count; barrier(); /* Prevent reloads of fbc->count */ if (ret >= 0) return ret; return 0; }struct percpu_counterで管理するトータルのカウント値を取得します。 fbc->count + fbc->counters下のCPU毎のカウント値。
static inline s64 percpu_counter_sum_positive(struct percpu_counter *fbc) { s64 ret = __percpu_counter_sum(fbc); return ret < 0 ? 0 : ret; } s64 __percpu_counter_sum(struct percpu_counter *fbc) { s64 ret; int cpu; raw_spin_lock(&fbc->lock); ret = fbc->count; for_each_online_cpu(cpu) { s32 *pcount = per_cpu_ptr(fbc->counters, cpu); ret += *pcount; } raw_spin_unlock(&fbc->lock); return ret; }ファイルクローズで、_fputからfile_free()で、percpu_counter_dec()がコールされ、カレントCPUのnr_files.counters下のカウント値がデクリメントされますが、-32ならpercpu_counter_inc()での実装と同列です。
int filp_close(struct file *filp, fl_owner_t id) { int retval = 0; if (!file_count(filp)) { printk(KERN_ERR "VFS: Close: file count is 0\n"); return 0; } if (filp->f_op && filp->f_op->flush) retval = filp->f_op->flush(filp, id); if (likely(!(filp->f_mode & FMODE_PATH))) { dnotify_flush(filp, id); locks_remove_posix(filp, id); } fput(filp); return retval; } void fput(struct file *file) { if (atomic_long_dec_and_test(&file->f_count)) __fput(file); } static void __fput(struct file *file) { struct dentry *dentry = file->f_path.dentry; struct vfsmount *mnt = file->f_path.mnt; struct inode *inode = dentry->d_inode; might_sleep(); fsnotify_close(file); eventpoll_release(file); locks_remove_flock(file); if (unlikely(file->f_flags & FASYNC)) { if (file->f_op && file->f_op->fasync) file->f_op->fasync(-1, file, 0); } if (file->f_op && file->f_op->release) file->f_op->release(inode, file); security_file_free(file); ima_file_free(file); if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL && !(file->f_mode & FMODE_PATH))) { cdev_put(inode->i_cdev); } fops_put(file->f_op); put_pid(file->f_owner.pid); file_sb_list_del(file); if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) i_readcount_dec(inode); if (file->f_mode & FMODE_WRITE) drop_file_write_access(file); file->f_path.dentry = NULL; file->f_path.mnt = NULL; file_free(file); dput(dentry); mntput(mnt); } static inline void file_free(struct file *f) { percpu_counter_dec(&nr_files); file_check_state(f); call_rcu(&f->f_u.fu_rcuhead, file_free_rcu); } static inline void percpu_counter_dec(struct percpu_counter *fbc) { percpu_counter_add(fbc, -1); }
percpu_counterのnr_filesカウント値に掛かる上記内容の検証サンプル。
[root@localhost lkm]# cat babakaka.c #include <linux/percpu_counter.h> #define TRUE 1 #define FALSE 0 struct percpu_counter babakaka; void get_percpucounter(int line) { long cnt1, cnt2, cnt3; s32 *p; cnt1 = percpu_counter_read_positive(&babakaka); p = per_cpu_ptr(babakaka.counters, get_cpu()); cnt2 = *p; cnt3 = percpu_counter_sum_positive(&babakaka); printk("%02ld+%02ld=%02ld ", cnt1,cnt2,cnt3); if (line) { printk("\n"); } } static int __init babakaka_init(void) { int i; percpu_counter_init(&babakaka, 0); for (i = 1; i <= 50; i++) { percpu_counter_inc(&babakaka); get_percpucounter(FALSE); if (!(i % 10) && i) { printk("\n"); } } percpu_counter_destroy(&babakaka); printk("---babakaka.count is inaccurate by closed file---\n"); percpu_counter_init(&babakaka, 0); for (i = 1; i <= 32; i++) { percpu_counter_inc(&babakaka); } get_percpucounter(TRUE); percpu_counter_dec(&babakaka); get_percpucounter(TRUE); percpu_counter_destroy(&babakaka); return -1; } module_init(babakaka_init);結果:nr_files.count + while(オンラインCPU){nr_files.counters} = 実トータル数
[root@localhost lkm]# insmod babakaka.ko insmod: error inserting 'babakaka.ko': -1 Operation not permitted [root@localhost lkm]# dmesg : 00+01=01 00+02=02 00+03=03 00+04=04 00+05=05 00+06=06 00+07=07 00+08=08 00+09=09 00+10=10 00+11=11 00+12=12 00+13=13 00+14=14 00+15=15 00+16=16 00+17=17 00+18=18 00+19=19 00+20=20 00+21=21 00+22=22 00+23=23 00+24=24 00+25=25 00+26=26 00+27=27 00+28=28 00+29=29 00+30=30 00+31=31 32+00=32 32+01=33 32+02=34 32+03=35 32+04=36 32+05=37 32+06=38 32+07=39 32+08=40 32+09=41 32+10=42 32+11=43 32+12=44 32+13=45 32+14=46 32+15=47 32+16=48 32+17=49 32+18=50 ---babakaka.count is inaccurate by closed file--- 32+00=32 32+-1=31
補足
/proc/sys/fs/file-maxのfiles_stat.max_filesの更新では、32の倍数による制約がなく、files_stat.max_files=40の時、32+18=50等のケースでget_nr_files()=32 percpu_counter_sum_positive()=50で、ファイル取得は不可とすべきが、可能となってしまいます。CAP_SYS_ADMINなら、files_stat.max_filesの制約はなく、掛かるメモリ制約故で、ファイル取得その物の厳密な制約の必要がない故の実装かと思いますが、そうならここまでの実装をあえてしなくても・・・? スパコンを考慮してのことでしょうか? (趣味から始めたITで、プログラミングのふわ〜とした経験はありますが、そもそもITの核とした実務経験はなく、憲法楽者が安保法案は違憲って言ってるような解釈です。)