Archive for October, 2014

C/S模型下Server 中fork()的健壮性

October 30th, 2014

C/S模型下Server 下编程非常依赖fork()子进程去处理具体的业务逻辑。 每当accept()接收到一个TCP连接时,主服务器进程就fork一个子服务器进程。子服务器进程调用相应的函数,通过client_fd(连接套接字)对客户端发来的网络请求进程处理;由于客户端的请求已被子服务进程处理,那么主服务器进程就什么也不做,通过sockfd(监听套接字)继续循环等待新的网络请求。

..........
while (1) {
	sin_size = sizeof(struct sockaddr_in);
	if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
		my_error("accept", errno, __LINE__);
		continue;
	}

	if ((pid = fork()) == 0) {
		close(sockfd);
		process_client_request(client_fd);
		close(client_fd);
		exit(0);
	} else if (pid > 0)
		close(client_fd);
	else
		my_error("fork", errno, __LINE__);
}

每个文件都有一个引用计数,该引用计数表示当前系统内的所有进程打开该文件描述符的个数。套接字是一种特殊的文件,当然也有引用计数。 当fork执行后,由于子进程复制了父进程的资源,所以子进程也拥有这两个套接字描述符,则此时sockfd和client_fd的引用计数都为2。只有当子进程处理完客户请求时,client_fd的引用计数才由于close函数而变为0。 但是这里存在一个严重的问题:如果客户端意外退出,就会导致server子进程成为僵尸进程。我们设想如果server非常繁忙,就会导致system出现大量的zombie进程!system会应该耗尽系统资源而宕机! 这是因为

当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止。

子进程退出时,内核将子进程置为僵尸状态,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。

进程表中代表子进程的数据项是不会立刻释放的,虽然不再活跃了,可子进程还停留在系统里,因为它的退出码还需要保存起来以备父进程中后续的wait调用使用。

所以我们要处理zombie进程! 两种方式:

  • 调用wait或者waitpid函数查询子进程退出状态,此方法父进程会被挂起。
  • 如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。

当子进程终止时会给父进程发送SIGCHLD信号,因此我们可以利用信号处理函数捕获这个信号并对僵死进程进行处理。我们知道在父进程中调用wait函数可以防止先于父进程终止的子进程编程僵死进程

void sig_zchild(int signo)
{
	pid_t pid;
	int stat;

	pid = wait(&stat);
        printf("child %d terminated\n", pid);
	return;
}

修改服务器程序,在accept函数调用之前调用signal函数:

	if(listen(sockfd, BACKLOG) == -1) {

		printf("listen error!\n");
		exit(1);
	}

	if (signal(SIGCHLD, sig_zchild) == SIG_ERR) {
		printf("signal error!\n");
		exit(1);
	}

	while (1) {

		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr,
&sin_size)) == -1) {

			printf("accept error!\n");
			continue;
		}
		…… ……
	}//while

但是这个程序仍存在一个问题:当多个子进程同时退出时,会导致父进程无法同时处理SIGCHILD信号,导致有部分子进程zombie。

void sig_zchild(int signo)
{
 pid_t pid;
 int stat;

 while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
 printf("child %d terminated\n", pid);

 return;
}

使用while可以等待SIGCHILD信号,直到处理完成! 信号在内核中也是存放在队列中!

 

 

参考:http://www.cnblogs.com/mickole/p/3187770.html

VFS Data Structure关系(3) – 跨文件系统的文件操作分析

October 28th, 2014

通过对VFS 以及文件系统的分析,我们现在来分析一下跨文件系统的文件传输

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

http://www.lizhaozhong.info/archives/1110

比如从一个floppy传输到harddisk(ext2——>ext4)

首先我们明确一个概念:一切皆文件!

在linux中,无论是普通的文件,还是特殊的目录、设备等,VFS都将它们同等看待成文件,通过统一的文件操作界面来对它们进行操作。操作文件时需先打开;打开文件时,VFS会知道该文件对应的文件系统格式;当VFS把控制权传给实际的文件系统时,实际的文件系统再做出具体区分,对不同的文件类型执行不同的操作。

原理:将ext2格式的磁盘上的一个文件a.txt拷贝到ext4格式的磁盘上,命名为b.txt。这包含两个过程,对a.txt进行读操作,对b.txt进行写操作。读写操作前,需要先打开文件。由前面的分析可知,打开文件时,VFS会知道该文件对应的文件系统格式,以后操作该文件时,VFS会调用其对应的实际文件系统的操作方法。所以,VFS调用vfat的读文件方法将a.txt的数据读入内存;在将a.txt在内存中的数据映射mmap()到b.txt对应的内存空间后,VFS调用ext4的写文件方法将b.txt写入磁盘;从而实现了最终的跨文件系统的复制操作。

关于mmap()使用

fs/open.c

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    struct open_flags op;
    int fd = build_open_flags(flags, mode, &op);
    struct filename *tmp;

    if (fd)
        return fd;

    tmp = getname(filename);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    fd = get_unused_fd_flags(flags);
    if (fd >= 0) {
        struct file *f = do_filp_open(dfd, tmp, &op);
        if (IS_ERR(f)) {
            put_unused_fd(fd);
            fd = PTR_ERR(f);
        } else {
            fsnotify_open(f);
            fd_install(fd, f);
        }
    }
    putname(tmp);
    return fd;
}

下面我们来分析下linux-3.14.8的内核(Not Finished!)

sys_open()
    |
    |------do_sys_open()
    |           |
    |           |------get_unused_fd_flags()
                |
                |------do_filp_open()
                            |
                            |----path_openat()
                            |       |
                            |       |----link_path_walk()

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