2017년 3월 1일 수요일

Efficient Linux Kernel 4.x Programming Techniques - Part 2

이번 posting에서는 지난 posting에 이어 linux kernel 4.x version에서 적용한 가능한 kernel programming 기법을 계속 소개해 보고자 한다[두번째 시간]. 예제 program의 시험 환경은 이전(UDOO Neo board, Buildroot 환경)과 동일하다.


<목차>
1. Character 드라이버 예제
2. Proc 예제
3. Sysfs 예제
4. Linked list 예제
5 KFIFO 사용 예제
6. MMAP 관련 예제
7. Netlink socket 예제


1. Character 드라이버 예제
이번 절에서는 역시 몸풀기 단계로써, 가장 쉽게 접근할 수 있는 character 드라이버 예제를 소개하기로 하겠다. 너무 쉽다고 무시하지 말기 바란다^^.

--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab3_seek.c>
/*
 * The code herein is: Copyright Jerry Cooperstein, 2012
 *
 * This Copyright is retained for the purpose of protecting free
 * redistribution of source.
 *
 *     URL:    http://www.coopj.com
 *     email:  coop@coopj.com
 *
 * The primary maintainer for this code is Jerry Cooperstein
 * The CONTRIBUTORS file (distributed with this
 * file) lists those known to have contributed to the source.
 *
 * This code is distributed under Version 2 of the GNU General Public
 * License, which you should have received with the source.
 *
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/cdev.h>

#define MYDEV_NAME "mycdrv"

static char *ramdisk;   /* 단순 memory buffer 임 */
static size_t ramdisk_size = (16 * PAGE_SIZE);    /* 16 * 4096 byte 크기 */
static dev_t first;
static unsigned int count = 1;
static int my_major = 700, my_minor = 0;   /* device node major/minor number */
static struct cdev *my_cdev;

static int mycdrv_open(struct inode *inode, struct file *file)   /* open 함수 */
{
  static int counter = 0;
pr_info(" attempting to open device: %s:\n", MYDEV_NAME);
pr_info(" MAJOR number = %d, MINOR number = %d\n",
imajor(inode), iminor(inode));
counter++;  /* 단순히 counter만 1 증가 시킴 */

pr_info(" successfully open  device: %s:\n\n", MYDEV_NAME);
pr_info("I have been opened  %d times since being loaded\n", counter);
pr_info("ref=%d\n", (int)module_refcount(THIS_MODULE));

  /* turn this on to inhibit seeking */
/* file->f_mode = file->f_mode & ~FMODE_LSEEK; */

return 0;
}

static int mycdrv_release(struct inode *inode, struct file *file)   /* release(close) 함수 */
{
  pr_info(" CLOSING device: %s:\n\n", MYDEV_NAME);
return 0;
}

static ssize_t
mycdrv_read(struct file *file, char __user * buf, size_t lbuf, loff_t * ppos)   /* read 함수 - lbuf 만큼 읽어 들임. *ppos의 값은 읽은 값 만큰 증가시킴 */
{
int nbytes, maxbytes, bytes_to_do;
maxbytes = ramdisk_size - *ppos;
bytes_to_do = maxbytes > lbuf ? lbuf : maxbytes;
if (bytes_to_do == 0)
pr_info("Reached end of the device on a read");
nbytes = bytes_to_do - copy_to_user(buf, ramdisk + *ppos, bytes_to_do);   /* ramdisk + *ppos부터 bytes_to_do 크기 만큼의 내용을 사용자 영역(buf)으로 복사함 */
*ppos += nbytes;   /* nbytes(대부분 lbuf 크기 값) 만큼 position을 이동시킴 */
pr_info("\n Leaving the   READ function, nbytes=%d, pos=%d\n", nbytes, (int)*ppos);
return nbytes;   /* 읽은 byte 수를 return 함 */
}

static ssize_t
mycdrv_write(struct file *file, const char __user * buf, size_t lbuf,
    loff_t * ppos)   /* write 함수 - lbuf 크기 만큼 write하고, *ppos의 값을 write한 만큼 증가 시킴 */
{
int nbytes, maxbytes, bytes_to_do;
maxbytes = ramdisk_size - *ppos;
bytes_to_do = maxbytes > lbuf ? lbuf : maxbytes;
if (bytes_to_do == 0)
pr_info("Reached end of the device on a write");
nbytes =
       bytes_to_do - copy_from_user(ramdisk + *ppos, buf, bytes_to_do);   /* 사용자 영역(buf)으로 부터 ramdisk + *ppos의 영역(kernel)에 bytes_to_do 크기 만큼 값을 복사함 */
*ppos += nbytes;  /* position 값을 nbytes 증가 시킴 */
pr_info("\n Leaving the   WRITE function, nbytes=%d, pos=%d\n", nbytes, (int)*ppos);
return nbytes;  /* write한 byte 수를 return 함 */
}

static loff_t mycdrv_lseek(struct file *file, loff_t offset, int orig)
{
loff_t testpos;
switch (orig) {
case SEEK_SET:
testpos = offset;   /* argument로 넘겨 받은 값을 testpos로 사용한다 */
break;
case SEEK_CUR:
testpos = file->f_pos + offset;   /* 현재 file offset에 argument로 넘겨 받은 값을 더한다 */
break;
case SEEK_END:
testpos = ramdisk_size + offset;  /* ramdisk_size에 argument로 넘겨 받은 값을 더한다 */
break;
default:
return -EINVAL;
}
testpos = testpos < ramdisk_size ? testpos : ramdisk_size;
testpos = testpos >= 0 ? testpos : 0;
file->f_pos = testpos;   /* file의 position을 위에서 설정한 값으로 변경함 */
pr_info("Seeking to pos=%ld\n", (long)testpos);
return testpos;  /* 새로운 위치 값을 return 한다 */
}

static const struct file_operations mycdrv_fops = {
.owner = THIS_MODULE,
.read = mycdrv_read,
.write = mycdrv_write,
.open = mycdrv_open,
.release = mycdrv_release,
.llseek = mycdrv_lseek
};

static int __init my_init(void)
{
first = MKDEV(my_major, my_minor);   /* major/minor number로 부터 만들어진 dev_t를 return 한다 */
if (register_chrdev_region(first, count, MYDEV_NAME) < 0) {
pr_err("failed to register character device region\n");
return -1;
}
if (!(my_cdev = cdev_alloc())) {
pr_err("cdev_alloc() failed\n");
unregister_chrdev_region(first, count);
return -1;
}
cdev_init(my_cdev, &mycdrv_fops);
ramdisk = kmalloc(ramdisk_size, GFP_KERNEL);

if (cdev_add(my_cdev, first, count) < 0) {
pr_err("cdev_add() failed\n");
cdev_del(my_cdev);
unregister_chrdev_region(first, count);
kfree(ramdisk);
return -1;
}

pr_info("\nSucceeded in registering character device %s\n", MYDEV_NAME);
return 0;
}

static void __exit my_exit(void)
{
if (my_cdev)
cdev_del(my_cdev);
unregister_chrdev_region(first, count);
kfree(ramdisk);
pr_info("\ndevice unregistered\n");
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Jerry Cooperstein");
MODULE_DESCRIPTION("LDD:2.0 s_04/lab3_seek.c");
MODULE_LICENSE("GPL v2");
--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab3_seek_test.c - application code>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
int length = 20, position = 0, fd, rc;
char *message, *nodename = "/dev/mycdrv";

if (argc > 1)
nodename = argv[1];

if (argc > 2)
position = atoi(argv[2]);

if (argc > 3)
length = atoi(argv[3]);

/* set up the message */
message = malloc(length);
memset(message, 'x', length);
message[length - 1] = '\0'; /* make sure it is null terminated */

/* open the device node */

fd = open(nodename, O_RDWR);    /* /dev/mycdrv device open */
printf(" I opened the device node, file descriptor = %d\n", fd);

/* seek to position */

rc = lseek(fd, position, SEEK_SET);
printf("return code from lseek = %d\n", rc);

/* write to the device node twice */

rc = write(fd, message, length);
printf("return code from write = %d\n", rc);
rc = write(fd, message, length);
printf("return code from write = %d\n", rc);

/* reset the message to null */

memset(message, 0, length);

/* seek to position */

rc = lseek(fd, position, SEEK_SET);
printf("return code from lseek = %d\n", rc);

/* read from the device node */

rc = read(fd, message, length);
printf("return code from read = %d\n", rc);
printf(" the message was: %s\n", message);

close(fd);
exit(0);
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to compile>
export KROOT=/home/chyi/IoT/UDOO/src/buildroot/output/build/linux-4.9
export ARCH=arm
export CROSS_COMPILE=arm-linux-
export CC=arm-linux-gcc

export CC=arm-linux-ld
export PATH=/home/chyi/IoT/UDOO/src/buildroot/output/host/usr/bin:$PATH
  => cross compile을 위한 환경 변수를 지정한다.

make
  => build하여 kernel module을 생성해 낸다.

sudo cp ./
lab3_seek.ko ~/IoT/UDOO/src/buildroot/output/images/rootfs/modules
sudo cp ./lab3_seek_test ~/IoT/UDOO/src/buildroot/output/images/rootfs/modules
  => build 결과물을 rootfs(NFS booting 용)의 적당한 위치로 복사한다.

<How to run it on the target board>

# insmod ./lab3_seek.ko 
[ 4926.225471]
[ 4926.225471] Succeeded in registering character device mycdrv

mknod /dev/mycdrv c 700 0
  => /dev/mycdrv device file(node)을 생성한다.

# ./lab3_seek_test 
[ 4933.749229]  attempting to open device: mycdrv:
[ 4933.753815]  MAJOR number = 700, MINOR number = 0
[ 4933.758702]  successfully open  device: mycdrv:
[ 4933.758702]
[ 4933.764743] I have been opened  1 times since being loaded
[ 4933.770311] ref=1
 I opened the device node, file d[ 4933.775786] Seeking to pos=0
escriptor = 3
[ 4933.781965]
[ 4933.781965]  Leaving the   WRITE function, nbytes=20, pos=20
return code from lseek = 0
[ 4933.790987]
[ 4933.790987]  Leaving the   WRITE function, nbytes=20, pos=40
return code from write = 20
[ 4933.799653] Seeking to pos=0
return code from write = 20
[ 4933.804974]
[ 4933.804974]  Leaving the   READ function, nbytes=20, pos=20
return code from lseek = 0
[ 4933.815898]  CLOSING device: mycdrv:
[ 4933.815898]
return code from read = 20
 the message was: xxxxxxxxxxxxxxxxxxx


2. Proc 예제
이번 절에서는 seq_file interface를 이용하여 /proc 아래에 파일을 생성하는 방법을 소개하고자 한다. 보통은 /proc 아래에 생성하려는 파일의 크기가 클 경우 이 방법을 사용하지만, 크기가 작다고 해서 사용하지 못할 이유는 없다. 실제로 kernel code의 많은 부분에서 이 방법을 사용하여 proc file system을 구성하고 있음을 눈여겨 볼 필요가 있다.

--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab5_seqfile.c>
#include <linux/module.h>
#include <linux/sched.h> /* Get "jiffies" from here */
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/seq_file.h>

static int items = 10;   /* 10번 반복하도록 하자 */
static int x_delay = 1;
static unsigned long future;

static char const my_proc[] = { "x_busy" };   /* /proc 아래에 생성되는 파일 이름 지정 */

/* Sequential file iterator                                              */

static void *x_busy_seq_start(struct seq_file *sf, loff_t * pos)
{
void *results;

if (*pos < items) {
future = jiffies + x_delay * HZ;
while (time_before(jiffies, future)) ;  /* 1초간의 delay(busy waiting)를 준다 */
results = (void *)&jiffies;
        pr_info("(%s) IF ... *pos : [%d]\n", __func__, *pos);
} else {
results = NULL;
        pr_info("(%s) ELSE ... *pos : [%d]\n", __func__, *pos);
}
return results;
}

static void *x_busy_seq_next(struct seq_file *sf, void *v, loff_t * pos)
{
void *results;

(*pos)++;
if (*pos < items) {
future = jiffies + x_delay * HZ;
while (time_before(jiffies, future)) ;   /* 1초간의 delay(busy waiting)를 준다 */
results = (void *)&jiffies;
        pr_info("(%s) IF ... *pos : [%d]\n", __func__, *pos);
} else {
results = NULL;
        pr_info("(%s) ELSE ... *pos : [%d]\n", __func__, *pos);
}
return results;
}

static void x_busy_seq_stop(struct seq_file *sf, void *v)
{
/* Nothing to do here */
}

static int x_busy_seq_show(struct seq_file *sf, void *v /* jiffies in disquise */
    )
{
volatile unsigned long *const jp = (volatile unsigned long *)v;
int results;

seq_printf(sf, "jiffies = %lu.\n", *jp);   /* 실제 이 내용이 stdout으로 출력되게 됨 */
results = 0;
return results;
}

static struct seq_operations proc_x_busy_seq_ops = {
.start = x_busy_seq_start,
.next = x_busy_seq_next,
.stop = x_busy_seq_stop,
.show = x_busy_seq_show,
};

static int proc_x_busy_open(struct inode *inode, struct file *file)
{
return seq_open(file, &proc_x_busy_seq_ops);
    /* seq_open() 함수 대신 single_open() 함수를 사용할 수도 있다. 이 경우, struct seq_operations의 모든 callback 함수를 구현할 필요 없이, show 함수만 구현해 주면 된다. */
    /* return single_open(file, x_busy_seq_show, NULL); */
}

static const struct file_operations proc_x_busy_operations = {
.open = proc_x_busy_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
    /* write 함수가 필요하다면 여기에 추가해 주면 된다 */
    /* .write = my_write_func */
};

static struct proc_dir_entry *x_proc_busy;

static int __init my_init(void)
{
int results;

results = -1;
do {
x_proc_busy = proc_create(my_proc, 0, NULL, &proc_x_busy_operations);   /* my_proc "x_busy" 파일이 /proc 아래에 만들어 짐 */
if (!x_proc_busy) {
break;
}
results = 0;
} while (0);
return results;
}

static void __exit my_exit(void)
{
if (x_proc_busy) {
remove_proc_entry(my_proc, NULL);  /* /proc에서 x_busy 파일을 제거함 */
}
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Tommy Reynolds");
MODULE_AUTHOR("Jerry Cooperstein");
MODULE_AUTHOR("Chunghan Yi");
MODULE_DESCRIPTION("LDD:2.0 s_14/lab5_seqfile.c");
MODULE_LICENSE("GPL v2");
module_param(items, int, S_IRUGO);
MODULE_PARM_DESC(items, "How many items to simulate");
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to run it on the target board>
# insmod ./lab5_seqfile.ko
# [ 3042.768345] (x_busy_seq_start) IF ... *pos : [0]
[ 3043.768332] (x_busy_seq_next) IF ... *pos : [1]
[ 3044.768329] (x_busy_seq_next) IF ... *pos : [2]
[ 3045.768327] (x_busy_seq_next) IF ... *pos : [3]
[ 3046.768327] (x_busy_seq_next) IF ... *pos : [4]
[ 3047.768326] (x_busy_seq_next) IF ... *pos : [5]
[ 3048.768325] (x_busy_seq_next) IF ... *pos : [6]
[ 3049.768325] (x_busy_seq_next) IF ... *pos : [7]
[ 3050.768324] (x_busy_seq_next) IF ... *pos : [8]
[ 3051.768327] (x_busy_seq_next) IF ... *pos : [9]
[ 3051.772920] (x_busy_seq_next) ELSE ... *pos : [10]
[ 3051.793498] (x_busy_seq_start) ELSE ... *pos : [10]
[ 3051.798959] (x_busy_seq_start) ELSE ... *pos : [10]
  => 위의 내용을 출력해 본 이유는 seq_start, seq_next, seq_stop 함수가 어떤 순서로 호출되는지를 확인해 보기 위함임.

# cd /proc
# cat x_busy 
jiffies = 274276.
jiffies = 274376.
jiffies = 274476.
jiffies = 274576.
jiffies = 274676.
jiffies = 274776.
jiffies = 274876.
jiffies = 274976.
jiffies = 275076.
jiffies = 275176.


3. Sysfs 예제 
이번 절에서는 kernel example source 중에서 kobject를 생성하는 예제 program(linux-4.9/samples/kobject)을 소개해 보고자 한다. 이미 2절에서 /proc 아래에 파일을 생성하는 방법을 다루어 보았는데, 현재 kernel의 추세는 proc 보다는 sysfs 아래에 파일을 위치 시키는 것을 권고하고 있다.

--------------------------------------------------------------------------------------------------------------------------------------------------------------
<kobject-example.c>
/*
 * Sample kobject implementation
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
 * Copyright (C) 2007 Novell Inc.
 *
 * Released under the GPL version 2 only.
 *
 */
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>

/*
 * This module shows how to create a simple subdirectory in sysfs called
 * /sys/kernel/kobject-example  In that directory, 3 files are created:
 * "foo", "baz", and "bar".  If an integer is written to these files, it can be
 * later read out of it.
 */

static int foo;
static int baz;
static int bar;

/*
 * The "foo" file where a static variable is read from and written to.
 */
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)   /* /sys/kernel/kobject_example/foo 파일을 읽을때 호출되는 함수 */
{
return sprintf(buf, "%d\n", foo);  /* integer foo의 값을 buf로 복사한다 */
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)  /* /sys/kernel/kobject_example/foo 파일에 값을 쓸 때 호출되는 함수 */
{
int ret;

ret = kstrtoint(buf, 10, &foo);   /* buf 값을 integer로 변환하여 foo에 대입한다 */
if (ret < 0)
return ret;

return count;
}

/* Sysfs attributes cannot be world-writable. */
static struct kobj_attribute foo_attribute =
__ATTR(foo, 0664, foo_show, foo_store);

/*
 * More complex function where we determine which variable is being accessed by
 * looking at the attribute for the "baz" and "bar" files.
 */
static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
     char *buf)  /* /sys/kernel/kobject_example/baz or bar 파일을 읽을때 호출되는 함수 */
{
int var;

if (strcmp(attr->attr.name, "baz") == 0)
var = baz;
else
var = bar;
return sprintf(buf, "%d\n", var);
}

static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
      const char *buf, size_t count)   /* /sys/kernel/kobject_example/baz or bar 파일에 값을 쓸 때 호출되는 함수 */
{
int var, ret;

ret = kstrtoint(buf, 10, &var);
if (ret < 0)
return ret;

if (strcmp(attr->attr.name, "baz") == 0)
baz = var;
else
bar = var;
return count;
}

static struct kobj_attribute baz_attribute =
__ATTR(baz, 0664, b_show, b_store);
static struct kobj_attribute bar_attribute =
__ATTR(bar, 0664, b_show, b_store);


/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *attrs[] = {
&foo_attribute.attr,
&baz_attribute.attr,
&bar_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};

/*
 * An unnamed attribute group will put all of the attributes directly in
 * the kobject directory.  If we specify a name, a subdirectory will be
 * created for the attributes with the directory being the name of the
 * attribute group.
 */
static struct attribute_group attr_group = {
.attrs = attrs,
};

static struct kobject *example_kobj;

static int __init example_init(void)
{
int retval;

/*
* Create a simple kobject with the name of "kobject_example",
* located under /sys/kernel/
*
* As this is a simple directory, no uevent will be sent to
* userspace.  That is why this function should not be used for
* any type of dynamic kobjects, where the name and number are
* not known ahead of time.
*/
example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;

/* Create the files associated with this kobject */
retval = sysfs_create_group(example_kobj, &attr_group);
if (retval)
kobject_put(example_kobj);

return retval;
}

static void __exit example_exit(void)
{
kobject_put(example_kobj);
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");   /* 참고문헌 [2]의 저자 */
--------------------------------------------------------------------------------------------------------------------------------------------------------------

참고로, struct kobj_attribute는 include/linux/kobject.h 파일에 위치하고 있으며, 그 내용은 다음과 같다.

struct kobj_attribute {
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
            char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
             const char *buf, size_t count);
};

또한 struct attribute는 include/linux/sysfs.h 파일에 위치하고 있다.

struct attribute {
    const char      *name;
    umode_t         mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    bool            ignore_lockdep:1;
    struct lock_class_key   *key;
    struct lock_class_key   skey;
#endif
};

끝으로,  struct kobject는 include/linux/kobject.h에 정의되어 있다.

struct kobject {
    const char      *name;
    struct list_head    entry;
    struct kobject      *parent;
    struct kset     *kset;
    struct kobj_type    *ktype;
    struct kernfs_node  *sd; /* sysfs directory entry */
    struct kref     kref;   
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    struct delayed_work release;
#endif
    unsigned int state_initialized:1;
    unsigned int state_in_sysfs:1;
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1;
};
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to run it on the target board>
# insmod ./kobject-example.ko 
[   18.454888] kobject_example: loading out-of-tree module taints kernel.


그림 3.1 kobject-example.ko 실행 시, sysfs 모습


4. Linked list 예제
프로그램을 짜다(소금 ?) 보면, list를 사용해야 할 상황이 많이 발생한다. Linux kernel에서도 이 경우를 대비하여 보다 효율적으로 list를 제어할 수 있도록, 관련 API를 제공하고 있다. 따라서 이번 절에서는 kernel linked list를 활용한 예제 프로그램을 하나 소개해 보기로 하겠다. 아주 간단한 program이지만 제대로 이해할 필요가 있어 보인다. 참고로, 참고문헌 [4]의 chapter 6을 보면 list 관련 내용이 잘 설명되어 있으니 한번 참고하기 바란다.

--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab1_list.c>
#include <linux/module.h>
#include <asm/atomic.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/init.h>

/* we're going to do a camel race! */
static LIST_HEAD(camel_list);

struct camel_entry {  /* list를 구성하는 element */
struct list_head clist; /* link in camel list */
int gate; /* assigned gate at Camel Derby */
char name[20]; /* camel's name */
};

static int __init my_init(void)
{
struct camel_entry *ce;
int k;

for (k = 0; k < 5; k++) {
if (!(ce = kmalloc(sizeof(struct camel_entry), GFP_KERNEL))) {   /* struct camel_entry 관련 kernel 메모리를 할당 받는다 */
pr_info(" Camels: failed to allocate memory for camel %d \n", k);
return -ENOMEM;
}

ce->gate = 11 + k; /* gate number */
sprintf(ce->name, "Camel_%d", k + 1);
pr_info(" Camels: adding %s at gate %d to camel_list \n", ce->name, ce->gate);
list_add(&ce->clist, &camel_list);  /* camel_list의 앞에 항목(ce)을 추가한다 */
}
return 0;
}

static void __exit my_exit(void)
{
struct list_head *list; /* pointer to list head object */
struct list_head *tmp; /* temporary list head for safe deletion */

if (list_empty(&camel_list)) {   /* list에 항목이 없다면 그냥 return 한다 */
pr_info("Camels (exit): camel list is empty! \n");
return;
}
pr_info("Camels: (exit): camel list is NOT empty! \n");

list_for_each_safe(list, tmp, &camel_list) {   /* camel_list의 항목을 하나씩 훑어 가면서 아래 내용을 수행한다 */
struct camel_entry *ce =
          list_entry(list, struct camel_entry, clist);   /* list가 가리키는 내용으로 부터 struct camel_entry 항목을 추출해 낸다. */
list_del(&ce->clist);  /* 앞에서 추출한 내용(element)을 제거한다 */
pr_info("Camels (exit): %s at gate %d removed from list \n", ce->name, ce->gate);
kfree(ce);
}

/* Now, did we remove the camel manure? */

if (list_empty(&camel_list))  /* camel_list에 항목이 없는지를 확인한다 */
pr_info("Camels (done): camel list is empty! \n");
else
pr_info("Camels: (done): camel list is NOT empty! \n");
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Jerry Cooperstein");
MODULE_AUTHOR("Dave Harris");
MODULE_DESCRIPTION("LDD:2.0 s_07/lab1_list.c");
MODULE_LICENSE("GPL v2");
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to run it on the target board>
# insmod ./lab1_list.ko 
[ 3156.702333]  Camels: adding Camel_1 at gate 11 to camel_list 
[ 3156.708185]  Camels: adding Camel_2 at gate 12 to camel_list 
[ 3156.716174]  Camels: adding Camel_3 at gate 13 to camel_list 
[ 3156.724354]  Camels: adding Camel_4 at gate 14 to camel_list 
[ 3156.731942]  Camels: adding Camel_5 at gate 15 to camel_list 

# rmmod lab1_list
[ 3165.469602] Camels: (exit): camel list is NOT empty! 
[ 3165.474758] Camels (exit): Camel_5 at gate 15 removed from list 
[ 3165.481133] Camels (exit): Camel_4 at gate 14 removed from list 
[ 3165.487205] Camels (exit): Camel_3 at gate 13 removed from list 
[ 3165.493369] Camels (exit): Camel_2 at gate 12 removed from list 
[ 3165.499514] Camels (exit): Camel_1 at gate 11 removed from list 
[ 3165.505568] Camels (done): camel list is empty!


5. KFIFO 사용 예제
Linux kernel에서는 효과적인 kernel 구현을 위해 다양한 data structure(예를 들어, linked list, fifo, rbtree, hash ... 등)를 구현해 두고 있다. 이번 절에서는 이중 FIFO(queue) 관련 API를 사용하는 예제 program(linux-4.9/samples/kfifo)을 소개해 보고자 한다.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
<bytestream-example.c>

/*
 * Sample kfifo byte stream implementation
 *
 * Copyright (C) 2010 Stefani Seibold <stefani@seibold.net>
 *
 * Released under the GPL version 2 only.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/mutex.h>
#include <linux/kfifo.h>

/*
 * This module shows how to create a byte stream fifo.
 */

/* fifo size in elements (bytes) */
#define FIFO_SIZE 32    /* fifo의 최대 크기 */

/* name of the proc entry */
#define PROC_FIFO "bytestream-fifo"

/* lock for procfs read access */
static DEFINE_MUTEX(read_lock);  /* read 동작을 위해 사용되는 mutex - 정적 선언 */

/* lock for procfs write access */
static DEFINE_MUTEX(write_lock);  /* write 동작을 위해 사용되는 mutex - 정적 선언 */

/*
 * define DYNAMIC in this example for a dynamically allocated fifo.
 *
 * Otherwise the fifo storage will be a part of the fifo structure.
 */
#if 0
#define DYNAMIC
#endif

#ifdef DYNAMIC
static struct kfifo test;  /* kfifo를 동적으로 할당하고자 할 경우 사용 */
#else
static DECLARE_KFIFO(test, unsigned char, FIFO_SIZE);   /* kfifo 정적 선언 - unsigned char 형 32개로 구성 */
#endif

static const unsigned char expected_result[FIFO_SIZE] = {
 3,  4,  5,  6,  7,  8,  9,  0,
 1, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42,
};

static int __init testfunc(void)   /* fifo 동작을 확인하는 함수 */
{
  unsigned char buf[6];
unsigned char i, j;
unsigned int ret;

printk(KERN_INFO "byte stream fifo test start\n");

  /* put string into the fifo */
kfifo_in(&test, "hello", 5);   /* fifo에 5개의 unsigned char(hello) 입력(추가) */

/* put values into the fifo */
for (i = 0; i != 10; i++)
kfifo_put(&test, i);  /* fifo에 0 ~ 9까지의 숫자 입력(추가) */

/* show the number of used elements */
printk(KERN_INFO "fifo len: %u\n", kfifo_len(&test));   /* fifo의 현재 크기(element 갯수) 출력 */

/* get max of 5 bytes from the fifo */
i = kfifo_out(&test, buf, 5);   /* fifo로 부터 5개의 element(= hello) 출력(꺼내기) */
printk(KERN_INFO "buf: %.*s\n", i, buf);

/* get max of 2 elements from the fifo */
ret = kfifo_out(&test, buf, 2);  /* fifo로 부터 2개의 element(0,1) 꺼내기 */
printk(KERN_INFO "ret: %d\n", ret);
/* and put it back to the end of the fifo */
ret = kfifo_in(&test, buf, ret);  /* fifo에 앞선 함수 수행 결과(=2) 를 입력(추가) */
printk(KERN_INFO "ret: %d\n", ret);

/* skip first element of the fifo */
printk(KERN_INFO "skip 1st element\n");
kfifo_skip(&test);  /* fifo의 첫번째 element(= 2)를 skip */

/* put values into the fifo until is full */
for (i = 20; kfifo_put(&test, i); i++)   /* fifo가 가득 찰 때(fifo len이 32가 될때)까지 20 부터 숫자를 하나씩 증가시키면서 대입 */
;

printk(KERN_INFO "queue len: %u\n", kfifo_len(&test));   /* fifo의 총 element 수(= 32)를 출력 */

/* show the first value without removing from the fifo */
if (kfifo_peek(&test, &i))   /* fifo의 element(= 3)를 제거하지 않으면서 첫번째 element를 출력 */
printk(KERN_INFO "%d\n", i);

/* check the correctness of all values in the fifo */
j = 0;
while (kfifo_get(&test, &i)) {   /* fifo의 현재 index 위치의 값을 출력 */
printk(KERN_INFO "item = %d\n", i);
if (i != expected_result[j++]) {   /* 꺼낸 값을 expected_result[] 배열의 값과 비교 */
printk(KERN_WARNING "value mismatch: test failed\n");
return -EIO;
}
}
if (j != ARRAY_SIZE(expected_result)) {
printk(KERN_WARNING "size mismatch: test failed\n");
return -EIO;
}
printk(KERN_INFO "test passed\n");

return 0;
}

static ssize_t fifo_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)  /* proc write 함수 */
{
int ret;
unsigned int copied;

if (mutex_lock_interruptible(&write_lock))
return -ERESTARTSYS;

ret = kfifo_from_user(&test, buf, count, &copied);   /* userspace로 부터 넘어온 값(= buf)을 fifo(= test)에 추가. 단순히 queue의 맨끝에 추가하는 것이 아니라 fifo 전체가 buf 내용으로 교체됨. */

mutex_unlock(&write_lock);

return ret ? ret : copied;
}

static ssize_t fifo_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  /* proc read 함수 */
{
int ret;
unsigned int copied;

if (mutex_lock_interruptible(&read_lock))
return -ERESTARTSYS;

ret = kfifo_to_user(&test, buf, count, &copied);  /* fifo로 부터 data를 꺼내어 userspace(=buf)로 넘긴다. fifo 전체의 내용이 넘어가는 것으로 보아야 함. */

mutex_unlock(&read_lock);

return ret ? ret : copied;
}

static const struct file_operations fifo_fops = {  /* proc file operations 정의 */
.owner = THIS_MODULE,
.read fifo_read,
.write fifo_write,
.llseek = noop_llseek,
};

static int __init example_init(void)
{
#ifdef DYNAMIC
int ret;

ret = kfifo_alloc(&test, FIFO_SIZE, GFP_KERNEL);   /* 동적으로 fifo를 할당하고자 할 경우 사용되는 API */
if (ret) {
printk(KERN_ERR "error kfifo_alloc\n");
return ret;
}
#else
INIT_KFIFO(test);   /* 정적으로 선언한 kfifo 변수를 초기화 시킴 */
#endif
if (testfunc() < 0) {   /* testfunc() 함수 호출 */
#ifdef DYNAMIC
kfifo_free(&test);
#endif
return -EIO;
}

if (proc_create(PROC_FIFO, 0, NULL, &fifo_fops) == NULL) {  /* /proc 아래에 bytestream-fifo 파일 생성 */
#ifdef DYNAMIC
kfifo_free(&test);
#endif
return -ENOMEM;
}
return 0;
}

static void __exit example_exit(void)
{
remove_proc_entry(PROC_FIFO, NULL);   /* /proc/bytestream-fifo 파일 제거 */
#ifdef DYNAMIC
kfifo_free(&test);  /* 동적으로 할당한 kfifo(= test) 해제 */
#endif
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stefani Seibold <stefani@seibold.net>");
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to run it on the target board>
insmod ./bytestream-example.ko 
[12911.736850] byte stream fifo test start
[12911.740849] fifo len: 15
[12911.743409] buf: hello
[12911.745790] ret: 2
[12911.747822] ret: 2
[12911.749929] skip 1st element
[12911.752836] queue len: 32
[12911.755474] 3
[12911.757067] item = 3
[12911.759321] item = 4
[12911.761529] item = 5
[12911.763731] item = 6
[12911.765931] item = 7
[12911.768132] item = 8
[12911.770380] item = 9
[12911.772587] item = 0
[12911.774788] item = 1
[12911.776988] item = 20
[12911.779336] item = 21
[12911.781630] item = 22
[12911.783918] item = 23
[12911.786204] item = 24
[12911.788537] item = 25
[12911.790831] item = 26
[12911.793119] item = 27
[12911.795406] item = 28
[12911.797693] item = 29
[12911.800025] item = 30
[12911.802317] item = 31
[12911.804606] item = 32
[12911.806892] item = 33
[12911.809222] item = 34
[12911.811515] item = 35
[12911.813803] item = 36
[12911.816091] item = 37
[12911.818421] item = 38
[12911.820713] item = 39
[12911.823001] item = 40
[12911.825288] item = 41
[12911.827574] item = 42
[12911.829905] test passed

그림 5.1 kfifo 예제 proc 동작 모습


6. MMAP 관련 예제
이번 절에서는 kernel 영역의 memory를 userspace로 mapping하여 userspace에서 자유롭게 사용할 수 있도록 해주는 mmap 예제 program을 소개해 보고자 한다. mmap을 사용하는 대표적인 예로는 framebuffer, android ashmem 등을 생각해 볼 수 있다.

--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab_miscdev.h>   /* 1절의 character driver와 비슷한 역할의 하는 코드 - 단, misc driver 형태 임, 아래 lab3_mmap.c 파일에 include되는 파일임. */
#ifndef _LAB_CHAR_H
#define _LAB_CHAR_H

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/miscdevice.h>

#define MYDEV_NAME "mycdrv"

static char *ramdisk;
static size_t ramdisk_size = (16 * PAGE_SIZE);   /* 65536 = 16 * 4096 bytes */

static const struct file_operations mycdrv_fops;

/* generic entry points */

static inline int mycdrv_generic_open(struct inode *inode, struct file *file)
{
static int counter = 0;
pr_info(" attempting to open device: %s:\n", MYDEV_NAME);
pr_info(" MAJOR number = %d, MINOR number = %d\n", imajor(inode), iminor(inode));
counter++;

pr_info(" successfully open  device: %s:\n\n", MYDEV_NAME);
pr_info("I have been opened  %d times since being loaded\n", counter);
pr_info("ref=%d\n", (int)module_refcount(THIS_MODULE));

return 0;
}

static inline int mycdrv_generic_release(struct inode *inode, struct file *file)
{
pr_info(" closing character device: %s:\n\n", MYDEV_NAME);
return 0;
}

static inline ssize_t
mycdrv_generic_read(struct file *file, char __user * buf, size_t lbuf,
    loff_t * ppos)
{
    /* 나중에 lab3_mmap_test.c에서 mmaped buffer의 내용과 이 함수를 호출하여 얻은 결과를 비교하게 됨. 미리 말하지만, 동일한 buffer(ramdisk)를 참조하므로 결과는 당연히 같음 ...... (A) */
int nbytes, maxbytes, bytes_to_do;
maxbytes = ramdisk_size - *ppos;
bytes_to_do = maxbytes > lbuf ? lbuf : maxbytes;
if (bytes_to_do == 0)
pr_warning("Reached end of the device on a read");

nbytes = bytes_to_do - copy_to_user(buf, ramdisk + *ppos, bytes_to_do);
*ppos += nbytes;
pr_info("\n Leaving the   READ function, nbytes=%d, pos=%d\n", nbytes, (int)*ppos);
return nbytes;
}

static inline ssize_t
mycdrv_generic_write(struct file *file, const char __user * buf, size_t lbuf,
     loff_t * ppos)
{
int nbytes, maxbytes, bytes_to_do;
maxbytes = ramdisk_size - *ppos;
bytes_to_do = maxbytes > lbuf ? lbuf : maxbytes;
if (bytes_to_do == 0)
pr_warning("Reached end of the device on a write");
nbytes =
    bytes_to_do - copy_from_user(ramdisk + *ppos, buf, bytes_to_do);
*ppos += nbytes;
pr_info("\n Leaving the   WRITE function, nbytes=%d, pos=%d\n", nbytes, (int)*ppos);
return nbytes;
}

static inline loff_t mycdrv_generic_lseek(struct file *file, loff_t offset,
  int orig)
{
loff_t testpos;
switch (orig) {
case SEEK_SET:
testpos = offset;
break;
case SEEK_CUR:
testpos = file->f_pos + offset;
break;
case SEEK_END:
testpos = ramdisk_size + offset;
break;
default:
return -EINVAL;
}
testpos = testpos < ramdisk_size ? testpos : ramdisk_size;
testpos = testpos >= 0 ? testpos : 0;
file->f_pos = testpos;
pr_info("Seeking to pos=%ld\n", (long)testpos);
return testpos;
}

static struct miscdevice my_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = MYDEV_NAME,
.fops = &mycdrv_fops,
};

static int __init my_generic_init(void)
{
ramdisk = kmalloc(ramdisk_size, GFP_KERNEL);   /* 여기서 mmap에 사용할 kernel memory를 할당해 둠 */
if (misc_register(&my_misc_device)) {
pr_err("Couldn't register device misc, " "%d.\n", my_misc_device.minor);
kfree(ramdisk);
return -EBUSY;
}

pr_info("\nSucceeded in registering character device %s\n", MYDEV_NAME);
return 0;
}

static void __exit my_generic_exit(void)
{
misc_deregister(&my_misc_device);
pr_info("\ndevice unregistered\n");
kfree(ramdisk);
}

MODULE_AUTHOR("Jerry Cooperstein");
MODULE_DESCRIPTION("LDD:2.0 s_18/lab_miscdev.h");
MODULE_LICENSE("GPL v2");
#endif
--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab3_mmap.c>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/io.h> /* for virt_to_phys() */

/* either of these (but not both) will work */
//#include "lab_char.h"
#include "lab_miscdev.h"   /* 위의 파일 */

#define MMAP_DEV_CMD_GET_BUFSIZE 1 /* defines our IOCTL cmd */

static int mycdrv_mmap(struct file *filp, struct vm_area_struct *vma)  /* userspace에서 mmap() 함수 호출시 call되는 함수 */
{
unsigned long pfn;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long len = vma->vm_end - vma->vm_start;

if (offset >= ramdisk_size)
return -EINVAL;
if (len > (ramdisk_size - offset))
return -EINVAL;
pr_info("%s: mapping %ld bytes of ramdisk at offset %ld\n", __stringify(KBUILD_BASENAME), len, offset);

/* need to get the pfn for remap_pfn_range -- either of these two
   follwoing methods will work */

/*    pfn = page_to_pfn (virt_to_page (ramdisk + offset)); */
pfn = virt_to_phys(ramdisk + offset) >> PAGE_SHIFT;   /* virtual address를 physical address로 전환함. 단, kernel direct mapped RAM memory에서만 유효함 */

    /**
     * remap_pfn_range로 넘어가는 각각의 parameter의 의미는 다음과 같다.
     * @vma: user vma to map to
     * @vma->vm_start: target user address to start at
     * @pfn: physical address of kernel memory
     * @len: size of map area
     * @vma->vm_page_prot: page protection flags for this mapping
     */
if (remap_pfn_range(vma, vma->vm_start, pfn, len, vma->vm_page_prot)) {  /* 여기에서 kernel memory를 userspace로 remap 시켜 준다 */
return -EAGAIN;
}
return 0;
}

/*
 *  mycdrv_unlocked_ioctl() --- give the user the value ramdisk_size
 */
static long
mycdrv_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned long tbs = ramdisk_size;
void __user *ioargp = (void __user *)arg;

switch (cmd) {
default:
return -EINVAL;

case MMAP_DEV_CMD_GET_BUFSIZE:
if (copy_to_user(ioargp, &tbs, sizeof(tbs)))   /* kernel에서 할당한 buffer의 크기(ramdisk_size) 값을 userspace로 전달한다 */
return -EFAULT;
return 0;
}
}

static const struct file_operations mycdrv_fops = {
.owner = THIS_MODULE,
.read = mycdrv_generic_read,
.write = mycdrv_generic_write,
.mmap = mycdrv_mmap,
.unlocked_ioctl = mycdrv_unlocked_ioctl,
.llseek = mycdrv_generic_lseek,
};

module_init(my_generic_init);
module_exit(my_generic_exit);

MODULE_AUTHOR("Bill Kerr");
MODULE_AUTHOR("Jerry Cooperstein");
MODULE_DESCRIPTION("LDD:2.0 s_18/lab3_mmap.c");
MODULE_LICENSE("GPL v2");
--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab3_mmap_test.c - application>
/*
 * Author Bill Kerr 8/2003
 * Modifications Jerry Cooperstein.2003-2008
 * LICENSE GPLv2
 @*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <malloc.h>

#define MMAP_DEV_CMD_GET_BUFSIZE 1 /* defines our IOCTL cmd */

void read_and_compare(int fd, char *read_buf, char *mmap_buf, unsigned long len)
{
/* Read the file and compare with mmap_buf[] */

if (read(fd, read_buf, len) != len) {   /* fd를 가지고 file read한 내용을 read_buf에 담는다 */
fprintf(stderr, "read problem:  %s\n", strerror(errno));
exit(1);
}
if (memcmp(read_buf, mmap_buf, len) != 0) {  /* read_buf의 내용과 argument로 넘어온 mmpa_buf의 내용을 len 만큼 비교한다 */
fprintf(stderr, "buffer miscompare\n");
exit(1);
}
}

int main(int argc, char **argv)
{
unsigned long j, len;
int fd;
char *read_buf, *mmap_buf, *filename = "/dev/mycdrv";

srandom(getpid());

if (argc > 1)
filename = argv[1];

if ((fd = open(filename, O_RDWR)) < 0) {
fprintf(stderr, "open of %s failed:  %s\n", filename, strerror(errno));
exit(1);
}
/* have the driver tell us the buffer size */
if (ioctl(fd, MMAP_DEV_CMD_GET_BUFSIZE, &len) < 0) {   /* len에 kernel에서 할당한 buffer의 크기를 가져옴 */
fprintf(stderr, "ioctl failed:  %s\n", strerror(errno));
exit(1);
}
printf("driver's ioctl says buffer size is %ld\n", len);

read_buf = malloc(len);
mmap_buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); /* kernel memory를 userspace로 mapping하여 사용하도록 함. mmap_buf를 이용하면 됨. */
if (mmap_buf == (char *)MAP_FAILED) {
fprintf(stderr, "mmap of %s failed:  %s\n", filename,
strerror(errno));
exit(1);
}
printf("mmap succeeded:  %p\n", mmap_buf);

/* modify the mmaped buffer */
for (j = 0; j < len; j++)
*(mmap_buf + j) = (char)j;    /* kernel mmapped buffer에 값을 저장함 */

/* Read the file and compare with mmap_buf[] */
read_and_compare(fd, read_buf, mmap_buf, len);   /* fd를 가지고, read() 함수를 호출하여 얻은 결과와 mmap_buf의 내용을 상호 비교함 - 동일함 ... 위의 (A) 참조 */
printf("comparison of same data via read() and mmap() successful\n");

/* Change one randomly chosen byte in the mmap region */

j = random() % len;
*(mmap_buf + j) = random() % j;  /* kernel mmapped buffer에 다른 값을 저장함 */

/*  repeat the read-back comparison. */
(void)lseek(fd, 0, SEEK_SET);
read_and_compare(fd, read_buf, mmap_buf, len);  /* fd를 가지고, read() 함수를 호출하여 얻은 결과와 mmap_buf의 내용을 상호 비교함 - 동일함 ... 위의 (A) 참조 */
printf("comparison of modified data via read() and mmap() successful\n");

return 0;
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to run it on the target board>
insmod ./lab3_mmap.ko 
[ 5328.033761] 
[ 5328.033761] Succeeded in registering character device mycdrv


./lab3_mmap_test 
driver's ioctl says buffer size i[ 5339.009482] "lab3_mmap": mapping 65536 bytes of ramdisk at offset 0
s 65536
mmap succeeded:  0xb6e4a000
[ 5339.020115] 
[ 5339.020115]  Leaving the   READ function, nbytes=65536, pos=65536
comparison of same data via read([ 5339.031384] Seeking to pos=0
) and mmap() successful
[ 5339.037177] 
[ 5339.037177]  Leaving the   READ function, nbytes=65536, pos=65536
comparison of modified data via read() and mmap() successful


7. Netlink socket 예제
Linux에서 kernel과 userspace간의 통신은 매우 중요한 문제이다. 여러가지 통신 방법 중, 이번 절에서는 netlink socket을 사용하는 방법을 소개해 보고자 한다.

--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab2_nl_sender.c - 메시지 송신 kernel module>

/*
 * The code herein is: Copyright Jerry Cooperstein, 2009
 *
 * This Copyright is retained for the purpose of protecting free
 * redistribution of source.
 *
 *     URL:    http://www.coopj.com
 *     email:  coop@coopj.com
 *
 * The primary maintainer for this code is Jerry Cooperstein
 * The CONTRIBUTORS file (distributed with this
 * file) lists those known to have contributed to the source.
 *
 * This code is distributed under Version 2 of the GNU General Public
 * License, which you should have received with the source.
 *
 */
/*
  Netlink demo that sends a message from kernel-space to user-space.

  Copyright Dominic Duval <dduval@redhat.com> according to the terms
  of the GNU Public License.

 (minor changes introduced by J. Cooperstein, coop@coopj.com
  to work with newer kernels.)

 (major changes introduced by Paul Drews, paul.drews@intel.com)

*/

#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/sched.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>

#define MAX_PAYLOAD 1024   /* netlink socket을 통해 전달한  message buffer의 크기 */
#define NETLINK_MESSAGE "This message originated from the kernel!"
#define NL_EXAMPLE 19           /* was 19 */
#define NL_GROUP 1              /* was 1 */

#define MSG_INTERVAL_SEC 5

#define seconds_to_jiffies(sec) (cputime_to_jiffies(secs_to_cputime(sec)))

static struct sock *nl_sk = NULL;
static struct task_struct *sender_thread = NULL;

static int my_sender_thread (void *data)  /* netlink socket을 사용하여 userspace로 data를 지속적으로 던지는 함수(= thread function) */
{
    struct sk_buff *skb = NULL;   /* socket buffer pointer 선언 */
    struct nlmsghdr *nlh;   /* netlink msg header pointer 선언 */

    do {
        skb = alloc_skb (NLMSG_SPACE (MAX_PAYLOAD), GFP_KERNEL);   /* skb buffer 동적 할당 */
        nlh = (struct nlmsghdr *)skb_put (skb, NLMSG_SPACE (MAX_PAYLOAD));  /* sk buffer에 data를 추가한다 */
        nlh->nlmsg_len = NLMSG_SPACE (MAX_PAYLOAD);
        nlh->nlmsg_pid = 0;
        nlh->nlmsg_flags = 0;
        strcpy (NLMSG_DATA (nlh), NETLINK_MESSAGE);
        NETLINK_CB (skb).dst_group = NL_GROUP;

        netlink_broadcast (nl_skskb, 0, NL_GROUP, GFP_KERNEL);   /* nl_sk socket으로 sk buffer(= skb)를 내보낸다 */
        printk (KERN_INFO "my_sender_thread sent a message at jiffies=%ld\n",
                jiffies);
        set_current_state (TASK_INTERRUPTIBLE);  /* 현재 task를 interruptible하도록 설정한다. 즉, 동작 중 interrupt 발생 시, 처리를 중지하고 sleep으로 들어 갈 수 있도록 설정한다 */
        schedule_timeout (seconds_to_jiffies (MSG_INTERVAL_SEC));   /* 5초가 sleep 한다 */
    } while (!kthread_should_stop ());   /* thread가 멈출 조건이 아니면 do while loop를 무한 반복 */

    return 0;

}   /* my_sender_thread */

static int __init my_init (void)
{
    nl_sk = netlink_kernel_create (&init_netNL_EXAMPLE, NULL);  /* kernel netlink socket을 만든다 */

    if (nl_sk == NULL)
        return -1;

    sender_thread = kthread_run (my_sender_thread,
                                 NULL, "lab2_nl_sender_thread");   /* kernel thread를 하나 만든다 */
    if (IS_ERR (sender_thread)) {
        printk (KERN_INFO "Error %ld createing thread\n",
                PTR_ERR (sender_thread));
        sock_release (nl_sk->sk_socket);
        return -1;
    }

    printk (KERN_INFO "Adding netlink testing module\n");

    return 0;
}

static void __exit my_exit (void)
{
    printk (KERN_INFO "Removing netlink testing module\n");
    kthread_stop (sender_thread);   /* thread를 종료한다 */
    sock_release (nl_sk->sk_socket);  /* socket을 close 한다 */
}

module_exit (my_exit);
module_init (my_init);

MODULE_AUTHOR ("Dominic Duval");
MODULE_AUTHOR ("Paul Drews");
MODULE_AUTHOR ("Jerry Cooperstein");
MODULE_DESCRIPTION ("LPD:1.0 s_27/lab2_nl_sender.c");
MODULE_LICENSE ("GPL v2");
--------------------------------------------------------------------------------------------------------------------------------------------------------------
<lab2_nl_receive_test.c - 메시지 수신 application>

#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#define MAX_PAYLOAD 1024
#define NL_EXAMPLE 19           /* was 19 */
#define NL_GROUP 1              /* was 1 */

int read_event (int sock)
{
    int ret;
    struct nlmsghdr *nlh;

    nlh = malloc (NLMSG_SPACE (MAX_PAYLOAD));   /* message를 수신할 버퍼를 할당 */
    memset (nlh, 0, NLMSG_SPACE (MAX_PAYLOAD));
    ret = recv (sock, (void *)nlh, NLMSG_SPACE (MAX_PAYLOAD), 0);  /* socket으로 부터 data를 수신한다 */

    printf ("Message size: %d , Message: %s\n", ret, (char *)NLMSG_DATA (nlh));   /* socket으로 부터 수신한 message 크기와 nl msg header를 제외한 나머지 즉, 순수 data 부분만을 출력한다 */

    if (nlh)
free(nlh);

    return 0;
}

int main (int argc, char *argv[])
{
    struct sockaddr_nl addr;
    int nls;
    int rc;

    /* Set up the netlink socket */
    nls = socket (PF_NETLINKSOCK_RAWNL_EXAMPLE);   /* netlink socket을 open한다 */
    printf ("nls=%d\n", nls);

    memset ((void *)&addr, 0, sizeof (addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid ();
    addr.nl_groups = NL_GROUP;
    rc = bind (nls, (struct sockaddr *)&addr, sizeof (addr));   /* socket을 bind한다 */
    printf ("errno=%d\n", errno);
    perror ("bind");
    printf ("rc from bind = %d\n", rc);

    while (1)
        read_event (nls);   /* loop을 돌며 netlink socket으로 부터 data를 수신한다 */

    return 0;
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to run it on the target board>
insmod ./lab2_nl_sender.ko 
[15578.658085] Adding netlink testing module
[15578.662411] my_sender_thread sent a message at jiffies=1527865
[15583.691285] my_sender_thread sent a message at jiffies=1528368
[15588.731366] my_sender_thread sent a message at jiffies=1528872
[15593.771414] my_sender_thread sent a message at jiffies=1529376
[15598.811430] my_sender_thread sent a message at jiffies=1529880

./lab2_nl_receive_test 
nls=3
errno=0
bind: Success
rc from bind = 0
Message size: 1040 , Message: This message originated from the kernel!
Message size: 1040 , Message: This message originated from the kernel!
Message size: 1040 , Message: This message originated from the kernel!
Message size: 1040 , Message: This message originated from the kernel!


이상으로 Linux kernel 4.x에서도 동작 가능한, 몇가지 kernel programming 기법을 예제를 통해 확인해 보았다. 참고로  지금까지 소개한 코드는 아래 github에서 확인할 수 있다.




References
1. Writing Linux Device Drivers(a guide with exercises), Jerry Cooperstein.
2. Linux Device Drivers 3rd Edition, Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman.
3. Essential Linux Device Drivers, Sreekrishnan Venkateswaran.
4. Linux Kernel Development 3rd Edition, Robert Love.



Slowboot

댓글 7개: