在linux用户空间中,我们常常需要调用系统调用。下面我们以linux2.6.37版本为例,跟踪一下read系统调用的实现。不同版本的linux系统调用实现可能会有所不同。
在一些应用程序中,我们可以看到如下定义:
scssCopy code
#define real_read(fd, buf, count ) (syscall(SYS_read, (fd), (buf), (count)))
登录后复制
实际上,真正调用的是系统函数syscall(SYS_read),即sys_read()函数。在Linux2.6.37版本中,该函数是通过几个宏定义实现的。
Linux系统调用(SCI,system call interface)实际上是一个多路汇聚以及分解的过程,汇聚点是0x80中断入口点(X86系统结构)。也就是说,所有系统调用都从用户空间中汇聚到0x80中断点,同时保存具体的系统调用号。当0x80中断处理程序运行时,将根据系统调用号对不同的系统调用分别处理,即调用不同的内核函数进行处理。
引起系统调用的途径有两种:
(1)int $0×80,这是老式Linux内核版本中引起系统调用的唯一方式。
(2)sysenter汇编指令
在Linux内核中,我们可以使用下列宏定义来进行系统调用。
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_read(file, buf, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}
return ret;
}
登录后复制
其中SYSCALL_DEFINE3的宏定义如下:
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
登录后复制
##的意思就是宏中的字符直接替换,
如果name = read,那么在宏中__NR_##name就替换成了__NR_read了。 NR##name是系统调用号,##指的是两次宏展开.即用实际的系统调用名字代替”name”,然后再把__NR…展开.如name == ioctl,则为__NR_ioctl。
#ifdef CONFIG_FTRACE_SYSCALLS
#define SYSCALL_DEFINEx(x, sname, ...) \
static const char *types_##sname[] = { \
__SC_STR_TDECL##x(__VA_ARGS__) \
}; \
static const char *args_##sname[] = { \
__SC_STR_ADECL##x(__VA_ARGS__) \
}; \
SYSCALL_METADATA(sname, x); \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#else
#define SYSCALL_DEFINEx(x, sname, ...) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#endif
登录后复制
不管是否定义CONFIG_FTRACE_SYSCALLS宏,最终都会执行 下面的这个宏定义:
__SYSCALL_DEFINEx(x, sname, VA_ARGS)
#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS
#define SYSCALL_DEFINE(name) static inline
long SYSC_##name
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \
asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \
{ \
__SC_TEST##x(__VA_ARGS__); \
return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
} \
SYSCALL_ALIAS(sys##name, SyS##name); \
static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
#else /*
CONFIG_HAVE_SYSCALL_WRAPPERS */
#define SYSCALL_DEFINE(name) asmlinkage
long sys_##name
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
#endif /*
CONFIG_HAVE_SYSCALL_WRAPPERS */
登录后复制
最终会调用下面类型的宏定义:
asmlinkage long sys##name(__SC_DECL##x(VA_ARGS))
也就是我们前面提到的sys_read()系统函数。
asmlinkage通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词!这和我们上一篇文章quagga中提到的宏定义,有异曲同工之妙。
也就是宏定义中的下面代码:
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
file = fget_light(fd, &fput_needed);
if (file) {
loff_t pos = file_pos_read(file);
ret = vfs_read(file, buf, count, &pos);
file_pos_write(file, pos);
fput_light(file, fput_needed);
}
return ret;
登录后复制
代码解析:
- fget_light() :根据 fd 指定的索引,从当前进程描述符中取出相应的 file 对象(见图3)。
- 如果没找到指定的 file 对象,则返回错误
- 如果找到了指定的 file 对象:
- 调用 file_pos_read() 函数取出此次读写文件的当前位置。
- 调用 vfs_read() 执行文件读取操作,而这个函数最终调用 file->f_op.read() 指向的函数,代码如下:
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
- 调用 file_pos_write() 更新文件的当前读写位置。
- 调用 fput_light() 更新文件的引用计数。
- 最后返回读取数据的字节数。
到此,虚拟文件系统层所做的处理就完成了,控制权交给了 ext2 文件系统层。
以上就是Syscall系统调用Linux内核跟踪的详细内容,更多请关注小闻网其它相关文章!
评论(0)