如何挂载一个文件系统

挂载一个文件系统中最重要的数据结构有以下3个:
1.          file_system_type 要挂载的文件系统类型。
2.          super_block其中有怎样获取该文件系统相关数据的方法。
3.          vfsmount 这个数据结构起到被挂载文件系统和挂载点文件系统的枢纽所用
这几个数据结构的关系,请着重看下图的红色椭圆形区域:

 
 
对照上图,我们可以得出结论,如果要挂载一个文件系统需要做的工作:
1.查找相应的文件系统类型,such as ext2 ext3 or ntfs or yaffs2 or rootfs etc.
2.查找相应的挂载点,方法:一路摸索,顺藤摸瓜。找到其dentry和inode.
3.生成一个vfsmount,这个数据结构是挂载点目录以及正在挂载的这个文件系统的根目录(依据的数据结构是:mnt_mountpoing和mnt_root)的枢纽(非常重要),并将这个vfsmount放在到hashtable中。这个hashtable的hash值运算依据的是挂载点目录以及挂载点inode.除了挂载到hashtable中外,还要链到父挂载点的子链表中。
放在hashtable的原因是,将来在lookup其内的目录或者文件时,需要根据挂载点的目录和挂载点的inode取hash值快速得到vfsmount。
4.主要的工作完成后,还需要把正在挂载的这个文件系统的根目录的inode和dentry取出来放在内存中,其中dentry的值还要赋给vfsmount的mnt_root.
当然这个第4步骤和第3步骤可能会有混合,主要是为了给vfsmount->mnt_root赋值,所以,需要取该文件系统的根目录。
 
按照以上的分析,我们对照review一下内核的源码的流程(为了方便,将与分析无关的语句去掉了):
2393 SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
2394                 char __user *, type, unsigned long, flags, void __user *, data)
2395 {
…….//做些参数检测,然后将用户空间的相关变量拷贝到内核区间
2419
2420         ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
2421                 (void *) data_page);
————————————————————————————-
2191 long do_mount(char *dev_name, char *dir_name, char *type_page,
2192                   unsigned long flags, void *data_page)
2193 {
…做些参数检测工作,然后寻找挂载点。
2211         retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
2212         if (retval)
2213                 return retval;
….
//下面根据参数,选择相应的函数,我们假定最一般的情况,选择do_new_mount
2244         if (flags & MS_REMOUNT)
2245                 retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
2246                                     data_page);
2247         else if (flags & MS_BIND)
2248                 retval = do_loopback(&path, dev_name, flags & MS_REC);
2249         else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
2250                 retval = do_change_type(&path, flags);
2251         else if (flags & MS_MOVE)
2252                 retval = do_move_mount(&path, dev_name);
2253         else
2254                 retval = do_new_mount(&path, type_page, flags, mnt_flags,
2255                                       dev_name, data_page);
——————————————————————————————-
1877 /*
1878  * create a new mount for userspace and request it to be added into the
1879  * namespace's tree
1880  */
1881 static int do_new_mount(struct path *path, char *type, int flags,
1882                         int mnt_flags, char *name, void *data)
1883 {
…做一些其他工作后,开始调用do_kenn_mount,从传参的情况,可以推测,要做的工作是:
1.       找到相关的文件系统
2.       从即将挂载的设备中,取出节点,然后初始化vfsmount
3.       将返回值赋给mnt.
那么最重要的vfsmount已经得到
1894         mnt = do_kern_mount(type, flags, name, data);
1895         if (IS_ERR(mnt))
1896                 return PTR_ERR(mnt);
1897
1898         err = do_add_mount(mnt, path, mnt_flags);
1899         if (err)
1900                 mntput(mnt);
1901         return err;
1902 }
——————————————————————————————-

do_new_mount->do_kern_mount-> vfs_kern_mount
964 vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
 965 {
下面分配一个vfsmount
 975         mnt = alloc_vfsmnt(name);
 976         if (!mnt)
 977                 goto out;

根据情况,调用不同的函数,将vfsmount数据结构填好。以及分配superblock,以及相关的dentry和inode.
 992         if (type->mount) {
 993                 root = type->mount(type, flags, name, data);
 994                 if (IS_ERR(root)) {
 995                         error = PTR_ERR(root);
 996                         goto out_free_secdata;
 997                 }
 998                 mnt->mnt_root = root;
 999                 mnt->mnt_sb = root->d_sb;
1000         } else {
1001                 error = type->get_sb(type, flags, name, data, mnt);
1002                 if (error < 0)
1003                         goto out_free_secdata;
—————————————————————————————————————-
do_new_mount->do_add_mount
1938 static int do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags)
1939 {
….
为了防止前面在获取文件系统内部根目录时其他进程已经挂载到该挂载点(可能是其他文件系统,也可能是正在挂载的文件系统)因此要检测一下
1956         if (path->mnt->mnt_sb == newmnt->mnt_sb &&
1957             path->mnt->mnt_root == path->dentry)
1958                 goto unlock;
1959
1960         err = -EINVAL;
1961         if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
1962                 goto unlock;
1963
1964         newmnt->mnt_flags = mnt_flags;
//把vfsmount插入到合适的链表中,注意,在这里面还将vfsmount中的成员mnt_mountpoint重新赋值为path->dentry.而不是在vfs_kern_mount时赋的mnt_root
1965         err = graft_tree(newmnt, path);
1966
1967 unlock:
1968         up_write(&namespace_sem);
1969         return err;
1970 }
 
从这个代码跟踪,可以发现,基本上挂载一个文件系统依据了我们前面分析的步骤。
欢迎批评指正。
<完>

Posted in File System | 1 Comment

通过简单的例子,学习systemtap

看例子先! 

怎样遍历数组:
1.  #
2.  # Print the system call count by process name in descending order.
3.  #
4.
5.  global syscalls
6.
7.  probe begin {
8.     print ("Collecting data… Type Ctrl-C to exit and display results\n")
9.  }
10.
11. probe syscall.* {
12.    syscalls[execname()]++
13. }
14.
15. probe end {
16.    printf ("%-10s %-s\n", "#SysCalls", "Process Name")
17.    foreach (proc in syscalls-)
18.       printf("%-10d %-s\n", syscalls[proc], proc)
19. }
 
这中间,比较难理解的是第17行,那么它的解释如下:
The variable proc is an index variable that iterates over the range of values possible for the array index of syscalls. Also note the en dash (-) after syscalls that denotes that the iteration runs in reverse order. This character ensures that the number of system calls made print in descending order. To print in ascending order, change the script tosyscalls+.
——————————————————————–
 
例子二:怎样探测内核变量或者局部变量
 
通过stap的L参数可以查看支持的内核变量.在stap的man手册中,有下面一句:
 
stap the -L option should be the used to get possible variables
 
例如:
 
$stap -L 'kernel.function("sys_read")'
kernel.function("sys_read@/build/buildd/linux-2.6.38/fs/read_write.c:402") $fd:unsigned int $buf:char* $count:size_t $fput_needed:int
 
从这个例子中,可以看到,对于内核中的变量,通过$加上变量的名字就可以访问了,但是并不是内核里面所有的变量都可以访问,使用前,请先用stap -L 检测一下!
 
不过在使用的过程中,可能会碰到一些问题,比如对于char * name的变量,用printf("%s\n",$name)输出的时候,会报错,错误的类型是"type mismatch".为什么呢?因为name本身是一个指针,需要转换一下才可以.转换函数可以查看man stapfuncs,不过,有些机器上安装的man手册不太完整,完整的可以看这个网站:http://linux.die.net/man/5/stapfuncs 
在下面列举一些:
 
kernel_string:string (addr:long)
Copy a 0-terminated string from kernel space at given address.
kernel_long:long (addr:long)
Copy a long from kernel space at given address.
kernel_int:long (addr:long)
Copy an int from kernel space at given address.
kernel_short:long (addr:long)
Copy a short from kernel space at given address.
kernel_char:long (addr:long)
Copy a char from kernel space at given address.
user_string:string (addr:long)
Copy a string from user space at given address. If the access would fault, return "<unknown>" and signal no errors.
user_string2:string (addr:long, err_msg:string)
Copy a string from user space at given address. If the access would fault, return instead the err_msg value.
user_string_warn:string (addr:long)
Copy a string from user space at given address. If the access would fault, signal a warning and return "<unknown>".
 
使用其中kernel_string() 就可以把char *name的值取出来:kernel_string($name).
 
除了可以检测到内核里面的变量之外,还可以检测到系统中具有全局意义的变量,比如进程的名字,进程的ID,以及UID等,这些变量被stap包装为一个函数,列表如下:
 
tid() The id of the current thread.
pid() The process (task group) id of the current thread.
uid() The id of the current user.
execname() The name of the current process.
cpu() The current cpu number.
gettimeofday_s() Number of seconds since epoch.可以有us ms等
get_cycles() Snapshot of hardware cycle counter.
pp() A string describing the probe point being currently handled.
probefunc() If known, the name of the function in which this probe was placed.
 
例如下面这个例子,这个例子在运行stap后有哪些进程调用do_fork,以及调用时候的标志是什么:
 
1.  #!/usr/bin/stap
2.
3.  global proc_counter
4.
5.  probe begin {
6.  print ("Started monitoring creation of new processes….Press ^C to terminate\n")
7.  printf ("%-25s %-10s %-s\n", "Process Name", "#Process ID", "#Clone Flags")
8.  }
9.
10. probe kernel.function("do_fork") {
11.    proc_counter++
12.    printf("%-25s %-10d 0x%-x\n", execname(), pid(), $clone_flags)
13. }
14.
15. probe end {
16.    printf ("\n%d processes forked during the observed period\n", proc_counter)
17.}
在上面这个例子中,调用了execname()来访问进程的名字,pid()来访问进程号,$clone_flags来说明其中使用的标志.
 
除此之外,对于syscall还有一些特殊的探测变量:argstr :参数列表,只对syscall有。
name: syscall的名字
retstr.对syscall.return才有
使用这些变量的时候,不用加前面的$符号.
 
另外,systemtap还内置了一些函数:比如我想只跟踪某个程序调用sys_read的情况.怎么判断程序的名字是否与execname()相等呢? 使用systemtap内置的isinstr函数就可以了.例如:
 
probe syscall.open{
  if(isinstr(execname(),"ls")
     printf("%s \n",execname())} 
 
如果是ls程序的话,那么就把该程序的名字打印出来
 
———————
 
好了,最后在贴一个调试程序的例子:
例子3,
  1 probe syscall.* {
  2     en = execname();
  3     ui = uid();
  4     eui = euid();
  5     if (en == "<redacted>")
  6     {
  7         printf("%s(%d): %s(%s)", en, pid(), name, argstr);
  8     
  9         if (ui != eui) {
 10            printf(" as %d/%d ", ui, eui);
 11         } else {
 12         printf(" as %d ", ui);
 13         }
 14      }
 15 }           
 16 
 17 probe syscall.*.return {
 18         en = execname();
 19         if (en == "<redacted>") {
 20             printf("= %s\n", retstr);
 21         }
 
This produces output with system call arguments and return values helpfully decoded for you; it looks like:
 
  <redacted>(14087): open("/etc/passwd", O_RDONLY) as 2315/0 = 3  [...]  <redacted>(14087): close(1) as 2315/0 = -9 (EBADF)
 
<全文完>
注:ubuntu下安装systemtap请参考以前的blog.其他发行版请直接看tutorial. 欢迎批评指正!

 

Posted in Linux Skills | 6 Comments

外网主机ssh和vnc访问内网主机

之前,孤陋寡闻,以为外网访问内网纯属扯淡!早上开shlug的邮件列表,看到这个thread。实验了一下,夷~,竟然可以。太棒了!

外网通过ssh访问内网的方法:
$ ssh -f -N -R 7070:127.0.0.1:22 外网主机用户名@外网主机ip
$ ssh 内网主机用户名@127.0.0.1 -p 7070
原理:将外网7070的端口映射到内网主机的22端口

外网通过VNC访问内网的方法:
同ssh访问的方法一样,我们还可以使用VNC服务:
在内网主机上安装好VNC,然后,开启一个session(默认的端口是从5900开始的)
在内网主机操作:
$vnc4server :1
$vnc4passwd //设定VNC连接密码
$ssh -f -N -R 1234:127.0.0.1:5901 外网主机用户名@外网主机ip
好,已经将5901绑定到外网主机的1234端口了。
然后,操作外网主机:
在外网主机菜单Applications->Internet->Remote Desktop Viewer
选择VNC协议,填入127.0.0.1:1234
然后, 会要求输入密码,就是内网主机通过vnc4passwd设定的密码了。

OK啦~

Posted in Linux Skills | 2 Comments

Linux系统调用宏展开笔记

不知道从哪个版本的内核开始,系统调用变成宏了。在2.6.38的内核里面追踪了一下,以SYSCALL_DEFINE1为例:

 
SYSCALL_DEFINE1(name,…)展开sys_name,后面的数字,若为1则是一个参数,若为2则为2个参数
 
具体的宏,展开跟踪流程如下:
#define SYSCALL_DEFINE1(name, …) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
                                        |   
                                        v   
                        #define SYSCALL_DEFINEx(x, sname, …)              \
                                  __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
                                         |                             
                                         v                             
                        #define __SYSCALL_DEFINEx(x, name, …)                 \
                         asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
                                                     \   
                                                      \   
                                         #define __SC_DECL1(t1, a1)  t1 a1
  
拿一个例子来对照一下: 
SYSCALL_DEFINE1(brk, unsigned long, brk)  
              |                               
              v   
SYSCALL_DEFINE1(1, _brk, unsigned long, brk)
              |                               
              v   
__SYSCALL_DEFINE1(1, _brk, unsigned long ,brk)
              |                    
              v   
asmlinkage long sys_brk(unsigned long brk)
                                    
 

SYSCALL_DEFINE1(brk, unsigned long, brk)—> long sys_brk(unsigned long brk)

{
    unsigned long rlim, retval;
    unsigned long newbrk, oldbrk;
    struct mm_struct *mm = current->mm;
    unsigned long min_brk;
        
    ……
 
    retval = mm->brk;
    up_write(&mm->mmap_sem);
    return retval;
}
 
Posted in Linux Kernel | 1 Comment

Xen-ARM Guest DOM启动以及VCPU切换

/*
从hypervisor到DOM的转换比较复杂,简单的步骤有两个阶段:
1.将guest的contest的寄存器信息赋值,其中还包括一些DOM设置信息
2.调用scheduler通过__switch_to调度到设置的VCPU上
*/
————hypervisor————-
construct_guest_dom()
{
…..
new_thread(v, dsi.v_kernentry, vstack_end, vstartinfo_start);
…..
}

void new_thread(struct vcpu *d, unsigned long start_pc, unsigned long start_stack, unsigned long start_info)
{
unsigned long *domain_stack;
struct cpu_info *ci;
struct cpu_user_regs *domain_context;
struct cpu_user_regs *regs = &d->arch.guest_context.user_regs;

domain_stack = alloc_xenheap_pages(STACK_ORDER);
if(domain_stack == NULL) {
return;
}

ci = (struct cpu_info *)domain_stack;
ci->cur_vcpu = d;

domain_stack += (STACK_SIZE – sizeof(struct cpu_user_regs))/sizeof(unsigned long);

domain_context = (struct cpu_user_regs *)domain_stack;
domain_context->r0 = 0;
domain_context->r12 = start_info;//参数传过来
domain_context->r13 = start_stack;
domain_context->r15 = start_pc;//DOM的启始地址

domain_context->psr = 0×13;

regs->r13 = (unsigned long)domain_stack;
regs->r14 = (unsigned long)ret_to_user;
}

调度VCPU代码:
open_softirq(SCHEDULE_SOFTIRQ, __enter_scheduler);

__enter_scheduler调用__switch_to,传给__switch_to的有三个参数:

#define switch_to(prev,next,last)                                       \
do {                                                                    \
__switch_to(prev,&prev->arch.guest_context, &next->arch.guest_context);   \
} while (0)

ENTRY(__switch_to)
disable_irq    ip            @ ensure IRQs are disabled
add     ip, r1, #(OFFSET_USER_REGS + OFFSET_R4)
save_ctx:
stmia   ip, {r4 – sl, fp, ip, sp, lr}      @ Store most regs on stack

mrc    p15, 0, r4, c3, c0, 0
str    r4, [r1, #(OFFSET_SYS_REGS + OFFSET_VDACR)]
load_ctx:
ldr    r4, [r2, #(OFFSET_SYS_REGS + OFFSET_VDACR)]
mcr    p15, 0, r4, c3, c0, 0

add    ip, r2, #(OFFSET_USER_REGS + OFFSET_R4)
enable_irq    r4
ldmia   ip,  {r4 – sl, fp, ip, sp, pc}       @ Load all regs saved previously

@    mov    pc, lr
nop
nop
b    .

————-dom——————–
ENTRY(stext)
ldr r10, start_info //start_info的地址 -> r10
str r12, [r10]      //r12的值 -> xen_start_info变量(start_info标记处)

ldr sp, __init_sp   //sp->13

b   start_kernel //进入Guest KERNEL

start_info:
.long   xen_start_info

Posted in XENARM | Leave a comment