linuxにシステムコール追加

久々にやってみようと思って、2.6.33でシステムコールの追加をしてみました。
やってみたら新しい発見があったり面白いですね。

copy_buffer.cというファイルは新規に作ったのと、それようにMakefileを弄ってます。
他のファイルはシステムコールを追加するために弄っているので、もし新規にシステムコールを追加するならここは変更する必要があります。
ファイルを新規に作らずに、既存のファイル内に関数を追加するなら、Makefileを弄る必要はありません。
テスト環境は仮想環境(kvm)でホスト・ゲストともにアーキテクチャx86_64です。

システムコールの追加方法は、lwnの記事で最近のカーネルに対してシステムコールの追加に関する記事があったので、
それを参考に弄るファイルとかを調べました。
多分、最近のカーネルと昔のカーネルでは弄るファイルが多少変わってます。
最近はinclude/asm/unistd.hはなくてinclude/asm-generic/unistd.hだったり変化があります。

カーネルのdiffstatはこんな感じです。

masami@lisa:~$ diffstat  copy.diff 
 arch/x86/ia32/ia32entry.S          |    1 +
 arch/x86/include/asm/unistd_32.h   |    3 ++-
 arch/x86/include/asm/unistd_64.h   |    3 +++
 arch/x86/kernel/syscall_table_32.S |    2 ++
 include/asm-generic/unistd.h       |    5 ++++-
 include/linux/syscalls.h           |    3 +++
 kernel/Makefile                    |    3 ++-
 kernel/copy_buffer.c               |   26 ++++++++++++++++++++++++++
 8 files changed, 43 insertions(+), 3 deletions(-)

実際のdiffはこちらです。

diff --git a/arch/x86/ia32/ia32entry.S b/arch/x86/ia32/ia32entry.S
index 53147ad..d6f8e65 100644
--- a/arch/x86/ia32/ia32entry.S
+++ b/arch/x86/ia32/ia32entry.S
@@ -842,4 +842,5 @@ ia32_sys_call_table:
 	.quad compat_sys_rt_tgsigqueueinfo	/* 335 */
 	.quad sys_perf_event_open
 	.quad compat_sys_recvmmsg
+	.quad sys_copy_buffer
 ia32_syscall_end:
diff --git a/arch/x86/include/asm/unistd_32.h b/arch/x86/include/asm/unistd_32.h
index 3baf379..2fc10f0 100644
--- a/arch/x86/include/asm/unistd_32.h
+++ b/arch/x86/include/asm/unistd_32.h
@@ -343,10 +343,11 @@
 #define __NR_rt_tgsigqueueinfo	335
 #define __NR_perf_event_open	336
 #define __NR_recvmmsg		337
+#define __NR_copy_buffer	338
 
 #ifdef __KERNEL__
 
-#define NR_syscalls 338
+#define NR_syscalls 339
 
 #define __ARCH_WANT_IPC_PARSE_VERSION
 #define __ARCH_WANT_OLD_READDIR
diff --git a/arch/x86/include/asm/unistd_64.h b/arch/x86/include/asm/unistd_64.h
index 4843f7b..447c3dd 100644
--- a/arch/x86/include/asm/unistd_64.h
+++ b/arch/x86/include/asm/unistd_64.h
@@ -664,6 +664,9 @@ __SYSCALL(__NR_perf_event_open, sys_perf_event_open)
 #define __NR_recvmmsg				299
 __SYSCALL(__NR_recvmmsg, sys_recvmmsg)
 
+#define __NR_copy_buffer				300
+__SYSCALL(__NR_copy_buffer, sys_copy_buffer)
+
 #ifndef __NO_STUBS
 #define __ARCH_WANT_OLD_READDIR
 #define __ARCH_WANT_OLD_STAT
diff --git a/arch/x86/kernel/syscall_table_32.S b/arch/x86/kernel/syscall_table_32.S
index 15228b5..bbe07f9 100644
--- a/arch/x86/kernel/syscall_table_32.S
+++ b/arch/x86/kernel/syscall_table_32.S
@@ -337,3 +337,5 @@ ENTRY(sys_call_table)
 	.long sys_rt_tgsigqueueinfo	/* 335 */
 	.long sys_perf_event_open
 	.long sys_recvmmsg
+	.long sys_copy_buffer
+	
\ No newline at end of file
diff --git a/include/asm-generic/unistd.h b/include/asm-generic/unistd.h
index 6a0b30f..da9efe6 100644
--- a/include/asm-generic/unistd.h
+++ b/include/asm-generic/unistd.h
@@ -627,8 +627,11 @@ __SYSCALL(__NR_accept4, sys_accept4)
 #define __NR_recvmmsg 243
 __SYSCALL(__NR_recvmmsg, sys_recvmmsg)
 
+#define __NR_copy_buffer 244
+__SYSCALL(__NR_copy_buffer, sys_copy_buffer)
+
 #undef __NR_syscalls
-#define __NR_syscalls 244
+#define __NR_syscalls 245
 
 /*
  * All syscalls below here should go away really,
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 207466a..3455589 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -836,4 +836,7 @@ asmlinkage long sys_perf_event_open(
 asmlinkage long sys_mmap_pgoff(unsigned long addr, unsigned long len,
 			unsigned long prot, unsigned long flags,
 			unsigned long fd, unsigned long pgoff);
+
+asmlinkage long sys_copy_buffer(char *to, char *from, unsigned long len);
+
 #endif
diff --git a/kernel/Makefile b/kernel/Makefile
index 864ff75..b69d76e 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -10,7 +10,8 @@ obj-y     = sched.o fork.o exec_domain.o panic.o printk.o \
 	    kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \
 	    hrtimer.o rwsem.o nsproxy.o srcu.o semaphore.o \
 	    notifier.o ksysfs.o pm_qos_params.o sched_clock.o cred.o \
-	    async.o
+	    async.o copy_buffer.o
+
 obj-y += groups.o
 
 ifdef CONFIG_FUNCTION_TRACER
diff --git a/kernel/copy_buffer.c b/kernel/copy_buffer.c
new file mode 100644
index 0000000..01586e4
--- /dev/null
+++ b/kernel/copy_buffer.c
@@ -0,0 +1,26 @@
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/unistd.h>
+#include <linux/syscalls.h>
+#include <asm/uaccess.h>
+
+SYSCALL_DEFINE3(copy_buffer, char *, to, char *, from, unsigned long, len)
+{
+	char buf[256] = { 0 };
+
+	printk(KERN_INFO "This is %s()\n", __FUNCTION__);
+
+	if (!from)
+		return -EINVAL;
+
+	if (len > 256)
+		return -EINVAL;
+
+	if (copy_from_user(buf, from, len))
+		return -EFAULT;
+
+	if (copy_to_user(to, buf, len))
+		return -EFAULT;
+
+	return len;
+}

copy_buffer.c以外のファイルに付いては、システムコール番号の追加とか、システムコールの総数に1足してあげる程度です。
今回始めて知ったのは、下の関数の定義方法でした。

SYSCALL_DEFINE3(copy_buffer, char *, to, char *, from, unsigned long, len)

SYSCALL_DEFINEはマクロで、定義はinclude/linux/syscalls.hにあります。
こいつはSYSCALL_DEFINE0からSYSCALL_DEFINE6まであって、数字は何個引数を取るかを意味します。
今回作ったcopy_buffer()は3つの引数を取るので、SYSCALL_DEFINE3を使ってます。
書式は、以下の用になってて、関数名の後に、型と引数の名前が続きます。

SYSCALL_DEFINE3(関数名, 型, 引数名, 型, 引数名, 型, 引数名)

システムコールを実装している関数だからと言って、これを必ず使ってるわけでも無いみたいです。sys_fork()とか・・

テスト用のプログラムはこれです。

masami@lisa:~$ cat copy_buffer_test.c
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#define __NR_copy_buffer	300

static inline long execute_syscall(char *to, char *from, unsigned long len)
{
	return syscall(__NR_copy_buffer, to, from, len);
}

static void test_003(void)
{
	long ret = 0;
	char from[256] = { 0 };

	strcpy(from, "Hello, World");

	ret = execute_syscall(NULL, from, sizeof(from));
	printf("%s\n", strerror(errno));

}

static void test_002(void)
{
	long ret = 0;
	char to[256] = { 0 };

	ret = execute_syscall(to, NULL, sizeof(to));
	printf("%s\n", strerror(errno));
}

static void test_001(void)
{
	long ret = 0;
	char to[256] = { 0 };
	char from[256] = { 0 };

	strcpy(from, "Hello, World");
	
	ret = execute_syscall(to, from, sizeof(to));
	if (ret < 0)
		printf("%s\n", strerror(errno));
	else
		printf("the to buffer is [%s] and its length is %d\n", to, ret);

}

int main(int argc, char **argv)
{
	test_001();
	test_002();
	test_003();

	return 0;
}

_syscall[0-6]()は最近のカーネルでは使えないので、syscall(2)を使います。
これの使い方は簡単で、1番目の引数はシステムコールの番号、後は、実行したいシステムコールの引数を渡します。

syscall(__NR_copy_buffer, to, from, sizeof(from) - 1);

これを実行するとこんな感じです。

masami@lisa:~$ gcc copy_buffer_test.c
masami@lisa:~$ ./a.out
the to buffer is [Hello, World] and its length is 256
Invalid argument
Bad address