ファイルディスクリプタ
struct fileはstruct fdtableで管理され、current->files->fdtab->fd[]に、配列サイズがfdtab->max_fdsを最大として設定されます。そのインデックスがファイルディスクリプタとなります。close_on_exec/open_fdsは、fd[]のファイルがFD_CLOEXECのファイルかどうかをビットインデックスで管理します。
FD_SET(fd, fdt->open_fds)、flagがO_CLOEXECならFD_SET(fd, fdt->close_on_exec)でfd位置のビットをセットします。
struct fdtableの最大数より小さければ取得済みということで、それ以外expand_fdtable()でfdtableを拡張します。
なお、ビット管理のfdt->open_fds/fdt->close_on_execは、拡張サイズの2倍とし、先頭をfdt->open_fdsへ、中間以降をfdt->close_on_execとします。
プロセス毎の全ファイル数は、setrlimit/getrlimitシステムコールで行えます。
struct fdtable { unsigned int max_fds; struct file __rcu **fd; fd_set *close_on_exec; fd_set *open_fds; struct rcu_head rcu; struct fdtable *next; }; current->files->fdtab->fd[];ファイルopen時、do_sys_open()でget_unused_fd_flags()で取得し、do_filp_open()で取得したstruct fileを、fd_install()でバインドします。
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; }get_unused_fd_flags()はalloc_fd()を、検索開始ファイルIDをstart=0とし走査取得します。files->next_fdは前回取得したIDで、files->next_fd-fd区間は、取得済みの可能性があります。find_next_zero_bit()で、fd以降の空きファイルIDを取得します。
FD_SET(fd, fdt->open_fds)、flagがO_CLOEXECならFD_SET(fd, fdt->close_on_exec)でfd位置のビットをセットします。
#define get_unused_fd_flags(flags) alloc_fd(0, (flags)) int alloc_fd(unsigned start, unsigned flags) { struct files_struct *files = current->files; unsigned int fd; int error; struct fdtable *fdt; spin_lock(&files->file_lock); repeat: fdt = files_fdtable(files); fd = start; if (fd < files->next_fd) fd = files->next_fd; if (fd < fdt->max_fds) fd = find_next_zero_bit(fdt->open_fds->fds_bits, fdt->max_fds, fd); error = expand_files(files, fd); if (error < 0) goto out; if (error) goto repeat; if (start <= files->next_fd) files->next_fd = fd + 1; FD_SET(fd, fdt->open_fds); if (flags & O_CLOEXEC) FD_SET(fd, fdt->close_on_exec); else FD_CLR(fd, fdt->close_on_exec); error = fd; #if 1 /* Sanity check */ if (rcu_dereference_raw(fdt->fd[fd]) != NULL) { printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd); rcu_assign_pointer(fdt->fd[fd], NULL); } #endif out: spin_unlock(&files->file_lock); return error; }rcu_assign_pointer()は、fdt->fd[fd]=fileとなります。fdt->fd[]はRCUでの実装となっています。fdt->fd[fd]=NULLで、掛かるstruct fileを他のプロセスがread中である考慮の必要ありません。
void fd_install(unsigned int fd, struct file *file) { struct files_struct *files = current->files; struct fdtable *fdt; spin_lock(&files->file_lock); fdt = files_fdtable(files); BUG_ON(fdt->fd[fd] != NULL); rcu_assign_pointer(fdt->fd[fd], file); spin_unlock(&files->file_lock); }rlimit(RLIMIT_NOFILE)はカレントプロセスの最大オープンファイル数です。sysctl_nr_openはカーネル全プロセスの最大オープンファイル数です。デフォルトは1024*1024の1Mです。この最大値より取得開始位置が大きければ、これ以上取得できません。
struct fdtableの最大数より小さければ取得済みということで、それ以外expand_fdtable()でfdtableを拡張します。
int sysctl_nr_open __read_mostly = 1024*1024; int expand_files(struct files_struct *files, int nr) { struct fdtable *fdt; fdt = files_fdtable(files); if (nr >= rlimit(RLIMIT_NOFILE)) return -EMFILE; if (nr < fdt->max_fds) return 0; if (nr >= sysctl_nr_open) return -EMFILE; return expand_fdtable(files, nr); } static inline unsigned long rlimit(unsigned int limit) { return task_rlimit(current, limit); } static inline unsigned long task_rlimit(const struct task_struct *tsk, unsigned int limit) { return ACCESS_ONCE(tsk->signal->rlim[limit].rlim_cur); }alloc_fdtable()はnrの応じたstruct fdtableを取得します。そのfdtableにカレントのfdtableを上書きした後、files->fdt=new_fdtとし、カレントだったfdtableを解放します。推測ですが、以前はfdtable->nextに新規取得したfdtableをリストすることで管理していたのでないかと。現状はfdtable->nextは未使用です。実装的には毎回、新規fdtableに旧fdtableを複写し、旧fdtableを解放しますが、ファイルIDの検索/取得と言う点では、1つのfdtableで管理した方が効率的です。
static int expand_fdtable(struct files_struct *files, int nr) __releases(files->file_lock) __acquires(files->file_lock) { struct fdtable *new_fdt, *cur_fdt; spin_unlock(&files->file_lock); new_fdt = alloc_fdtable(nr); spin_lock(&files->file_lock); if (!new_fdt) return -ENOMEM; if (unlikely(new_fdt->max_fds <= nr)) { __free_fdtable(new_fdt); return -EMFILE; } cur_fdt = files_fdtable(files); if (nr >= cur_fdt->max_fds) { /* Continue as planned */ copy_fdtable(new_fdt, cur_fdt); rcu_assign_pointer(files->fdt, new_fdt); if (cur_fdt->max_fds > NR_OPEN_DEFAULT) free_fdtable(cur_fdt); } else { /* Somebody else expanded, so undo our attempt */ __free_fdtable(new_fdt); } return 1; }引数のnrの拡張サイズを補正します。これはfdt->fdはpageサイズ(4k)およびfdt->open_fds/fdt->close_on_execのビット管理のため。と思います。nrを1K単位のファイル数 nr /= (1024 / sizeof(struct file *))として拡張し、2のべき乗となるファイル数 nr /= roundup_pow_of_two(nr + 1)へと補正し、その補正ファイル数でもって改めて拡張サイズ nr *= (1024 / sizeof(struct file *))といたします。
なお、ビット管理のfdt->open_fds/fdt->close_on_execは、拡張サイズの2倍とし、先頭をfdt->open_fdsへ、中間以降をfdt->close_on_execとします。
static struct fdtable * alloc_fdtable(unsigned int nr) { struct fdtable *fdt; char *data; nr /= (1024 / sizeof(struct file *)); nr = roundup_pow_of_two(nr + 1); nr *= (1024 / sizeof(struct file *)); if (unlikely(nr > sysctl_nr_open)) nr = ((sysctl_nr_open - 1) | (BITS_PER_LONG - 1)) + 1; fdt = kmalloc(sizeof(struct fdtable), GFP_KERNEL); if (!fdt) goto out; fdt->max_fds = nr; data = alloc_fdmem(nr * sizeof(struct file *)); if (!data) goto out_fdt; fdt->fd = (struct file **)data; data = alloc_fdmem(max_t(unsigned int, 2 * nr / BITS_PER_BYTE, L1_CACHE_BYTES)); if (!data) goto out_arr; fdt->open_fds = (fd_set *)data; data += nr / BITS_PER_BYTE; fdt->close_on_exec = (fd_set *)data; fdt->next = NULL; return fdt; out_arr: free_fdmem(fdt->fd); out_fdt: kfree(fdt); out: return NULL; }
補足
システムとしての全ファイル数のsysctl_nr_open[root@localhost kitamura]# cat /proc/sys/fs/nr_open 1048576
プロセス毎の全ファイル数は、setrlimit/getrlimitシステムコールで行えます。
#define INR_OPEN_CUR 1024 /* Initial setting for nfile rlimits */ #define INR_OPEN_MAX 4096 /* Hard limit for nfile rlimits */ struct rlimit { unsigned long rlim_cur; unsigned long rlim_max; }; #define INIT_RLIMITS \ { \ [RLIMIT_CPU] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_FSIZE] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_DATA] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_STACK] = { _STK_LIM, _STK_LIM_MAX }, \ [RLIMIT_CORE] = { 0, RLIM_INFINITY }, \ [RLIMIT_RSS] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_NPROC] = { 0, 0 }, \ -----> [RLIMIT_NOFILE] = { INR_OPEN_CUR, INR_OPEN_MAX }, \ [RLIMIT_MEMLOCK] = { MLOCK_LIMIT, MLOCK_LIMIT }, \ [RLIMIT_AS] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_LOCKS] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_SIGPENDING] = { 0, 0 }, \ [RLIMIT_MSGQUEUE] = { MQ_BYTES_MAX, MQ_BYTES_MAX }, \ [RLIMIT_NICE] = { 0, 0 }, \ [RLIMIT_RTPRIO] = { 0, 0 }, \ [RLIMIT_RTTIME] = { RLIM_INFINITY, RLIM_INFINITY }, \ } static inline unsigned long task_rlimit_max(const struct task_struct *tsk, unsigned int limit) { return ACCESS_ONCE(tsk->signal->rlim[limit].rlim_max); }