Archive for the ‘Kernel内核编程’ category

tools/vm/page-types.c 解析使用

December 9th, 2014

page-types.c这个代码位于kernel源码下的tool/vm/page-type.c

我们可以通过使用makefile编译这个程序,通过这个程序我们可以查找每个特定进程的page frame number  记住这个pfn只是一个index而已,我们如果想取得真实的物理地址需要使用pfn*page_size,当然这个是page frame 的物理起始地址。

这个程序是位于用户态,通过查找/proc/pid/pagemap 和 /proc/pid/maps来找到pfn的。

这个程序的功能比较强大,我们使用的仅仅是指定一个$./page-type -p pid

首先mian()中先要解析参数,然后通过parse_number()来获取用户输入的pid,然后我们知道当指定了一个pid,也就意味着我们打开一个正在运行的pid的maps

[root@localhost 13352]# ls
attr       clear_refs       cpuset   fd       limits    mountinfo   ns         oom_score_adj  root       stack   syscall
autogroup  cmdline          cwd      fdinfo   loginuid  mounts      numa_maps  pagemap        sched      stat    task
auxv       comm             environ  gid_map  maps      mountstats  oom_adj    personality    sessionid  statm   uid_map
cgroup     coredump_filter  exe      io       mem       net         oom_score  projid_map     smaps      status  wchan
00400000-00401000 r-xp 00000000 fd:02 4330673                            /home/lzz/mca-ras/src/tools/simple_process/simple_process
00600000-00601000 r--p 00000000 fd:02 4330673                            /home/lzz/mca-ras/src/tools/simple_process/simple_process
00601000-00602000 rw-p 00001000 fd:02 4330673                            /home/lzz/mca-ras/src/tools/simple_process/simple_process
019b8000-019d9000 rw-p 00000000 00:00 0                                  [heap]
33c1a00000-33c1a20000 r-xp 00000000 fd:01 2622088                        /usr/lib64/ld-2.18.so
33c1c1f000-33c1c20000 r--p 0001f000 fd:01 2622088                        /usr/lib64/ld-2.18.so
33c1c20000-33c1c21000 rw-p 00020000 fd:01 2622088                        /usr/lib64/ld-2.18.so
33c1c21000-33c1c22000 rw-p 00000000 00:00 0
33c1e00000-33c1fb4000 r-xp 00000000 fd:01 2622743                        /usr/lib64/libc-2.18.so
33c1fb4000-33c21b3000 ---p 001b4000 fd:01 2622743                        /usr/lib64/libc-2.18.so
33c21b3000-33c21b7000 r--p 001b3000 fd:01 2622743                        /usr/lib64/libc-2.18.so
33c21b7000-33c21b9000 rw-p 001b7000 fd:01 2622743                        /usr/lib64/libc-2.18.so
33c21b9000-33c21be000 rw-p 00000000 00:00 0
7f3fe081a000-7f3fe081d000 rw-p 00000000 00:00 0
7f3fe083c000-7f3fe083e000 rw-p 00000000 00:00 0
7fff87571000-7fff87592000 rw-p 00000000 00:00 0                          [stack]
7fff875fe000-7fff87600000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

我们可以看到这个进程每个段,包括BSS CODE HEAP STACK等。然后哦page-types.c中的parse_pid读取这个表里面的信息。
关于这个表每个列的含义看这个http://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps

我们主要关注头两行,vm_start vm_end。这时我们要获取虚拟地址的page number:

pg_start[nr_vmas] = vm_start / page_size;
pg_end[nr_vmas] = vm_end / page_size;

至于这个page_size可以通过page_size = getpagesize();获取。这里我们要注意unsigned long的取值范围是0-2^64-1,-1也就是2^64-1

然后我们进入walk_task()函数,因为process可能会存在匿名映射,所以我们需要使用filebacked_path[i]判断文件地址。

进入walk_vma()后。
这时我们需要读取/proc/pid/pagemap,这个我们用常规打不开,所以我们只好通过vm index读取的方式获得pages和pfn的相关信息。

详细我们查看

http://stackoverflow.com/questions/17021214/decode-proc-pid-pagemap-entry
https://www.kernel.org/doc/Documentation/vm/pagemap.txt

关注

* Bits 0-54 page frame number (PFN) if present
* Bit 63 page present
下面就是通过编程的方式获取这个的0-54位。总的来所就是使用位操作:

#define PFN_MASK (((1LL)<<55)-1)  //意味着0-54位都为1
*phy=(buf&PFN_MASK)*page_size+vir%page_size

核心函数:

        for (i = 0; i < pages; i++) {
//          printf("%lx\n",buf[i]);
            pfn = pagemap_pfn(buf[i]);
            if (pfn) {
                printf("%lx", pfn);
                printf("\t0x%lx",pfn*page_size);
                walk_pfn(index + i, pfn, 1, buf[i]);
            }
        }

我们可以看到pfn只是一个index而已,真正的物理地址需要pfn*page_size的方式,后面的各种属性是通过读取/proc/kpageflags获得。结果如下:

/home/lzz/mca-ras/src/tools/simple_process/simple_process
93a12	0x93a12000	referenced,uptodate,lru,active,mmap,private

/home/lzz/mca-ras/src/tools/simple_process/simple_process
54d07	0x54d07000	uptodate,lru,active,mmap,anonymous,swapbacked

/home/lzz/mca-ras/src/tools/simple_process/simple_process
9aa43	0x9aa43000	uptodate,lru,active,mmap,anonymous,swapbacked

[heap]
91f74	0x91f74000	uptodate,lru,active,mmap,anonymous,swapbacked
60fb3	0x60fb3000	uptodate,lru,active,mmap,anonymous,swapbacked
...............

 

我们看到物理地址只是在后面添加了3个0,其实也就是12位,正好是一个4k page的页内offset。

Linux中的虚拟地址转换物理地址实现

November 26th, 2014

Linux使用一种4级页表机制,即页全局目录(Page Global Directory)、页上级目录(Page Upper Directory)、页中间目录(Page Middle Directory)和页表(Page Table)。在这种分页机制下.

一个完整的线性地址被分为五部分:页全局目录、页上级目录、页中间目录、页表和偏移量,但是对于每个部分所占的位数则是不定的,这跟系统所在的体系架构有关。

Linux分别采用pgd_t、pud_t 、pmd_t和pte_t四种数据结构来表示页全局目录项、页上级目录项、页中间目录项和页表项。这四种数据结构本质上都是无符号长整型,Linux为了更严格数据类型检查,将无符号长整型分别封装成四种不同的页表项。

static void get_pgtable_macro(void)
{
	printk("PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET);
	printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);
	printk("PUD_SHIFT = %d\n", PUD_SHIFT);
	printk("PMD_SHIFT = %d\n", PMD_SHIFT);
	printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);

	printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);
	printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);
	printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);
	printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);

	printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);
}

static unsigned long vaddr2paddr(unsigned long vaddr)
{
	pgd_t *pgd;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;
	unsigned long paddr = 0;
        unsigned long page_addr = 0;
	unsigned long page_offset = 0;

	pgd = pgd_offset(current->mm, vaddr);
	printk("pgd_val = 0x%lx\n", pgd_val(*pgd));
	printk("pgd_index = %lu\n", pgd_index(vaddr));
	if (pgd_none(*pgd)) {
		printk("not mapped in pgd\n");
		return -1;
	}

	pud = pud_offset(pgd, vaddr);
	printk("pud_val = 0x%lx\n", pud_val(*pud));
	if (pud_none(*pud)) {
		printk("not mapped in pud\n");
		return -1;
	}

	pmd = pmd_offset(pud, vaddr);
	printk("pmd_val = 0x%lx\n", pmd_val(*pmd));
	printk("pmd_index = %lu\n", pmd_index(vaddr));
	if (pmd_none(*pmd)) {
		printk("not mapped in pmd\n");
		return -1;
	}

	pte = pte_offset_kernel(pmd, vaddr);
	printk("pte_val = 0x%lx\n", pte_val(*pte));
	printk("pte_index = %lu\n", pte_index(vaddr));
	if (pte_none(*pte)) {
		printk("not mapped in pte\n");
		return -1;
	}

	//页框物理地址机制 | 偏移量
	page_addr = pte_val(*pte) & PAGE_MASK;
	page_offset = vaddr & ~PAGE_MASK;
	paddr = page_addr | page_offset;
	printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
        printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);

	return paddr;
}

通过代码我们发现线性地址寻值过程:pgd_t -> pud_t ->pmd_t ->  pte_t  

具体Dmesg打印信息:

dmesg.log

目前在X86_64环境下,虽然支持64位,但是系统实际上目前远远使用不了如此大的内存,真正使用的只有48位,即

0xffff000000000000

 

然后使用上面的宏函数进行移位操作,拼接出下一级的页表物理基地址。然后再和后面的pud,pmd,pte部分地址相加,得到下一级的物理基地址。直到最后拿到页框的物理地址与pte或,找到真实的物理地址。

参考:

arch/x86/include/asm/pgtable_64_types.h

 

打印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

 

 

内核锁的使用

October 6th, 2014

内核锁有三种形态:

  • 原子操作
  • spinlock
  • semaphore

1.原子结构

typedef struct {
    int counter;
} atomic_t;

不能直接赋值,需要使用函数比如ATOMIC_INIT()等初始化等函数(arch/alpha/include/asm/atomic.h)
初始化宏的源码很明显的说明了如何初始化一个原子变量。我们在定义一个原子变量时可以这样的使用它:

atomic_t v=ATOMIC_INIT(0);

atomic_set函数可以原子的设置一个变量的值。

2.获取原子变量的值

23static inline int atomic_read(const atomic_t *v)
24{
25        return (*(volatile int *)&(v)->counter);
26}

返回原子型变量v中的counter值。关键字volatile保证&(v->counter)的值固定不变,以确保这个函数每次读入的都是原始地址中的值。

3.原子变量的加与减

47static inline void atomic_add(int i, atomic_t *v)
48{
49        asm volatile(LOCK_PREFIX "addl %1,%0"
50                     : "+m" (v->counter)
51                     : "ir" (i));
52}

61static inline void atomic_sub(int i, atomic_t *v)
62{
63        asm volatile(LOCK_PREFIX "subl %1,%0"
64                     : "+m" (v->counter)
65                     : "ir" (i));
66}

加减操作中使用了内联汇编语句。linux中的汇编语句都采用AT&T指令格式。

 

2.自旋锁spinlock(include/linux/spinlock_types.h)

typedef struct spinlock {
    union {
        struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
        struct {
            u8 __padding[LOCK_PADSIZE];
            struct lockdep_map dep_map;
        };
#endif
    };
} spinlock_t;

自旋锁的使用:

1.定义初始化自旋锁

使用下面的语句就可以先定一个自旋锁变量,再对其进行初始化:

spinlock_t lock;
spin_lock_init(&lock);

2.获得自旋锁

void spin_lock(spinlock_t*);
int spin_trylock(spinlock_t*);

使用spin_lock(&lock)这样的语句来获得自旋锁。如果一个线程可以获得自旋锁,它将马上返回;否则,它将自旋至自旋锁的保持者释放锁。

另外可以使用spin_trylock(&lock)来试图获得自旋锁。如果一个线程可以立即获得自旋锁,则返回真;否则,返回假,此时它并不自旋。

3.释放自旋锁

void spin_unlock(spinlock_t*);

使用spin_unlock(&lock)来释放一个已持有的自旋锁。注意这个函数必须和spin_lock或spin_trylock函数配套使用。

3.信号量semaphore(include/linux/semaphore.h)

struct semaphore {
    raw_spinlock_t      lock;
    unsigned int        count;
    struct list_head    wait_list;
};

#define __SEMAPHORE_INITIALIZER(name, n)                \
{                                   \
    .lock       = __RAW_SPIN_LOCK_UNLOCKED((name).lock),    \
    .count      = n,                        \
    .wait_list  = LIST_HEAD_INIT((name).wait_list),     \
}

#define DEFINE_SEMAPHORE(name)  \
    struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

static inline void sema_init(struct semaphore *sem, int val)
{
    static struct lock_class_key __key;
    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);

信号量(semaphore)是保护临界区的一种常用方法。它的功能与自旋锁相同,只能在得到信号量的进程才能执行临界区的代码。但是和自旋锁不同的是,一个进程不能获得信号量时,它会进入睡眠状态而不是自旋。

semaphore的使用

1.定义初始化信号量

使用下面的代码可以定义并初始化信号量sem:

struct semaphore sem;
sema_init(&sem,val);

其中val即为信号量的初始值。

除上面的方法,可以使用下面的两个宏定义并初始化信号量:

DECLARE_MUTEX(name);

DECLARE_MUTEX_LOCKED(name);

其中name为变量名。

2.获得信号量

down(&sem);

进程使用该函数时,如果信号量值此时为0,则该进车会进入睡眠状态,因此该函数不能用于中断上下文中。

down_interruptibale(&sem);

该函数与down函数功能类似,只不过使用down而睡眠的进程不能被信号所打断,而使用down_interruptibale的函数则可以被信号打断。

如果想要在中断上下文使用信号量,则可以使用下面的函数:

dwon_try(&sem);

使用该函数时,如果进程可以获得信号量,则返回0;否则返回非0值,不会导致睡眠。

3.释放信号量

up(&sem);

该函数会释放信号量,如果此时等待队列中有进程,则唤醒一个。

三种比较常用的互斥方式

需求 建议加锁的方法
低开销加锁 优先使用自旋锁
短期加锁 优先使用自旋锁
长期加锁 优先使用信号量
中断上下文加锁 使用自旋锁
持有锁时需要睡眠 使用信号量

自旋锁是忙等锁。当其他线程持有自旋锁时,如果另有线程想获得该锁,那么就只能循环的等待。这样忙的功能对CPU来说是极大的浪费。因此只有当由自旋锁保护的临界区执行时间很短时,使用自旋锁才比较合理。

spinlock.c
sharelist.c

在Linux3.X 添加 systemcall

September 25th, 2014

从代码级看syscall的问题

1.寄存器eax,传进来带的是中调用号,ret_from_sys_call()传出的是错误值。
2.由于内核函数是在内核中实现的,因此它必须符合内核编程的规则,比如函数名以sys_开始,函数定义时候需加asmlinkage标识符等。这个修饰符使得GCC编译器从堆栈中取该函数的参数而不是寄存器中。另外,系统调用函数的命名规则都是sys_XXX的形式。
3.不同的syscall,普通用户可用的命令和管理可用的命令分别被存放于/bin和/sbin目录下。

另外比较重要的一个特性就是在Linux 3.X系统中添加新的syscall 这相比于Linux 2.X添加syscall简单不少。Linux 2.X内核网上很多添加调用分析,就不赘述了。

1.定义系统调用服务例程。

#include &lt;linux/syscall.h&gt;

SYSCALL_DEFINE0(mycall)
{
        struct task_struct *p;
        printk(&quot;********************************************\n&quot;);
        printk(&quot;------------the output of mysyscall------------\n&quot;);
        printk(&quot;********************************************\n\n&quot;);
        printk(&quot;%-20s %-6s %-6s %-20s\n&quot;,&quot;Name&quot;,&quot;pid&quot;,&quot;state&quot;,&quot;ParentName&quot;);
        for(p = &amp;init_task; (p = next_task(p)) != &amp;init_task;)
                printk(&quot;%-20s %-6d %-6d %-20s\n&quot;,p-&gt;comm , p-&gt;pid, p-&gt;state, p-&gt;parent-&gt;comm);
        return 0;
}

以上定义宏SYSCALL_DEFINE0实际上会扩展为:

asmlinkage int sys_mycall()
{
               struct task_struct *p;
        printk(&quot;********************************************\n&quot;);
        printk(&quot;------------the output of mysyscall------------\n&quot;);
        printk(&quot;********************************************\n\n&quot;);
        printk(&quot;%-20s %-6s %-6s %-20s\n&quot;,&quot;Name&quot;,&quot;pid&quot;,&quot;state&quot;,&quot;ParentName&quot;);
        for(p = &amp;init_task; (p = next_task(p)) != &amp;init_task;)
                printk(&quot;%-20s %-6d %-6d %-20s\n&quot;,p-&gt;comm , p-&gt;pid, p-&gt;state, p-&gt;parent-&gt;comm);
        return 0;
}

内核在kernel/sys.c文件中定义了SYSCALL_DEFINE0~SYSCALL_DEFINE6等七个提供便利的宏。

2. 为系统调用服务例程在系统调用表中添加一个表项。

编译文件arch/x86/syscalls/syscall_64.tbl,在调用号小于512的部分追加一行:

2014-09-25 19:29:55的屏幕截图

注意:
① 上面的316是紧接着目前已定义系统调用315之后。
② 若为32系统添加系统调用号,在syscall_32.tbl中相应位置追加即可。系统调用在两个文件中的调用号没有必要一样。
③ 不需要像Linux 2.6的内核一样,在<asm/unistd.h>中添加类似于#define __NR_getjiffies 314之类的宏定义了,3.x的内核会自动根据系统调用表的定义生成。

3. 在内核头文件中,添加服务例程的声明。

在include/linux/syscalls.h文件的最后,#endif之前加入系统调用服务例程sys_mysyscall()的声明:

asmlinkage long sys_mycall(void);
#endif

剩下就是按照编译内核的方法进行编译好了  http://www.lizhaozhong.info/archives/496

4.系统调用表产生的过程

之所以Linux3.X与Linux2.X变化这么大。

主要是为了简化添加调用表的过程。

1. 系统调用表的产生过程。

内核开发者是在syscall_64.tbl中声明系统调用号与服务例程的对应关系,以及其ABI,但系统调用表的真正定义是在arch/x86/kernel/syscall_64.c中。

1)arch/x86/kernel/syscall_64.c, arch/x86/kernel/syscall_32.c文件中存放了实际的系统调用表定义,以64位系统为例,其中有如下内容:

#include &lt;asm/asm-offsets.h&gt;

#define __SYSCALL_COMMON(nr, sym, compat) __SYSCALL_64(nr, sym, compat)

#ifdef CONFIG_X86_X32_ABI
# define __SYSCALL_X32(nr, sym, compat) __SYSCALL_64(nr, sym, compat)
#else
# define __SYSCALL_X32(nr, sym, compat) /* nothing */
#endif

#define __SYSCALL_64(nr, sym, compat) extern asmlinkage void sym(void) ; // 注意,是分号结尾
#include &lt;asm/syscalls_64.h&gt; // 引入系统调用服务例程的声明
#undef __SYSCALL_64

#define __SYSCALL_64(nr, sym, compat) [nr] = sym, //注意,是逗号结尾

typedef void (*sys_call_ptr_t)(void);

extern void sys_ni_syscall(void);

const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { //系统调用表定义
        /*
         * Smells like a compiler bug -- it doesn't work
         * when the &amp; below is removed.
         */
        [0 ... __NR_syscall_max] = &amp;sys_ni_syscall,
&lt;strong&gt;#include &lt;asm/syscalls_64.h&gt; // 系统调用服务例程地址,对应arch/x86/include/generated/asm/syscalls_64.h文件&lt;/strong&gt;
};

2) arch/x86/syscalls目录中的syscall_64.tbl、syscall_32.tbl文件是系统调用表声明。syscalltbl.sh脚本负责生产syscalls_XX.h文件,由Makefile负责驱动
3) arch/x86/include/generated目录,其中存放根据arch/x86/syscalls目录生成的文件。主要有generated/asm/syscalls_64.h、generated/asm/syscalls_32.h文件,用于生成系统调用表数组。生成的syscalls_64.h内容部分如下:

截图 - 2014年09月25日 - 19时36分57秒

2. 系统调用号声明头文件的生成(#define0 __NR_xxx之类的信息)。

类似于系统调用表的产生,arch/x86/syscalls/syscallhdr.sh脚本负责generated/uapi/asm/unistd_32.h, generated/uapi/asm/unistd_64.h文件,unistd_XX.h文件又被间接include到asm/unistd.h中,后者最终被include用户空间使用的<sys/syscall.h>文件中(安装之后)。生成的generated/uapi/asm/unistd_64.h部分内容如下

... ...
#define __NR_sched_getattr 315
#define __NR_mycall 316

#endif /* _ASM_X86_UNISTD_64_H */

注意,这里的unistd.h文件与用户空间使用的文件没有任何关系,后者声明了系统调用包装函数,包括syscall函数等

下面我们来调用这个mycall()

 

截图 - 2014年09月25日 - 19时38分27秒

还有一种方式使用内嵌汇编的方式,具体文件在arch/x86/um/shared/sysdep/stub_64.h下面,模拟_syscall0宏调用。

#define __syscall_clobber "r11","rcx","memory"
#define __syscall "syscall"

static inline long stub_syscall0(long syscall)
{
    long ret;

    __asm__ volatile (__syscall
        : "=a" (ret)
        : "0" (syscall) : __syscall_clobber );

    return ret;
}

然后编译运行,打印在dmesg里面

kernel.dmesg

参考:

http://lwn.net/Articles/604287/

http://lwn.net/Articles/604515/