리눅스 커널의 구조와 원리

isbn: 9791158391980

저자가 디버깅, 커널을 강조하다보니, 제목이 너무 길어졌다. 다행히 초심자가 쉽게 이해할 수 있는 내용이다. 책 2권이다 보니 사기 아까웠지만, 읽고나면 살만한 책이다. 이 책이 자세하게 설명하여 커널이 어떻게 동작하는지 알 수 있다. 내용이 인터넷에도 있겠지만, 내가 찾는 수고를 저자가 했고, 틀린 내용을 저자가 걸러줬다. 이렇게 보면 지출할 만한 가격이다.

20년 전 리눅스를 시작하려 해도 괜찮은 책, 문서가 없었다. 그 시절 인터넷도 잘 발달되지 않아 따라 하기 어려웠다. 커널 구조, 동작 방식을 설명하는 책도 찾기 어려웠고, 파편화 된 영문 문서를 종합하여 이해할만한 시간, 능력, 의지도 없었다. PC 성능도 낮아 한번 커널 컴파일하면 4시간 정도 걸렸다. 커널 패닉에 닥치면 어떻게 해결할지 막막했다. 지금같이 휴대폰으로 찾아볼 수 없다보니. 진심이 아닌 취미로 접근하기 너무나도 어려웠다.

다행히 시대가 변해 성능좋고 값싼 하드웨어를 쉽게 구할 수 있다. 이 책과 같은 좋은 교재로 짧은 시간에 익힐 수 있다. 커널에서 지원하는 디버그 툴이 함수 호출 내역을 표시한다. 인터넷도 발달되어 남이 작성한 문서를 쉽게 찾고, 사용할 수 있다. 의지만 있다면 과거 5년 학습 결과를 지금 6개월에 달성할 수 있어 보인다.

책 차례를 보면 커널을 어떻게 접근할지 보인다. OS 기본이 프로세스이므로 task descriptor에서 시작한다. 인터럽트 또한 중요하여 다음에 있다. softirq, workqueue도 인터럽트 다음으로 쉽게 알 수 있다. 2권을 보면 그 외 타이머, 동기화, 스케줄러 등 기본 지식을 학습할 수 있다.

책을 읽고 나면 커널을 쉽게 접근할 수 있고, 남이 작성한 문서를 이해할 수 있다는 자신감-실력이 아닌-을 갖는다. IoT 시대를 대비할 수 있는 지금 배우지 않을 이유가 없다. ON/OFF 스위치를 만들더라도 인터넷도 되고, 터치 패드 있는 제품이 뽀대 난다. 얼마인지 모르겠으나 정말 있다!! 수화물 5kg를 희생하여 미국까지 들고 온 보람있다.

spinlock, mutex 실습

모듈을 로딩하면 kernel thread부터 만들어야 한다. 같은 책 초반에 나왔는데, 다시 보니 기억할 수 없다. 스레드를 만든 후 함수와 데이터를 넘겨야 하는데, 데이터가 void 포인터다. struct로 캐스팅 하고 싶은데, 에러가 났다. kernel 코드를 보고 괄호를 몇 번 붙였다. 아! ㅅㅂ. 구글 찾아보기보다 시(간)성비가 더 좋다.

pi@raspberrypi:~/linux $ grep -wn "kthread_create" -r ./drivers
./drivers/usb/usbip/usbip_common.h:285:		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
./drivers/usb/atm/ueagle-atm.c:2228:	sc->kthread = kthread_create(uea_kthread, sc, "ueagle-atm");
./drivers/usb/atm/usbatm.c:977:	t = kthread_create(usbatm_do_heavy_init, instance, "%s",
./drivers/usb/gadget/file_storage.c:3527:	fsg->thread_task = kthread_create(fsg_main_thread, fsg,
./drivers/usb/gadget/function/u_serial.c:1059:	info->console_thread = kthread_create(gs_console_thread,
./drivers/usb/gadget/function/f_mass_storage.c:2924:			kthread_create(fsg_main_thread, common, "file-storage");
./drivers/usb/host/dwc_common_port/dwc_common_fbsd.c:981:	retval = kthread_create((void (*)(void *))func, data, &thread->proc,
./drivers/iio/adc/ina2xx-adc.c:840:	task = kthread_create(ina2xx_capture_thread, (void *)indio_dev,

pi@raspberrypi:~/linux $ vi ./drivers/usb/host/dwc_common_port/dwc_common_fbsd.c
dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data)
{
        int retval;
        dwc_thread_t *thread = DWC_ALLOC(sizeof(*thread));

        if (!thread) {
                return NULL;
        }

        thread->abort = 0; 
        retval = kthread_create((void (*)(void *))func, data, &thread->proc,
                                RFPROC | RFNOWAIT, 0, "%s", name);
        if (retval) {
                DWC_FREE(thread);
                return NULL;
        }

        return thread;
}

spinlock을 만든 후 초기화 하지 않으면, 사용할 수 없다. kernel에 메모리를 할당 받으면 초기화는 꼭 해줘야 하는 느낌이다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>     //GPIO


#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>


#define GPIO_10_OUT (10)
#define DEVICE_DATA_MAX 256

#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/spinlock.h>


unsigned int GPIO_irqNumber;

/*사용자 데이터 부분*/
struct my_device_data{
	struct cdev cdev;
	int index;
	char my_string[DEVICE_DATA_MAX];
	struct timer_list simple_timer;
	spinlock_t lock;
	struct task_struct *kthread1;
	struct task_struct *kthread2;
} ;

int run_this_thread1(void* data);
int run_this_thread2(void* data);

int run_this_thread1(void* data)
{
	//while(1)로 하면 kthread_stop을 할 수 없음.
	//루프가 없으면 kthread_stop을 불렀을 경우, segment error.
	//thread가 없다면, 종료를 할 수 없어 보임..
	while(!kthread_should_stop())
	{
	//data is my_device_data
	struct my_device_data *ptr_main_data;
	//ptr_main_data = data;
	ptr_main_data = (struct my_device_data(*))data;

	//공통이 구조체 접근.
	spin_lock(&ptr_main_data->lock);
	ptr_main_data->index++;
	//pr_info("spin lock is %0x\n",ptr_main_data->lock);
	pr_info("==============\n");
	pr_info("[+]index is %d\n", ptr_main_data->index);
	pr_info("==============\n");
	spin_unlock(&ptr_main_data->lock);
	//pr_info("spin lock is %0x\n",ptr_main_data->lock);

	msleep(500);
	}
	return 0;

}

int run_this_thread2(void* data)
{
	while(!kthread_should_stop())
	{
	//data is my_device_data
	struct my_device_data *ptr_main_data;
	ptr_main_data = (struct my_device_data(*))data;
	//
	//공통이 구조체 접근.
	spin_lock(&ptr_main_data->lock);
	ptr_main_data->index++;
	pr_info("==============\n");
	pr_info("[+]index is %d\n", ptr_main_data->index);
	pr_info("==============\n");
	spin_unlock(&ptr_main_data->lock);
	msleep(500);
	}

	return 0;

}

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	/*Scheduling work queue*/
	return IRQ_HANDLED;

}


//device driver 작성 부분.
/*************드라이버 함수 ******************/
static int mydriver_open(struct inode *inode, struct file *file);
static int mydriver_release(struct inode *inode, struct file *file);
static ssize_t mydriver_read(struct file *flip,
		char *buf, size_t len, loff_t *off);
static ssize_t mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off);
/********************************************/



//file operation structure

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.read = mydriver_read,
	.write = mydriver_write,
	.open = mydriver_open,
	.release = mydriver_release,
};

static int mydriver_open(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was opend.\n");
	return 0;

}

static int mydriver_release(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was closed.\n");
	return 0;
}


static int mydriver_read(struct file *file,
		char *buf, size_t len, loff_t *off)
{
	pr_info("read\n");
	return 0;
}


static int mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off)
{
	return 0;

}

dev_t dev = 0;
static struct cdev my_cdev;
static struct class *dev_class;
struct my_device_data *main_data;
static int __init init_hw(void)
{
	//디바이스 등록
	if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0))
	{
		pr_err("[!]character device was not allocated\n");
		goto r_unreg;

	}
	pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev));


	//초기화
	cdev_init(&my_cdev, &fops);
	pr_info("[=]driver was initialized\n");


	//시스템에 추가
	if((cdev_add(&my_cdev, dev, 1)) < 0)
	{
		pr_err("[!]cannot add device to kernel\n");
		goto r_del;

	}


	//class 만듦.
	if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL)
	{
		pr_err("[!]cannot add class\n");
		goto r_class;
	}


	if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL)
	{

		pr_err("[!]cannot create device\n");
		goto r_device;
	}


	//gpio 10번을 사용.
	//export하여 간단히 사용.
	//입력은 값을 써 넣을 수 없음. 출력으로 설정.
	GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT);
	pr_info("[=]irq %d was assinged\n",GPIO_irqNumber);

	//interrupt 등록 필요
	if (request_irq(GPIO_irqNumber,
				(void*)gpio_irq_handler,
				IRQF_TRIGGER_RISING,
				"my_device",
				NULL))
	{
		pr_err("[!]my_device: cannot register IRQ\n");
		goto r_gpio;
	}
	pr_info("[=]module was installed\n");


	//메모리 공간 할당.
	main_data = kmalloc(sizeof(struct my_device_data), GFP_KERNEL);


	//spin lock init
	spin_lock_init(&main_data->lock);
	main_data->index=0;
	if(!main_data)
	{
		pr_err("[!]cannot alloc memory\n");
		goto r_memory;
	}
	pr_info("[=]got memory\n");

	//thread create.
	main_data->kthread1 = kthread_create(run_this_thread1, main_data, "my_thread1");
	main_data->kthread2 = kthread_create(run_this_thread2, main_data, "my_thread2");

	if(main_data->kthread1)
	{
		wake_up_process(main_data->kthread1);
		pr_info("wake up thread1 at %p\n", main_data->kthread1);
	}
	if(main_data->kthread2)
	{
		wake_up_process(main_data->kthread2);
		pr_info("wake up thread2 at %p\n", main_data->kthread2);
	}

	return 0;
r_gpio:
	gpio_free(GPIO_10_OUT);
r_device:
	device_destroy(dev_class,dev);

r_class:
	class_destroy(dev_class);
r_del:
	cdev_del(&my_cdev);

r_unreg:
	unregister_chrdev_region(dev,1);
r_memory:
	;

	return -1;
}

static void __exit exit_hw(void) {
	free_irq(GPIO_irqNumber, NULL);
	gpio_free(GPIO_10_OUT);
	//flush_work(struct work_struct *work);
	device_destroy(dev_class,dev);
	//class_unregister(dev_class);
	class_destroy(dev_class);
	cdev_del(&my_cdev);
	unregister_chrdev_region(dev,1);
	if(main_data->kthread1)
		kthread_stop(main_data->kthread1);
	if(main_data->kthread2)
		kthread_stop(main_data->kthread2);
	pr_info("kthread was stopped\n");
	pr_info("wake up thread1 at %p\n", main_data->kthread1);
	pr_info("wake up thread2 at %p\n", main_data->kthread2);


	kfree(main_data);
	printk(KERN_INFO "module was removed\n");
}


module_init(init_hw);
module_exit(exit_hw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("noname");
MODULE_DESCRIPTION("Hello, world!");

spin lock을 적용하지 않을 경우, 겹쳐쓴다. 적용하면 순서대로 실행된다 믿는다.

[ 1749.848372] ==============
[ 1749.848376] [+]index is 108
[ 1749.848380] ==============
[ 1750.378381] ==============
[ 1750.378392] [+]index is 109
[ 1750.378399] ==============
[ 1750.378414] ==============
[ 1750.378421] [+]index is 110
[ 1750.378428] ==============
[ 1750.898416] ==============
[ 1750.898431] [+]index is 111
[ 1750.898439] ==============
[ 1750.898461] ==============
[ 1750.898470] [+]index is 112
[ 1750.898478] ==============
[ 1751.418404] ==============
[ 1751.418417] [+]index is 113
[ 1751.418426] ==============
[ 1751.418446] ==============
[ 1751.418455] [+]index is 114
[ 1751.418463] ==============
[ 1751.938485] ==============
[ 1751.938501] [+]index is 115
[ 1751.938512] ==============
[ 1751.938537] ==============
[ 1751.938547] [+]index is 116
[ 1751.938556] ==============
[ 1752.458509] ==============
[ 1752.458529] [+]index is 117
[ 1752.458538] ==============
[ 1752.458560] ==============
[ 1752.458570] [+]index is 118
[ 1752.458578] ==============
[ 1752.978388] ==============
[ 1752.978394] [+]index is 119
[ 1752.978398] ==============
[ 1753.498497] kthread was stopped
[ 1753.498515] wake up thread1 at 39208d2a
[ 1753.498524] wake up thread2 at 5bdf5278
[ 1753.498536] module was removed
[ 1773.404079] [=]236-0, was allocated
[ 1773.404089] [=]driver was initialized
[ 1773.406890] [=]irq 54 was assinged
[ 1773.406942] [=]module was installed
[ 1773.406952] [=]got memory
[ 1773.410837] wake up thread1 at 6acc0d97
[ 1773.410859] wake up thread2 at 9613521a
[ 1773.411132] ==============
[ 1773.411143] [+]index is 1
[ 1773.411151] ==============
[ 1773.411794] ==============
[ 1773.411805] [+]index is 2
[ 1773.411812] ==============
[ 1773.528622] ==============
[ 1773.528626] ==============
[ 1773.528636] [+]index is 4
[ 1773.528637] [+]index is 4
[ 1773.528640] ==============
[ 1773.528641] ==============
[ 1773.648584] ==============
[ 1773.648585] ==============
[ 1773.648589] [+]index is 6
[ 1773.648590] [+]index is 6
[ 1773.648593] ==============
[ 1773.648594] ==============
[ 1773.768583] ==============
[ 1773.768584] ==============
[ 1773.768588] [+]index is 8
[ 1773.768589] [+]index is 8
[ 1773.768591] ==============
[ 1773.768594] ==============
[ 1773.888583] ==============
[ 1773.888587] [+]index is 9
[ 1773.888589] ==============
[ 1773.888591] ==============
[ 1773.888595] [+]index is 10
[ 1773.888598] ==============
[ 1774.008583] ==============
[ 1774.008585] ==============
[ 1774.008588] [+]index is 12
[ 1774.008591] [+]index is 12
[ 1774.008594] ==============
[ 1774.008596] ==============
[ 1774.128592] ==============
[ 1774.128593] ==============
[ 1774.128597] [+]index is 14
[ 1774.128599] [+]index is 14
[ 1774.128601] ==============
[ 1774.128604] ==============
[ 1774.248635] ==============
[ 1774.248638] ==============
[ 1774.248648] [+]index is 16
[ 1774.248652] [+]index is 16
[ 1774.248657] ==============
[ 1774.248663] ==============
[ 1774.368631] ==============
[ 1774.368642] [+]index is 17
[ 1774.368649] ==============
[ 1774.378613] ==============
[ 1774.378622] [+]index is 18
https://embetronicx.com/tutorials/linux/device-drivers/linux-device-drivers-tutorial-kernel-thread/

https://embetronicx.com/tutorials/linux/device-drivers/spinlock-in-linux-kernel-1/

https://embetronicx.com/tutorials/linux/device-drivers/linux-device-driver-tutorial-mutex-in-linux-kernel/

timer 실습

kernel timer를 실습했다. callback 함수 argrument로 timer를 가지고 있는 구조체 데이터를 전달할 수 있다. work queue와 같은 방식이다. 커널 특정 버전부터 이런 식으로 변경되었는 듯 하다. 타이머가 만료되면 다시 등록하도록 했다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>     //GPIO


#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>

#include <linux/timer.h>
#include <linux/jiffies.h>

#define GPIO_10_OUT (10)
#define DEVICE_DATA_MAX 256

unsigned int GPIO_irqNumber;

/*\uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \ubd80\ubd84*/
static struct my_device_data{
	struct cdev cdev;
	int index;
	char my_string[DEVICE_DATA_MAX];
	struct timer_list simple_timer;
} my_data1;


void timer_action_fn(struct timer_list *t);

void timer_action_fn(struct timer_list *t)
{
	struct my_device_data *ptr1;
	pr_info("%ld: timer function was activated\n", jiffies);
	ptr1=from_timer(ptr1, t, simple_timer);
	ptr1->index++;
	pr_info("index is %d\n", ptr1->index);
	mod_timer(&my_data1.simple_timer, jiffies+msecs_to_jiffies(1000));


}


static int init_timer(void){
	timer_setup(&my_data1.simple_timer, timer_action_fn, 0);
	pr_info("%ld: timer was setup\n", jiffies);
	mod_timer(&my_data1.simple_timer, jiffies+msecs_to_jiffies(1000));
	return 0;

}

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	/*Scheduling work queue*/
	return IRQ_HANDLED;

}


//device driver \uc791\uc131 \ubd80\ubd84.
/*************\ub4dc\ub77c\uc774\ubc84 \ud568\uc218 ******************/
static int mydriver_open(struct inode *inode, struct file *file);
static int mydriver_release(struct inode *inode, struct file *file);
static ssize_t mydriver_read(struct file *flip,
		char *buf, size_t len, loff_t *off);
static ssize_t mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off);
/********************************************/



//file operation structure

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.read = mydriver_read,
	.write = mydriver_write,
	.open = mydriver_open,
	.release = mydriver_release,
};

static int mydriver_open(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was opend.\n");
	return 0;

}

static int mydriver_release(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was closed.\n");
	return 0;
}


static int mydriver_read(struct file *file,
		char *buf, size_t len, loff_t *off)
{
	pr_info("read\n");
	return 0;
}


static int mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off)
{
	return 0;

}

dev_t dev = 0;
static struct cdev my_cdev;
static struct class *dev_class;
static int __init init_hw(void)
{
	//\ub514\ubc14\uc774\uc2a4 \ub4f1\ub85d
	if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0))
	{
		pr_err("[!]character device was not allocated\n");
		goto r_unreg;

	}
	pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev));


	//\ucd08\uae30\ud654
	cdev_init(&my_cdev, &fops);
	pr_info("[=]driver was initialized\n");


	//\uc2dc\uc2a4\ud15c\uc5d0 \ucd94\uac00
	if((cdev_add(&my_cdev, dev, 1)) < 0)
	{
		pr_err("[!]cannot add device to kernel\n");
		goto r_del;

	}


	//class \ub9cc\ub4e6.
	if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL)
	{
		pr_err("[!]cannot add class\n");
		goto r_class;
	}


	if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL)
	{

		pr_err("[!]cannot create device\n");
		goto r_device;
	}


	//gpio 10\ubc88\uc744 \uc0ac\uc6a9.
	//export\ud558\uc5ec \uac04\ub2e8\ud788 \uc0ac\uc6a9.
	//\uc785\ub825\uc740 \uac12\uc744 \uc368 \ub123\uc744 \uc218 \uc5c6\uc74c. \ucd9c\ub825\uc73c\ub85c \uc124\uc815.
	GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT);
	pr_info("[=]irq %d was assinged\n",GPIO_irqNumber);

	//interrupt \ub4f1\ub85d \ud544\uc694
	if (request_irq(GPIO_irqNumber,
				(void*)gpio_irq_handler,
				IRQF_TRIGGER_RISING,
				"my_device",
				NULL))
	{
		pr_err("[!]my_device: cannot register IRQ\n");
		goto r_gpio;
	}
	pr_info("[=]module was installed\n");

	//timer setup
	init_timer();
	return 0;
r_gpio:
	gpio_free(GPIO_10_OUT);
r_device:
	device_destroy(dev_class,dev);

r_class:
	class_destroy(dev_class);
r_del:
	cdev_del(&my_cdev);

r_unreg:
	unregister_chrdev_region(dev,1);

	return -1;
}

static void __exit exit_hw(void) {
	free_irq(GPIO_irqNumber, NULL);
	gpio_free(GPIO_10_OUT);
	//flush_work(struct work_struct *work);
	device_destroy(dev_class,dev);
	//class_unregister(dev_class);
	class_destroy(dev_class);
	cdev_del(&my_cdev);
	unregister_chrdev_region(dev,1);
	//timer delete
	del_timer(&my_data1.simple_timer);
	printk(KERN_INFO "module was removed\n");
}


module_init(init_hw);
module_exit(exit_hw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("noname");
MODULE_DESCRIPTION("Hello, world!");
[146685.757495] [=]236-0, was allocated
[146685.757504] [=]driver was initialized
[146685.757960] [=]irq 54 was assinged
[146685.757984] [=]module was installed
[146685.757991] 14638325: timer was setup
[146686.818385] 14638432: timer function was activated
[146686.818398] index is 1
[146687.858398] 14638536: timer function was activated
[146687.858402] index is 2
[146688.898415] 14638640: timer function was activated
[146688.898428] index is 3
[146689.938432] 14638744: timer function was activated
[146689.938445] index is 4
[146690.978452] 14638848: timer function was activated
[146690.978457] index is 5
[146692.018469] 14638952: timer function was activated
[146692.018483] index is 6
[146693.058494] 14639056: timer function was activated
[146693.058500] index is 7
[146694.098512] 14639160: timer function was activated
[146694.098548] index is 8
[146695.138529] 14639264: timer function was activated
[146695.138563] index is 9
[146696.178545] 14639368: timer function was activated
[146696.178573] index is 10
[146697.218575] 14639472: timer function was activated
[146697.218607] index is 11
[146698.258571] 14639576: timer function was activated
[146698.258587] index is 12

https://embetronicx.com/tutorials/linux/device-drivers/using-kernel-timer-in-linux-device-driver/

https://stackoverflow.com/questions/14953871/how-to-pass-custom-argument-to-linux-timers-function

workqueue 실습

리눅스가 work queue를 다양하게 사용한다. 나는 초짜라 DECLARE_WORK와 INIT_WORK를 구분할 수 없었다. DECLARE_WORK가 work struct를 전역 변수로 선언한다. 여러 work로 같은 데이터에 접근할 수 있다. workqueue funtion() 파라미터로 work struct를 넣는데, 여기로 work를 전달하면 된다. 코드가 넝마조각이 되고 있다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>     //GPIO


#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>


#include <linux/workqueue.h>

#define GPIO_10_OUT (10)
#define DEVICE_DATA_MAX 256

unsigned int GPIO_irqNumber;

/*\uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \ubd80\ubd84*/
static struct my_device_data{
	struct cdev cdev;
	int index;
	char my_string[DEVICE_DATA_MAX];
	struct work_struct mywork;
	struct work_struct work_read;
} my_data_global;

struct my_device_data *work_ptr;
struct my_device_data *work_ptr_read;
struct my_device_data *my_data;

/*workque initiate*/

void workqueue_fn(struct work_struct *work);

//defien workqueue_fn//

void workqueue_fn(struct work_struct *work_ptr)
{
	/*work_ptr\ub85c \uc0ac\uc6a9\uc790 \uc815\uc758 \ub370\uc774\ud130 \uc811\uadfc*/
	struct my_device_data *my_pointer;
	//container of \ub85c \uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \uc811\uadfc.
	my_pointer = container_of(work_ptr, struct my_device_data, mywork);
	my_pointer->index++;
	pr_info("Executing workqueue function\n");
	pr_info("index is %d\n",my_pointer->index);

}


void workqueue_read(struct work_struct *work_ptr)
{
	/*work_ptr\ub85c \uc0ac\uc6a9\uc790 \uc815\uc758 \ub370\uc774\ud130 \uc811\uadfc*/
	struct my_device_data *my_pointer;
	//container of \ub85c \uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \uc811\uadfc.
	my_pointer = container_of(work_ptr, struct my_device_data, work_read);
	my_pointer->index++;
	pr_info("opened, index is %d\n", my_pointer->index);

}

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	/*Scheduling work queue*/
	schedule_work(&work_ptr->mywork);
	pr_info("workqueue scheduled \n");
	return IRQ_HANDLED;

}


//device driver \uc791\uc131 \ubd80\ubd84.
/*************\ub4dc\ub77c\uc774\ubc84 \ud568\uc218 ******************/
static int mydriver_open(struct inode *inode, struct file *file);
static int mydriver_release(struct inode *inode, struct file *file);
static ssize_t mydriver_read(struct file *flip,
		char *buf, size_t len, loff_t *off);
static ssize_t mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off);
/********************************************/



//file operation structure

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.read = mydriver_read,
	.write = mydriver_write,
	.open = mydriver_open,
	.release = mydriver_release,
};

static int mydriver_open(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was opend.\n");
	//my_data_global.index++;
	
	return 0;

}

static int mydriver_release(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was closed.\n");
	return 0;
}


static int mydriver_read(struct file *file,
		char *buf, size_t len, loff_t *off)
{
	struct my_device_data *my_data;
	size_t datalen, read_len;
	my_data = (struct my_device_data*)&my_data_global;
	datalen = strlen(my_data->my_string);
	//\ucd5c\ub300\uac12\uc73c\ub85c \uac15\uc81c \uc124\uc815
	if(len > datalen)
	{
		len = datalen;
	}
	read_len = len - *off;
	if (read_len <= 0)
		return 0;
	pr_info("start %p, offset is %lld, read_len is %d\n", my_data->my_string, *off, read_len);
	if(copy_to_user(buf, my_data->my_string+*off, read_len))
		return -EFAULT;
	//read_len = datalen-*off;
	*off += read_len;
	pr_info("kernel has %d, read %d characters from kernel\n", datalen, read_len);
	//zero\ub97c \ubc18\ud658\ud560 \ub54c\uae4c\uc9c0 \ubc18\ubcf5.
	schedule_work(&work_ptr->work_read);
	return read_len;
}

static int mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off)
{
	size_t datalen, write_len, start_pos;
	my_data = &my_data_global;
	start_pos = strlen(my_data->my_string);
	datalen = strlen(my_data->my_string);
	if(len > datalen)
	{
		len = DEVICE_DATA_MAX;
	}

	write_len = len - *off;

	if(copy_from_user(my_data->my_string + start_pos + *off, buf, write_len))
		return -EFAULT;
	*off += write_len;
	return write_len;
}

dev_t dev = 0;
static struct cdev my_cdev;
static struct class *dev_class;
static int __init init_hw(void)
{
	//\ub514\ubc14\uc774\uc2a4 \ub4f1\ub85d
	if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0))
	{
		pr_err("[!]character device was not allocated\n");
		goto r_unreg;

	}
	pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev));


	//\ucd08\uae30\ud654
	cdev_init(&my_cdev, &fops);
	pr_info("[=]driver was initialized\n");


	//\uc2dc\uc2a4\ud15c\uc5d0 \ucd94\uac00
	if((cdev_add(&my_cdev, dev, 1)) < 0)
	{
		pr_err("[!]cannot add device to kernel\n");
		goto r_del;

	}


	//class \ub9cc\ub4e6.
	if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL)
	{
		pr_err("[!]cannot add class\n");
		goto r_class;
	}


	if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL)
	{

		pr_err("[!]cannot create device\n");
		goto r_device;
	}
	work_ptr = kmalloc(sizeof(*work_ptr), GFP_KERNEL);
	if(work_ptr == NULL)
	{
		pr_err("[!]cannot allocate memory\n");
		goto r_work;
	}
	INIT_WORK(&work_ptr->mywork, workqueue_fn);



	//work_ptr_read = kmalloc(sizeof(*work_ptr), GFP_KERNEL);
	INIT_WORK(&work_ptr->work_read, workqueue_read);

	//gpio 10\ubc88\uc744 \uc0ac\uc6a9.
	//export\ud558\uc5ec \uac04\ub2e8\ud788 \uc0ac\uc6a9.
	//\uc785\ub825\uc740 \uac12\uc744 \uc368 \ub123\uc744 \uc218 \uc5c6\uc74c. \ucd9c\ub825\uc73c\ub85c \uc124\uc815.
	GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT);
	pr_info("[=]irq %d was assinged\n",GPIO_irqNumber);

	//interrupt \ub4f1\ub85d \ud544\uc694
	if (request_irq(GPIO_irqNumber,
				(void*)gpio_irq_handler,
				IRQF_TRIGGER_RISING,
				"my_device",
				NULL))
	{
		pr_err("[!]my_device: cannot register IRQ\n");
		goto r_gpio;
	}
	pr_info("[=]module was installed\n");
	return 0;
r_gpio:
	gpio_free(GPIO_10_OUT);
r_device:
	device_destroy(dev_class,dev);

r_class:
	class_destroy(dev_class);
r_del:
	cdev_del(&my_cdev);

r_unreg:
	unregister_chrdev_region(dev,1);
r_work:
	kfree(work_ptr);

	return -1;
}

static void __exit exit_hw(void) {
	free_irq(GPIO_irqNumber, NULL);
	gpio_free(GPIO_10_OUT);
	//flush_work(struct work_struct *work);
	device_destroy(dev_class,dev);
	//class_unregister(dev_class);
	class_destroy(dev_class);
	cdev_del(&my_cdev);
	unregister_chrdev_region(dev,1);
	flush_work(&work_ptr->mywork);
	kfree(work_ptr);
	printk(KERN_INFO "module was removed\n");
}


module_init(init_hw);
module_exit(exit_hw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("noname");
MODULE_DESCRIPTION("Hello, world!");

모듈을 로딩하고 인터럽트를 만들면 schedule_work로 등록한 workqueue_fn을 실행한다.

[   52.456926] [=]236-0, was allocated
[   52.456935] [=]driver was initialized
[   52.457621] [=]irq 54 was assinged
[   52.457693] [=]module was installed
[   68.542830] workqueue scheduled 
[   68.542855] Executing workqueue function
[   68.542866] index is 1
[   69.549436] workqueue scheduled 
[   69.549460] Executing workqueue function
[   69.549466] index is 2
[   70.552638] workqueue scheduled 
[   70.552659] Executing workqueue function
[   70.552663] index is 3

파일을 오픈하면 같은 데이터를 다른 work로 index를 접근할 수 있다. schedule_work(&work_ptr->work_read)를 디바이스를 읽을 때마다 실행한다.

[   86.495196] Deviced file was opend.
[   86.495242] Deviced file was closed.
[   91.476276] Deviced file was opend.
[   91.476318] start 9f3ac859, offset is 0, read_len is 5
[   91.476327] kernel has 5, read 5 characters from kernel
[   91.476367] opened, index is 4
[   91.476430] Deviced file was closed.
[  105.664089] Deviced file was opend.
[  105.664138] start 9f3ac859, offset is 0, read_len is 5
[  105.664149] kernel has 5, read 5 characters from kernel
[  105.664197] opened, index is 5
[  105.664275] Deviced file was closed.

https://www.programmersought.com/article/61671813037/

https://stackoverflow.com/questions/7937245/how-to-use-linux-work-queue/7938990

https://github.com/fervagar/kernel_modules/blob/master/workQueue.c

character device를 tasklet으로 테스트

tasklet을 배우고 실습하다 보니, 캐릭터 디바이스를 활용한 예제를 찾았다. 하다보니 강제로 character device를 등록, 열고, 닫고, 읽고, 쓰는 방법을 배웠다. 대충 이해한 다음 아래 동작을 구성했다.

  • read: 앞쪽부터 읽음.
  • write: 데이터가 있다면 뒤로 채워 넣음.
  • 인터럽트: gpio를 시물레이션하여 앞에서 한 글자씩 지움.
  • 버퍼 용량은 char 256개 사용.

전에 사용한 파일을 수정했고, 모듈을 만들기 위한 makefile을 그대로 사용했다.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>     //GPIO


#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>

#define GPIO_10_OUT (10)
#define DEVICE_DATA_MAX 256

unsigned int GPIO_irqNumber;

void my_tasklet_fn(unsigned long); 

/*\uc0ac\uc6a9\uc790 \ub370\uc774\ud130 \ubd80\ubd84*/
static struct my_device_data{
	struct cdev cdev;
	int index;
	char my_string[DEVICE_DATA_MAX];
}my_data_global;

void my_tasklet_fn(unsigned long data) 
{
	/*taskelet \ubc1c\uc0dd\ud558\uba74 \uc55e\uc5d0\uc11c\ubd80\ud130 1\uac1c \ubb38\uc790 \uc0ad\uc81c*/
	struct my_device_data *my_data;
	int i;
	/*address\ub85c data\uac00 \ub118\uc5b4\uc624\uae30 \ub54c\ubb38\uc5d0 \ud3ec\uc778\ud130\ub85c casting*/
	my_data = (struct my_device_data*)data;
	//\uc55e\uc5d0\uc11c\ubd80\ud130 \ud55c \ubb38\uc790 \uc0ad\uc81c
	for (i=0;i<DEVICE_DATA_MAX;i++)
	{
		my_data->my_string[i]=my_data->my_string[i+1];

	}
	my_data->my_string[(DEVICE_DATA_MAX-1)]=(char)0;
	pr_info("taskelet: deleted 1st character.\n");

}

/* Init the Tasklet by Static Method */
DECLARE_TASKLET(my_tasklet,my_tasklet_fn, (unsigned long)&my_data_global);


static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	/*Scheduling Task to Tasklet*/
        tasklet_schedule(&my_tasklet); 
	//pr_info("interrupt occured\n");
	return IRQ_HANDLED;

}


//device driver \uc791\uc131 \ubd80\ubd84.
/*************\ub4dc\ub77c\uc774\ubc84 \ud568\uc218 ******************/
static int mydriver_open(struct inode *inode, struct file *file);
static int mydriver_release(struct inode *inode, struct file *file);
static ssize_t mydriver_read(struct file *flip,
		char *buf, size_t len, loff_t *off);
static ssize_t mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off);
/********************************************/



//file operation structure

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.read = mydriver_read,
	.write = mydriver_write,
	.open = mydriver_open,
	.release = mydriver_release,
};

static int mydriver_open(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was opend.\n");
	return 0;

}

static int mydriver_release(struct inode *inode, struct file *file)
{
	pr_info("Deviced file was closed.\n");
	return 0;
}


static int mydriver_read(struct file *file,
		char *buf, size_t len, loff_t *off)
{
	struct my_device_data *my_data;
	size_t datalen, read_len;
	my_data = (struct my_device_data*)&my_data_global;
	datalen = strlen(my_data->my_string);
	//\ucd5c\ub300\uac12\uc73c\ub85c \uac15\uc81c \uc124\uc815
	if(len > datalen)
	{
		len = datalen;
	}
	read_len = len - *off;
	if (read_len <= 0)
		return 0;
	pr_info("start %p, offset is %lld, read_len is %d\n", my_data->my_string, *off, read_len);
	if(copy_to_user(buf, my_data->my_string+*off, read_len))
		return -EFAULT;
	//read_len = datalen-*off;
	*off += read_len;
	pr_info("kernel has %d, read %d characters from kernel\n", datalen, read_len);
	//zero\ub97c \ubc18\ud658\ud560 \ub54c\uae4c\uc9c0 \ubc18\ubcf5.
	return read_len;
}

static int mydriver_write(struct file *flip,
		const char *buf, size_t len, loff_t *off)
{
	struct my_device_data *my_data;
	size_t datalen, write_len, start_pos;
	my_data = &my_data_global;
	start_pos = strlen(my_data->my_string);
	datalen = strlen(my_data->my_string);
	if(len > datalen)
	{
		len = DEVICE_DATA_MAX;
	}

	write_len = len - *off;

	if(copy_from_user(my_data->my_string + start_pos + *off, buf, write_len))
		return -EFAULT;
	*off += write_len;
	return write_len;
}

dev_t dev = 0;
static struct cdev my_cdev;
static struct class *dev_class;

static int __init init_hw(void)
{
	//\ub514\ubc14\uc774\uc2a4 \ub4f1\ub85d
	if(( alloc_chrdev_region(&dev, 0, 1, "test_device") < 0))
	{
		pr_err("[!]character device was not allocated\n");
		goto r_unreg;

	}
	pr_info("[=]%d-%d, was allocated\n", MAJOR(dev), MINOR(dev));


	//\ucd08\uae30\ud654
	cdev_init(&my_cdev, &fops);
	pr_info("[=]driver was initialized\n");


	//\uc2dc\uc2a4\ud15c\uc5d0 \ucd94\uac00
	if((cdev_add(&my_cdev, dev, 1)) < 0)
	{
		pr_err("[!]cannot add device to kernel\n");
		goto r_del;

	}


	//class \ub9cc\ub4e6.
	if((dev_class=class_create(THIS_MODULE, "my_class")) == NULL)
	{
		pr_err("[!]cannot add class\n");
		goto r_class;
	}


	if((device_create(dev_class, NULL, dev, NULL, "my_device")) == NULL)
	{

		pr_err("[!]cannot create device\n");
		goto r_device;
	}

	//gpio 10\ubc88\uc744 \uc0ac\uc6a9.
	//export\ud558\uc5ec \uac04\ub2e8\ud788 \uc0ac\uc6a9.
	//\uc785\ub825\uc740 \uac12\uc744 \uc368 \ub123\uc744 \uc218 \uc5c6\uc74c. \ucd9c\ub825\uc73c\ub85c \uc124\uc815.
	GPIO_irqNumber = gpio_to_irq(GPIO_10_OUT);
	pr_info("[=]irq %d was assinged\n",GPIO_irqNumber);

	//interrupt \ub4f1\ub85d \ud544\uc694
	if (request_irq(GPIO_irqNumber,
				(void*)gpio_irq_handler,
				IRQF_TRIGGER_RISING,
				"my_device",
				NULL))
	{
		pr_err("[!]my_device: cannot register IRQ\n");
		goto r_gpio;
	}
	pr_info("[=]module was installed\n");
	return 0;
r_gpio:
	gpio_free(GPIO_10_OUT);
r_device:
	device_destroy(dev_class,dev);

r_class:
	class_destroy(dev_class);
r_del:
	cdev_del(&my_cdev);
r_unreg:
	unregister_chrdev_region(dev,1);

	return -1;
}

static void __exit exit_hw(void) {
	free_irq(GPIO_irqNumber, NULL);
	gpio_free(GPIO_10_OUT);
	device_destroy(dev_class,dev);
	//class_unregister(dev_class);
	class_destroy(dev_class);
	cdev_del(&my_cdev);
	unregister_chrdev_region(dev,1);
	printk(KERN_INFO "module was removed\n");
}


module_init(init_hw);
module_exit(exit_hw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eunseong Park (esp-ark.com)");
MODULE_DESCRIPTION("Hello, world!");

make 후 모듈을 로딩한다. 캐릭터 디바이스가 236, 0으로 등록되었다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ sudo insmod my_driver.ko 
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ lsmod | grep my_driver
my_driver              16384  0
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -4
[42918.717112] [=]236-0, was allocated
[42918.717123] [=]driver was initialized
[42918.717332] [=]irq 54 was assinged
[42918.717363] [=]module was installed
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ ls /dev/my_device -l
crw------- 1 root root 236, 0  7\uc6d4 14 12:45 /dev/my_device

쓰기를 위해 권한을 부여했다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ sudo chmod 666 /dev/my_device ;ls -l /dev/my_device 
crw-rw-rw- 1 root root 236, 0  7\uc6d4 14 12:45 /dev/my_device

로딩 후 읽어보면 아무런 데이터를 읽을 수 없다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ head -c10 /dev/my_device
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -2
[43123.405028] Deviced file was opend.
[43123.405091] Deviced file was closed.

원하는 데이터를 디바이스에 기록한다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ echo "I need more space to delete characters" > /dev/my_device 
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ echo "I need more space to delete characters" > /dev/my_device

내용을 확인한다. 최대 길이를 넘기면 잘못 읽는데 어떻게 해결할 지 모르겠다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ head -c100 /dev/my_device 
I need more space to delete characters
I need more space to delete characters
head: '/dev/my_device를 읽는 도중 오류 발생: 주소가 잘못됨
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -10
[43264.763161] Deviced file was closed.
[43309.683908] Deviced file was opend.
[43309.683961] start 18f123ee, offset is 0, read_len is 10
[43309.683972] kernel has 78, read 10 characters from kernel
[43309.684006] Deviced file was closed.
[43316.934115] Deviced file was opend.
[43316.934165] start 18f123ee, offset is 0, read_len is 78
[43316.934183] kernel has 78, read 78 characters from kernel
[43316.934260] start 18f123ee, offset is 78, read_len is -56
[43316.934813] Deviced file was closed.

인터럽트를 설정 한 다음 발생시킨다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ sudo ./config_gpio.sh 
1
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ ./make_gpio.sh 
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ cat config_gpio.sh 
#!/bin/bash
echo "10" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio10/direction 
gpio read 10
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ cat make_gpio.sh 
#!/bin/bash
#echo "10" > /sys/class/gpio/export
#sleep(1)
#echo "out" > /sys/class/gpio/gpio10/direction 
#sleep(1)
#gpio read 10
#sleep(1)
echo 0 > /sys/class/gpio/gpio10/value ; echo 1 > /sys/class/gpio/gpio10/value;
sleep 1
echo 0 > /sys/class/gpio/gpio10/value ; echo 1 > /sys/class/gpio/gpio10/value;
sleep 1
echo 0 > /sys/class/gpio/gpio10/value ; echo 1 > /sys/class/gpio/gpio10/value;
pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ dmesg | tail -10
[43309.683972] kernel has 78, read 10 characters from kernel
[43309.684006] Deviced file was closed.
[43316.934115] Deviced file was opend.
[43316.934165] start 18f123ee, offset is 0, read_len is 78
[43316.934183] kernel has 78, read 78 characters from kernel
[43316.934260] start 18f123ee, offset is 78, read_len is -56
[43316.934813] Deviced file was closed.
[43694.677107] taskelet: deleted 1st character.
[43695.686488] taskelet: deleted 1st character.
[43696.693794] taskelet: deleted 1st character.

다시 읽어본다. 앞 캐릭터 3개가 지워졌다.

pi@raspberrypi:~/RaspberryDebug/my_driver_tasklet $ head -c100 /dev/my_device 
eed more space to delete characters
I need more space to delete characters
head: '/dev/my_device를 읽는 도중 오류 발생: 주소가 잘못됨

https://olegkutkov.me/2018/03/14/simple-linux-character-device-driver/

https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html

https://stackoverflow.com/questions/1330284/how-might-i-learn-to-write-char-device-drivers-for-linux

https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch03s04.html

https://pr0gr4m.tistory.com/entry/Linux-Kernel-5-Character-Device-Driver

http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/

https://stackoverflow.com/questions/12124628/endlessly-looping-when-reading-from-character-device