Archive for the ‘Linux’ category

VFS Data Structure关系(2)

October 27th, 2014

之前分析了文件系统主要的数据结构inode,dentry,super_block,file.

为了加快文件的一些操作,还引入了中间的数据结构。

struct file_system_type(include/linux/fs.h)

file_system_type结构用来描述具体的文件系统的类型信息。被Linux支持的文件系统,都有且仅有一 个file_system_type结构而不管它有零个或多个实例被安装到系统中。

struct file_system_type {
    const char *name;
    int fs_flags;
#define FS_REQUIRES_DEV     1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE      4
#define FS_USERNS_MOUNT     8   /* Can be mounted by userns root */
#define FS_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */
#define FS_RENAME_DOES_D_MOVE   32768   /* FS will handle d_move() during rename() internally. */
    struct dentry *(*mount) (struct file_system_type *, int,
               const char *, void *);
    void (*kill_sb) (struct super_block *);
    struct module *owner;
    struct file_system_type * next;
    struct hlist_head fs_supers;

    struct lock_class_key s_lock_key;
    struct lock_class_key s_umount_key;
    struct lock_class_key s_vfs_rename_key;
    struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

    struct lock_class_key i_lock_key;
    struct lock_class_key i_mutex_key;
    struct lock_class_key i_mutex_dir_key;
};

和路径查找相关:

struct nameidata(include/linux/namei.h)

struct nameidata {
    struct path path;
    struct qstr last;
    struct path root;
    struct inode    *inode; /* path.dentry.d_inode */
    unsigned int    flags;
    unsigned    seq, m_seq;
    int     last_type;
    unsigned    depth;
    char *saved_names[MAX_NESTED_LINKS + 1];
};

被Linux支持的文件系统,都有且仅有一个file_system_type结构而不管它有零个或多个实例被安装到系统 中。每安装一个文件系统,就对应有一个超级块和安装点。也就是说如果有多个ext4 文件系统,也就有多个ext4的super_block 与 vfsmount 。

超级块通过它的一个域s_type指向其对应的具体的文件系统类型。具体的 文件系统通过file_system_type中的一个域fs_supers链接具有同一种文件类型的超级块。同一种文件系统类型的超级块通过域s_instances链 接。

 

通过下图,我们可以看到第一个与第三个super_block都指向同一个file_system_type

super_block

 

从下图可知,进程通过task_struct中的一个域files_struct files来找到它当前所打开的文件对象;而我们通常所说的文件 描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找 到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。

这里有一个非常重要的数据结构

struct files_struct(include/linux/fdtable.h)

/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
    atomic_t count;
    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
    spinlock_t file_lock ____cacheline_aligned_in_smp;
    int next_fd;
    unsigned long close_on_exec_init[1];
    unsigned long open_fds_init[1];
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

我们可以看到fd_array 这个二维fd连接一个struct file类型的数据结构,这个struct file就是连接进程与文件的媒介,比dentry,inode更加高级!

super_block_and_task

 

 

通过学习VFS文件结构,我们就可以理解跨文件系统传输的基本原理。

下面的图我是按照kernel 3.14.8来画的,可以清晰的看出task_struct文件结构:

 

QQ图片20141027214645

 

 

Kdump 与 grub2 操作

October 24th, 2014

我们在调试kernel的时候,经常会出现kernel panic。这时我们system全部按键失灵,无法查看dmesg信息。

这时我们就要使用kdump工具。他会在system 重启前,将dmesg和vmcore信息保存到默认路径/var/crash/下

[root@localhost lzz]# vim /var/crash/127.0.0.1-2014.10.24-20\:22\:03/vmcore
vmcore            vmcore-dmesg.txt
[root@localhost lzz]# vim /var/crash/127.0.0.1-2014.10.24-20\:22\:03/vmcore
vmcore            vmcore-dmesg.txt

kdump工具主要是面向redhat产品,由于我使用的是fedora,比较好的兼容了redhat5.

 

最主要是安装 yum install –enablerepo=fedora-debuginfo –enablerepo=updates-debuginfo kexec-tools crash kernel-debuginfo kdump

然后在使用systemctl start kdump 启动服务

[root@localhost 127.0.0.1-2014.10.24-20:22:03]# systemctl status kdump
kdump.service - Crash recovery kernel arming
   Loaded: loaded (/usr/lib/systemd/system/kdump.service; enabled)
   Active: active (exited) since 五 2014-10-24 20:23:01 CST; 2min 5s ago
  Process: 641 ExecStart=/usr/bin/kdumpctl start (code=exited, status=0/SUCCESS)
 Main PID: 641 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/kdump.service

10月 24 20:23:01 localhost.localdomain kdumpctl[641]: kexec: loaded kdump kernel
10月 24 20:23:01 localhost.localdomain kdumpctl[641]: Starting kdump: [OK]
10月 24 20:23:01 localhost.localdomain systemd[1]: Started Crash recovery kernel arming.

PS:由于在linux发行版中,各大发行版逐渐开始使用systemctl ,所以chkconfig/service 命令都会在不久以后移除。

学习system  systemd-vs-sysVinit-cheatsheet

在配置linux kernel for mce-inject中

我们要在grub2中进行一些配置:

在grub2中,/boot/grub2/grub.cfg是脚本自动生成的(使用grub2-mkconfig命令)

我们需要在[root@localhost 127.0.0.1-2014.10.24-20:22:03]# vim /etc/default/grub

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
RUB_CMDLINE_LINUX="rd.lvm.lv=fedora/swap rd.md=0 rd.dm=0 $([ -x /usr/sbin/rhcrashkernel-param ] && /usr/sbin/rhcrashkernel-p    aram || :) rd.luks=0 vconsole.keymap=us rd.lvm.lv=fedora/root rhgb quiet crashkernel=240M"
GRUB_DISABLE_RECOVERY="true"
GRUB_THEME="/boot/grub2/themes/system/theme.txt"

在RUB_CMDLINE_LINUX中,我们在最后家一个crashkernel=240M ,这个240M是根据1GB-80M来计算的,太小的内存会造成kdump service 启动失败!

然后使用

# grub2-mkconfig -o /boot/grub2/grub.cfg

会自动更新grub2启动脚本,我们在/boot/grub2/grub.cfg看到生成的grub信息。

然后启动之后我们通过free -m /top命令看memory都会变小!也就意味着内存有一部分被分出去作crashkernel内存区域!

其实这个kdump实质就是在kernel crash掉后,system启动了crashkernel 来收集信息。

 

 

参考:

http://blog.csdn.net/sabalol/article/details/7043313

http://www.ibm.com/developerworks/cn/linux/l-kexec/

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Kernel_Crash_Dump_Guide/sect-kdump-config-cli.html

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/pdf/Kernel_Crash_Dump_Guide/Red_Hat_Enterprise_Linux-7-Kernel_Crash_Dump_Guide-en-US.pdf

打印VFS中的结构体

October 22nd, 2014

通过打印VFS结构体,我们可以快速掌握VFS主要结构体之间的关系

详见http://www.lizhaozhong.info/archives/1080

我之前在网上找了许多资料,都是关于linux 2.X的,有个问题在与inode中的i_dentry在linux 3.X中以hlist形式出现,我们都知道hash list比其一般的list_head查找速度更快。尤其是在大规模的链表中,具体的hlist定义在include/linux/list.h中。

他们都是一个个宏函数:

#define hlist_entry(ptr, type, member) container_of(ptr,type,member)

#define hlist_for_each(pos, head) \
 for (pos = (head)->first; pos ; pos = pos->next)

#define hlist_for_each_safe(pos, n, head) \
 for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
 pos = n)
.......

用法与list差不多http://www.lizhaozhong.info/archives/951

这里就不一一说明了。

我们要知道从super_block到inode,然后从inode寻找dentry是可行的。inode与dentry本来就是互通的。

包括从task_struct指向struct file -> dentry->inode都是可行的,比较灵活.

这里要说明的是前面那4个宏定义,每个kernel版本都是不同的,需要用注释命令进行查看。

#define SUPER_BLOCKS_ADDRESS 0xffffffff81c72cb0//  $cat /proc/kallsyms | grep super_block
#define SB_LOCK_ADDRESS 0xffffffff81fd5fa0// cat /proc/kallsyms | grep sb_lock
#define FILE_SYSTEM_ADDRESS 0xffffffff81fd6b88
#define FILE_SYSTEM_LOCK_ADDRESS 0xffffffff81fd6b80

int traverse_superblock(void)
{
	struct super_block *sb;
	struct list_head *pos;
	struct list_head *linode;
	struct inode *pinode;
//	struct hlist_head *ldentry;
	struct dentry *pdentry,*parents;
//	char *buffer= kmalloc(sizeof(char)*10000,GFP_KERNEL);	

	unsigned long long count = 0;
	printk("print some fields of super blocks:\n");

//	if(buffer==NULL)
//		return -ENOMEM;
	spin_lock((spinlock_t *)SB_LOCK_ADDRESS);

	list_for_each(pos,(struct list_head *)SUPER_BLOCKS_ADDRESS)
	{
		sb=list_entry(pos,struct super_block,s_list);
		printk("dev_t:	%d,	%d\n",MAJOR(sb->s_dev),MINOR(sb->s_dev));
		printk("fs_name:	%s\n",sb->s_type->name);

		list_for_each(linode,&sb->s_inodes)
		{
			pinode = list_entry(linode,struct inode,i_sb_list);
			count++;
			printk("%lu[",pinode->i_ino);

//			pdentry = d_find_alias(pinode);
			hlist_for_each_entry(pdentry,&pinode->i_dentry,d_alias)
			{
				parents = pdentry;
				while (!IS_ROOT(parents))
				{
					printk("%s->",parents->d_name.name);
					parents = parents->d_parent;
				}
				//memset(buffer,'\0',sizeof(buffer));
				//buffer = dentry_path_raw(parents,buffer,sizeof(buffer));
				//printk("%s",buffer);
			}

			printk("/]\n");
		}

		printk("\n");
	}
	spin_unlock((spinlock_t *)SB_LOCK_ADDRESS);
	printk("the number of inodes: %llu\n",sizeof(struct inode *)*count);
}

static int print_init(void)
{
	struct file_system_type **pos;
	printk("\n\nprint file system_type:\n");

	read_lock((rwlock_t *)FILE_SYSTEM_LOCK_ADDRESS);
	pos	=(struct file_system_type **)FILE_SYSTEM_ADDRESS;

	while(*pos)
	{
		printk("name: %s\n",(*pos)->name);
		pos = &((*pos)->next);
	}

	read_unlock((rwlock_t *)FILE_SYSTEM_LOCK_ADDRESS);
	return 0;
}

static int __init traverse_init(void)
{
//	print_init();
	traverse_superblock();
	return 0;
}

这里我们要阐述一个问题,比如我们想得到一个dentry的fullpath,需要一直向上遍历d_parent。判断是否到root path ,就是判断他是否是指向自己就可以了。
struct file_system_type **pos是一个指针数组,每个元素长度都是不同的。

 

 

dmesg.log

 

 

VFS Data Structure关系(1)

October 20th, 2014

 VFS是管理具体文件系统的接口,Linux可支持数十种文件系统,不同的文件系统可以同时共存于一个系统之中。这些不同类型的文件系统并不是各自封闭的,会进行文件复制和移动等。

VFS是在各种具体的文件系统之上建立了一个抽象层,它屏蔽了不同文件系统间的差异。它之所以可以将各种文件系统纳入其中,是因为它提供了一个通用的文件系统模型。

VFS主要通过一组数据结构来描述文件对象。其中有四个基本的结构体:

超级块(struct super_block):它描述一个已安装了的文件系统。

索引结点(struct inode):它描述一个文件。

目录项(strcut dentry):它描述文件系统的层次结构。一个完整路径的每个组成部分都是一个目录项。比如打开/home/lzz/code/hello.c时,内核分别为/,home/,lzz/,code/,hello.c创建相应的目录项。

文件(struct file):它描述一个已被进程打开的文件。

位置:

block

VFS采用面向对象的思想,在上述每一个结构体中即包含描述每个文件对象属性的数据,又包含对这些数据进行操作的函数指针结构体。也就是说,上述四个基本的结构体中,每一个结构体中又嵌套了一个子结构体,这个子结构体包含了对父结构体进行各种操作的函数指针。

struct dentry (include/linux/dcache.h)

为了方便对目标文件的快速查找,VFS引入了目录项。目标文件路径中的每一项都代表一个目录项,比如/home/test.c中,/,home,test.c都分别是一个目录项。这些目录项都属于路径的一部分,并且每个目录项都与其对应的inode相联系。如果VFS得到了某个dentry,那么也就随之得到了这个目录项所对应文件的inode,这样就可以对这个inode所对应的文件进行相应操作。所以,依次沿着目标文件路径中各部分的目录项进行搜索,最终则可找到目标文件的inode。

与超级块和索引结点不同的是,目录项在磁盘上并没有对应的实体文件,它会在需要时候现场被创建。因此,在目录项结构体中并没有脏数据字段,因为目录项并不会涉及重写到磁盘。

d_inode:与该目录项相关联的索引结点;

struct super_block (include/linux/fs.h)

超级块结构代表一个已经安装了的文件系统,其存储该文件系统的有关信息。对于一个基于磁盘的文件系统来说,这类对象存放于磁盘的特定扇区中;对于非基于磁盘的文件系统,它会在该文件系统的使用现场创建超级块结构并存放在内存中。

s_inode 代表该文件系统中所有的索引结点形成一个双联表,该字段存放这个链表的头结点;

s_files:该文件系统中所有已被打开的文件形成一个双联表,该字段存放这个链表的头结点;

struct file(include/linux/fs.h)

每当一个进程打开一个文件时,内存中有会有相应的file结构体。因此,当一个文件被多个进程打开时,这个文件就会有多个对应的文件结构体。但是,这些文件结构体对应的索引结点和目录项却是唯一的

struct inode (include/linux/fs.h)

索引结点结构体用来描述存放在磁盘上的文件信息。每当内核对磁盘上的文件进行操作时,就会将该文件的信息填充到一个索引结点可以代表一个普通的文件,也可以代表管道或者设备文件等这样的特殊文件

i_sb_list:每个文件系统中的inode都会形成一个双联表,这个双链表的头结点存放在超级块的s_inodes中。而该字段中的prev和next指针分别指向在双链表中与其相邻的前后两个元素;

i_list:VFS中使用四个链表来管理不同状态的inode结点。inode_unused将当前未使用的inode链接起来,inode_in_use将当前正在被使用的inode链接起来,超级块中的s_dirty将所有脏inode链接起来,i_hash将所有hash值相同的inode链接起来。i_list中包含prev和next两个指针,分别指向与当前inode处于同一个状态链表的前后两个元素。

VFS

 

关于此图的说明:

1. 图中平行结构之间的箭头表示这两者之间仍有有若干个类似结点相连接;

PS:在linux 3.14.8 中,从task_struct到struct file有一个过程:

task_struct -> struct files_struct -> struct file -> struct path -> struct dentry

2. 图中阴影部分所示的进程并不是以实际的关系来表示的;

3. 图中彩色线条示意的场景:三个进程分别打开同一个文件。进程1和进程2打开同一路径的文件,因此两者的打开文件对应同一个目录项;而进程3打开的是一个硬链接文件,因此对应的目录项与前两者不同;

4.索引结点中i_sb_list链表是链接一个文件系统中所有inode的链表,因此相邻的inode之间均会由此链表链接;而i_list链接的是处于同一个状态的所有inode。所以,相邻inode之间并不一定链接在一起。

 

具有健壮性的文件IO函数

October 13th, 2014

在理想环境下,我们使用linux下的read()write()函数可以依照我们想读的字节数读到buffer中。并没有考虑函数返回值的问题。

以read为例

static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
    int cnt;

    while (rp->rio_cnt <= 0) {  /* refill if buf is empty */
	rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, 
			   sizeof(rp->rio_buf));
	if (rp->rio_cnt < 0) {
	    if (errno != EINTR) /* interrupted by sig handler return */
		return -1;
	}
	else if (rp->rio_cnt == 0)  /* EOF */
	    return 0;
	else 
	    rp->rio_bufptr = rp->rio_buf; /* reset buffer ptr */
    }

    /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */
    cnt = n;          
    if (rp->rio_cnt < n)   
	cnt = rp->rio_cnt;
    memcpy(usrbuf, rp->rio_bufptr, cnt);
    rp->rio_bufptr += cnt;
    rp->rio_cnt -= cnt;
    return cnt;
}
ssize_t rio_readn(int fd, void *usrbuf, size_t n) 
{
 size_t nleft = n;
 ssize_t nread;
 char *bufp = usrbuf;

 while (nleft > 0) {
 if ((nread = read(fd, bufp, nleft)) < 0) {
 if (errno == EINTR) /* interrupted by sig handler return */
 nread = 0; /* and call read() again */
 else
 return -1; /* errno set by read() */ 
 } 
 else if (nread == 0)
 break; /* EOF */
 nleft -= nread;
 bufp += nread;
 }
 return (n - nleft); /* return >= 0 */
}

我们看到read返回值是有可能小于要求sizoof(buffer)的值,这种现象在kernel character device与network中非常普遍!

另外,read()应该也要处理用户发来的信号,如果遇到sigal信号,要使得返回值置0,并使得buffer指针的移动。

所以我们要注意,并进行比较。

比如在http://www.lizhaozhong.info/archives/1066中read也是实现了类似的思想。

维护一个len与count的关系,每次调用read函数都是确保len减去一个count大小的buffer,直到len<count,然后len赋值为count。

write函数也是类似,只不过len与count之间主要做加法。