目次
準備(方法①:カーネルソースから準備)
カーネルソースの取得
takayuki@ubuntu:~$ sudo apt update
takayuki@ubuntu:~$ sudo apt install linux-source
/usr/src以下にカーネルソースのbz2ファイルができるので、適当なディレクトリに回答する。
takayuki@ubuntu:~/repos$ tar jxvf /usr/src/linux-source-5.4.0.tar.bz2
カーネルモジュールの前に、ビルドに必要な.configを作成。他にmake prepare, make scriptsを実行しておく。
takayuki@ubuntu:~/repos/linux-source-5.4.0$ sudo apt-get update && sudo apt install -y build-essential bc bison flex
libelf-dev libssl-dev libncurses5-dev
takayuki@ubuntu:~/repos/linux-source-5.4.0$ make oldconfig
takayuki@ubuntu:~/repos/linux-source-5.4.0$ sudo make prepare
takayuki@ubuntu:~/repos/linux-source-5.4.0$ sudo make scripts
カーネルモジュールのビルド
VBoxでつかっているe1000ドライバをコンパイルしてみて、ちゃんとコンパイルできるか確認する。
takayuki@ubuntu:~/repos/linux-source-5.4.0/drivers/net/ethernet/intel/e1000$ sudo make -C /home/takayuki/repos/linux-source-5.4.0 M=`pwd`
make: Entering directory '/home/takayuki/repos/linux-source-5.4.0'
(..)
Building modules, stage 2.
MODPOST 1 modules
CC [M] /home/takayuki/repos/linux-source-5.4.0/drivers/net/ethernet/intel/e1000/e1000.mod.o
LD [M] /home/takayuki/repos/linux-source-5.4.0/drivers/net/ethernet/intel/e1000/e1000.ko
make: Leaving directory '/home/takayuki/repos/linux-source-5.4.0'
準備(方法②linux-headerをインストール)
sudo apt-get update
sudo apt-get install linux-headers-$(uname -r) build-essential
Hello worldモジュール作成
コード
ChatGPTでコード生成。Makefileも作成しておく。
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello world\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your name");
MODULE_DESCRIPTION("Hello world module");
- insmodで呼び出されるhello_initとrmmodで呼び出されるhello_exitを、それぞれmodule_initとmodule_exitでエントリポイントとして登録
- エントリポイントは他にopen, ioctl, closeが登録できる
- ドライバ内でプロセスからのioctlで始まるプロセスコンテキストと、割込みからはじまる割込みコンテキストがある。プロセスコンテキストは処理時間が長くてもいいが、割込みコンテキストは処理時間を短く、スリープもしないようにしないといけない。メモリコピーもできない。
obj-m := hello.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
ビルド
takayuki@ubuntu:~/repos/hello$ make -C /home/takayuki/repos/linux-source-5.4.0 M=$(pwd)
make: Entering directory '/home/takayuki/repos/linux-source-5.4.0'
AR /home/takayuki/repos/hello/built-in.a
CC [M] /home/takayuki/repos/hello/hello.o
WARNING: Symbol version dump ./Module.symvers
is missing; modules will have no dependencies and modversions.
Building modules, stage 2.
MODPOST 1 modules
CC [M] /home/takayuki/repos/hello/hello.mod.o
LD [M] /home/takayuki/repos/hello/hello.ko
make: Leaving directory '/home/takayuki/repos/linux-source-5.4.0'
実行
takayuki@ubuntu:~/repos/hello$ sudo insmod ./hello.ko
takayuki@ubuntu:~/repos/hello$ dmesg|tail -1
[ 7376.257904] Hello world
takayuki@ubuntu:~/repos/hello$ sudo rmmod ./hello.ko
ioctlでソケット操作
Ubuntu Manpage: socket – Linux のソケットインターフェース
ソケットFDを指定して、ソケットのオプションを設定、取得できる。 カーネル内実装にはSIOCGSTAMPやSIOCSPGRPがある。 error = ioctl(ip_socket, ioctl_type, &value_result); ソケットのioctlはsocket.cで、socketファイルにstruct file_operationsにsock_ioctlが設定されている。 ファイルに設定できるstruct file_operationsのため、自前のioctlをソケットに追加することはできない見込み。
ioctlでソケットオプションを取得するモジュール作成
ソケットファイルディスクリプタにioctlを設定するかわりに、ioctl用のデバイスドライバを作成し、自前のioctl関数を登録する。
処理対象のソケットは引数で指定する。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <net/sock.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#include "sock_ioctl.h"
static int sock_ioctl_devs = 2;
static int sock_ioctl_major = 0;
module_param(sock_ioctl_major, uint, 0);
static struct cdev sock_ioctl_cdev;
static struct class *sock_ioctl_class = NULL;
int minor;
#define DRIVER_NAME "sock_ioctl"
#define MINOR_BASE 0
#define MINOR_NUM 2
static long sock_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct sock_values sock_values;
struct socket *sock;
sockptr_t optval;
int val = 0;
int intval = 0;
int err = 0;
if (copy_from_user(&sock_values, (void __user *)arg, sizeof(struct sock_values))) {
printk(KERN_ERR "Can't copy arg from user land\n");
return -EFAULT;
}
sock = sockfd_lookup(sock_values.sockfd, &err);
if (!sock)return err;
switch (cmd) {
case SOCK_SET_CMD:
val = sock_values.val;
printk(KERN_ERR "val=%d\n",val);
optval = KERNEL_SOCKPTR(&val);
if(sock_setsockopt(sock, 0, sock_values.optname,
optval, sizeof(val)) < 0){
printk(KERN_ERR "Failed to set sockopt\n");
}
break;
case SOCK_GET_CMD:
//TODO: 実装する。
break;
default:
printk(KERN_ERR "Invalid ioctl command\n");
return -ENOTTY;
}
return 0;
}
static int sock_ioctl_open(struct inode* inode, struct file* file){
printk("%s: major %d minor %d (pid %d)", __func__, imajor(inode), iminor(inode), current->pid);
inode->i_private = inode;
file->private_data = file;
printk(" i_private=%p private_data=%p\n", inode->i_private, file->private_data);
return 0;
}
static int sock_ioctl_close(struct inode* inode, struct file* file){
printk("%s: major %d minor %d (pid %d)", __func__, imajor(inode), iminor(inode), current->pid);
printk(" i_private=%p private_data=%p\n", inode->i_private, file->private_data);
return 0;
}
static struct file_operations sock_ioctl_fops = {
.open = sock_ioctl_open,
.release = sock_ioctl_close,
.unlocked_ioctl = sock_ioctl,
};
static int __init sock_ioctl_init(void)
{
dev_t dev = MKDEV(sock_ioctl_major, 0);
int major;
//alloc_chrdev_regionで空いているメジャー番号を取得
if(alloc_chrdev_region(&dev, 0, sock_ioctl_devs, DRIVER_NAME)){
pr_err("Cannot allocate major number\n");
return -1;
}
sock_ioctl_major = major = MAJOR(dev);
//cdev_initでcdevを初期化
cdev_init(&sock_ioctl_cdev, &sock_ioctl_fops);
sock_ioctl_cdev.owner = THIS_MODULE;
//cdevをカーネルに登録。/proc/devices以下にに現れるようになる。mknodでデバイスファイルができる。
if(cdev_add(&sock_ioctl_cdev, MKDEV(sock_ioctl_major, 0), sock_ioctl_devs)){
pr_err("Cannot add the device to the system\n");
cdev_del(&sock_ioctl_cdev);
unregister_chrdev_region(dev, sock_ioctl_devs);
return -1;
}
//デバイスのクラス登録をする(/sys/class/sock_ioctl/ を作る)
sock_ioctl_class = class_create(THIS_MODULE, "sock_ioctl");
if (IS_ERR(sock_ioctl_class)) {
printk(KERN_ERR "class_create\n");
cdev_del(&sock_ioctl_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
//sys/class/sock_ioctl/sock_ioctl* を作る
for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(sock_ioctl_class, NULL, MKDEV(sock_ioctl_major, minor), NULL, "sock_ioctl%d", minor);
}
printk(KERN_ALERT "%s driver(major %d) installed.\n", DRIVER_NAME, major);
return 0;
};
static void __exit sock_ioctl_exit(void)
{
dev_t dev = MKDEV(sock_ioctl_major, 0);
// /sys/class/sock_ioctl/sock_ioctl* を削除する */
for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(sock_ioctl_class, MKDEV(sock_ioctl_major, minor));
}
// このデバイスのクラス登録を取り除く(/sys/class/sock_ioctl/を削除する)
class_destroy(sock_ioctl_class);
// このデバイスドライバ(cdev)をカーネルから取り除く
cdev_del(&sock_ioctl_cdev);
unregister_chrdev_region(dev, sock_ioctl_devs);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}
module_init(sock_ioctl_init);
module_exit(sock_ioctl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Takayuki Fukatani");
MODULE_DESCRIPTION("Sample module for ioctl");
#ifndef SOCK_DRIVER_H_
#define SOCK_DRIVER_H_
#include <linux/ioctl.h>
#include <linux/socket.h>
struct sock_values {
int sockfd;
int optname;
int val;
};
/*** ioctl用コマンド(request, 第2引数)の定義 ***/
#define SOCK_SET_IOC_TYPE 'M'
#define SOCK_SET_CMD _IOW(SOCK_SET_IOC_TYPE, 1, struct sock_values)
/* デバドラから値を取得するコマンド。パラメータはsock_values型 */
#define SOCK_GET_CMD _IOR(SOCK_IOC_TYPE, 2, struct sock_values)
#endif /* SOCK_DRIVER_H_ */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "sock_ioctl.h"
#define PORT 22
int main()
{
int fd = 0, sock = 0;
struct sock_values val;
struct sockaddr_in server_addr;
// ソケットの作成
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
// サーバの情報を設定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// サーバに接続
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
exit(1);
}
printf("Connection established\n");
val.sockfd = sock;
val.optname = SO_REUSEADDR;
val.val = 1;
if ((fd = open("/dev/sock_ioctl0", O_RDWR)) < 0) perror("open");
if (ioctl(fd, SOCK_SET_CMD, &val) < 0) perror("ioctl_set");
// ソケットオプションを取得
int get_optval;
socklen_t optlen = sizeof(get_optval);
if (getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &get_optval, &optlen) < 0) {
perror("getsockopt");
exit(1);
}
printf("SO_REUSEADDR: %d\n", get_optval);
if (close(sock) != 0) perror("close");
return 0;
}