ファイルディスクリプタ
Rev.3を表示中。最新版はこちら。
struct fileはstruct fdtableで管理され、current->files->fdtab->fd[]に、配列サイズがfdtab->max_fdsを最大として設定されます。そのインデックスがファイルディスクリプタとなります。close_on_exec/open_fdsは、fd[]のファイルがFD_CLOEXECのファイルかどうかをビットインデックスで管理します。
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を取得します。
#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);
}





