비동기 알림 구현 및 활용

  1. 비동기 알림 개요 ======

1.1 비동기 알림 소개

블로킹 및 논블로킹 방식 모두 애플리케이션이 장치 상태를 주로 확인해야 합니다.

비동기 알림은 드라이버가 접근 가능하다고 보고할 수 있으며, 애플리케이션이 신호를 수신한 후 드라이버 장치에서 데이터를 읽거나 쓰는 방식과 유사합니다.

비동기 알림의 핵심은 신호입니다:

#define SIGHUP 1 /* 터미널 중단 또는 제어 프로세스 종료 */
#define SIGINT 2 /* 터미널 중단(Ctrl+C 조합) */
#define SIGQUIT 3 /* 터미널 종료(Ctrl+\ 조합) */
#define SIGILL 4 /* 불법 명령 */
#define SIGTRAP 5 /* 디버깅 사용, 중단점 명령 생성 */
#define SIGABRT 6 /* abort(3)에서 발생한 종료 명령 */
#define SIGIOT 6 /* IOT 명령 */
#define SIGBUS 7 /* 버스 오류 */
#define SIGFPE 8 /* 부동소수점 연산 오류 */
#define SIGKILL 9 /* 프로세스 강제 종료 */
#define SIGUSR1 10 /* 사용자 정의 신호 1 */
#define SIGSEGV 11 /* 세그먼트 위반(잘못된 메모리 세그먼트) */
#define SIGUSR2 12 /* 사용자 정의 신호 2 */
#define SIGPIPE 13 /* 읽기 전용 파이프에 데이터 쓰기 */
#define SIGALRM 14 /* 알람 */
#define SIGTERM 15 /* 소프트웨어 종료 */
#define SIGSTKFLT 16 /* 스택 예외 */
#define SIGCHLD 17 /* 자식 프로세스 종료 */
#define SIGCONT 18 /* 프로세스 계속 */
#define SIGSTOP 19 /* 프로세스 실행 중단(일시정지) */
#define SIGTSTP 20 /* 프로세스 실행 중지(Ctrl+Z 조합) */
#define SIGTTIN 21 /* 백그라운드 프로세스가 터미널에서 데이터 읽기 필요 */
#define SIGTTOU 22 /* 백그라운드 프로세스가 터미널에 데이터 쓰기 필요 */
#define SIGURG 23 "긴급" 데이터 있음
#define SIGXCPU 24 /* CPU 자원 제한 초과 */
#define SIGXFSZ 25 /* 파일 크기 초과 */
#define SIGVTALRM 26 /* 가상 시계 신호 */
#define SIGPROF 27 /* 시계 신호 설명 */
#define SIGWINCH 28 /* 창 크기 변경 */
#define SIGIO 29 /* 입력/출력 작업 가능 */
#define SIGPOLL SIGIO
#define SIGPWR 30 전원 재시작
#define SIGSYS 31 /* 불법 시스템 호출 */
#define SIGUNUSED 31 /* 사용되지 않는 신호 */

9번과 19번 신호는 무시할 수 없습니다. 이러한 신호는 인터럽트 번호와 유사하며, 다른 인터럽트 번호는 다른 인터럽트를 나타내고, 다른 인터럽트는 다른 기능을 구현합니다.

인터럽트를 사용할 때 인터럽트 처리 함수를 설정해야 하므로, 애플리케이션에서 신호를 사용할 때도 신호 처리 함수가 필요합니다. 애플리케이션에서 signal 함수를 사용하여 신호 처리 함수를 설정합니다:

/*
 * @description : 지정된 신호의 처리 함수 설정
 * @param - signum : 처리 함수를 설정할 신호
 * @param - handler : 신호 처리 함수
 * @return : 성공 시 이전 처리 함수 반환, 실패 시 SIG_ERR 반환
 */
sighandler_t signal(int signum, sighandler_t handler);

여기서 신호 처리 프로토타입은 다음과 같습니다:

typedef void (*sighandler_t)(int)

이전에 사용한 kill -9 PID는 프로세스에 SIGKILL 신호를 전송하는 것입니다. CTRL + C를 누르면 현재 터미널을 사용 중인 애플리케이션에 SIGINT(2) 신호가 전송되어 터미널이 중단됩니다.

linux/atk-mpl 폴더에 signaltest.c를 생성하고 다음 코드를 입력합니다:

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

/* 신호 처리 함수 */
void signalint_handler(int num)
{
    printf("\r\nSIGINT 신호!\r\n");
    exit(0);
}

int main(void)
{
    signal(SIGINT, signalint_handler);
    while(1);
    return 0;
}

/*
 전체 흐름: 먼저 자체 신호 처리 함수를 설정한 후 애플리케이션을 시작합니다. 
 CTRL+C를 눌러 signaltest에 SIGINT 신호를 보내면 signalint_handler 함수가 실행됩니다.
 이 함수는 SIGINT signal!을 출력한 후 exit 함수가 signaltest 애플리케이션을 종료합니다.
 */

다음 명령으로 signaltest.c를 컴파일합니다:

gcc signaltest.c -o signaltest

다음 명령으로 signaltest 애플리케이션을 실행합니다:

./signaltest

CTRL+ C를 누르면 다음과 같은 결과가 나타납니다:

1.2 드라이버의 신호 처리

① fasync_struct 구조체

드라이버에서 신호를 사용하려면 먼저 fasync_struct 구조체 변수를 정의해야 합니다. 일반적으로 이 구조체를 xxx_dev 구조체에 배치합니다:

/* 버튼 장치 구조체 */
struct btn_dev{
	dev_t devid;			/* 장치 번호 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 클래스 		*/
	struct device *device;	/* 장치 	 */
	struct device_node	*nd; /* 장치 노드 */
	int btn_gpio;			/* 버튼에 사용되는 GPIO 번호		*/
	struct timer_list timer;			/* 버튼 값 		*/
	int irq_num;			/* 인터럽트 번호 		*/
	
	atomic_t status;   		/* 버튼 상태 */
	wait_queue_head_t r_wait;	/* 읽기 대기 큐 헤드 */
        struct fasync_struct *async_queue;    /* fasync_struct 구조체 */
};

② fasync 함수

이 함수는 사용자 공간 애플리케이션의 프로세스 ID를 드라이버의 해당 파일 비동기 알림 큐에 추가합니다.

비동기 알림을 사용하려면 드라이버에서 file_operation 작업 집합의 fasync 함수를 구현해야 합니다:

int (*fasync) (int fd, struct file *filp, int on)

드라이버의 fasync 함수 예시는 다음과 같습니다:

struct xxx_dev {
    ......
    struct fasync_struct *async_queue; /* 비동기 관련 구조체 */
};

static int xxx_fasync(int fd, struct file *filp, int on)    // fasync 함수
{
    struct xxx_dev *dev = (xxx_dev)filp->private_data;    // 프라이빗 데이터

    if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)    // 여기서 fasync_helper는 실제로 fasync에 의해 호출됨
        return -EIO;
    return 0;
}

static struct file_operations xxx_ops = {
    ......
    .fasync = xxx_fasync,    // file_operations에 있는 모든 함수는 이 형식으로 구현해야 합니다
    ......
};

드라이브 파일을 닫아야 하는 경우 release 함수 내에서 fasync_struct를 해제해야 합니다. 예시는 다음과 같습니다:

static int xxx_release(struct inode *inode, struct file *filp)
{
    return xxx_fasync(-1, filp, 0); /* 비동기 알림 삭제 */
}
static struct file_operations xxx_ops = {
    ......
    .release = xxx_release,        // file_operations를 사용하는 경우 항상 이렇게 구현합니다
};

③ kill_fasync 함수

/*
 * @description : 장치가 접근 가능할 때 드라이버가 애플리케이션에 신호를 보내야 합니다. 이 함수는 지정된 신호를 전송합니다
 * @param - fp : 조작할 fasync_struct
 * @param - sig : 전송할 신호
 * @param - band : 읽기 가능할 때는 POLL_IN, 쓰기 가능할 때는 POLL_OUT으로 설정
 * @return : 없음
 */
void kill_fasync(struct fasync_struct **fp, int sig, int band);

1.3 애플리케이션의 비동기 알림 처리

총 3단계로 구성됩니다:

① 신호 처리 함수 등록

여기서의 처리 함수는 직접 설정해야 하며, 마지막으로 signal 함수에 전달해야 합니다.

② 커널에 현재 애플리케이션의 프로세스 번호 알리기

fcntl(fd, F_SETOWN, getpid())를 사용하여 현재 애플리케이션의 프로세스 번호를 커널에 알립니다.

③ 비동기 알림 활성화

flags = fcntl(fd, F_GETFL); /* 현재 프로세스 상태 가져오기 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 현재 프로세스 비동기 알림 기능 활성화 */

핵심은 fcntl 함수를 사용하여 프로세스 상태를 FASYNC로 설정하는 것입니다. 이 단계를 거치면 드라이버의 fasync 함수가 실행됩니다.

사용자 공간 애플리케이션이 fcntl 시스템 호출을 사용하고 F_SETFL 명령과 FASYNC 플래그를 설정하면 커널은 드라이버의 fasync 함수를 호출합니다.

  1. 프로그램 작성 ======

/linux/atk-mpl/Drivers 폴더에 16_notification 폴더를 생성하고, notification.c 파일을 생성하여 다음 코드를 입력합니다:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fcntl.h>

#define BUTTON_CNT			1		/* 장치 번호 개수 	*/
#define BUTTON_NAME		"btn"	/* 이름 		*/

/* 버튼 상태 정의 */
enum button_status {
    BTN_PRESS = 0,      // 버튼 눌림
    BTN_RELEASE,        // 버튼 떨어짐
    BTN_KEEP,           // 버튼 상태 유지
};

/* 버튼 장치 구조체 */
struct btn_dev{
	dev_t devid;			/* 장치 번호 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 클래스 		*/
	struct device *device;	/* 장치 	 */
	struct device_node	*nd; /* 장치 노드 */
	int btn_gpio;			/* 버튼에 사용되는 GPIO 번호		*/
	struct timer_list timer;			/* 버튼 값 		*/
	int irq_num;			/* 인터럽트 번호 		*/	
	atomic_t status;   		/* 버튼 상태 */
	wait_queue_head_t r_wait;	/* 읽기 대기 큐 헤드 */
	struct fasync_struct *async_queue;	/* fasync_struct 구조체 */
};

static struct btn_dev button;          /* 버튼 장치 */

static irqreturn_t btn_interrupt(int irq, void *dev_id)		// 인터럽트 처리 함수
{
	/* 버튼 디바운싱 처리, 15ms 지연으로 타이머 활성화 */
	mod_timer(&button.timer, jiffies + msecs_to_jiffies(15));
    return IRQ_HANDLED;
}

/*
 * @description	: 버튼 IO 초기화, open 함수로 드라이브를 열 때
 * 				  버튼에 사용되는 GPIO 핀을 초기화합니다.
 * @param 		: 없음
 * @return 		: 없음
 */
static int btn_parse_dt(void)
{
	int ret;
	const char *str;
	
	/* LED에 사용되는 GPIO 설정 */
	/* 1. 장치 노드 가져오기: btn */
	button.nd = of_find_node_by_path("/btn");
	if(button.nd == NULL) {
		printk("btn 노드를 찾을 수 없습니다!\r\n");
		return -EINVAL;
	}

	/* 2. status 속성 읽기 */
	ret = of_property_read_string(button.nd, "status", &str);
	if(ret < 0) 
	    return -EINVAL;

	if (strcmp(str, "okay"))
        return -EINVAL;
    
	/* 3. compatible 속성 값 읽기 및 비교 */
	ret = of_property_read_string(button.nd, "compatible", &str);
	if(ret < 0) {
		printk("btn: Failed to get compatible property\n");
		return -EINVAL;
	}

    if (strcmp(str, "alientek,btn")) {
        printk("btn: Compatible match failed\n");
        return -EINVAL;
    }

	/* 4. 장치 트리의 gpio 속성 가져오기, BTN0에 사용되는 KEY 번호 가져오기 */
	button.btn_gpio = of_get_named_gpio(button.nd, "btn-gpio", 0);
	if(button.btn_gpio < 0) {
		printk("btn-gpio를 가져올 수 없습니다");
		return -EINVAL;
	}

    /* 5. GPIO에 해당하는 인터럽트 번호 가져오기 */
    button.irq_num = irq_of_parse_and_map(button.nd, 0);
    if(!button.irq_num){
        return -EINVAL;
    }

	printk("btn-gpio 번호 = %d\r\n", button.btn_gpio);
	return 0;
}

static int btn_gpio_init(void)		// gpio 초기화
{
	int ret;
    unsigned long irq_flags;
	
	ret = gpio_request(button.btn_gpio, "BTN0");		// gpio 요청
    if (ret) {
        printk(KERN_ERR "btn: Failed to request btn-gpio\n");
        return ret;
	}	
	
	/* GPIO를 입력 모드로 설정 */
    gpio_direction_input(button.btn_gpio);

   /* 장치 트리에 지정된 인터럽트 트리거 유형 가져오기 */
	irq_flags = irq_get_trigger_type(button.irq_num);
	if (IRQF_TRIGGER_NONE == irq_flags)
		irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
		
	/* 인터럽트 요청 */
	ret = request_irq(button.irq_num, btn_interrupt, irq_flags, "Btn0_IRQ", NULL);
	if (ret) {
        gpio_free(button.btn_gpio);
        return ret;
    }

	return 0;
}

static void btn_timer_function(struct timer_list *arg)		// 타이머 처리 함수
{
    static int last_val = 1;
    int current_val;

    /* 버튼 값 읽기 및 현재 상태 판단 */
    current_val = gpio_get_value(button.btn_gpio);
    if (0 == current_val && last_val){
        atomic_set(&button.status, BTN_PRESS);	// 눌림
		wake_up_interruptible(&button.r_wait);	// r_wait 큐 헤드의 모든 큐 깨우기
		if(button.async_queue)
			kill_fasync(&button.async_queue, SIGIO, POLL_IN);	// 
	}
    else if (1 == current_val && !last_val) {
        atomic_set(&button.status, BTN_RELEASE);   // 떨어짐
		wake_up_interruptible(&button.r_wait);	// r_wait 큐 헤드의 모든 큐 깨우기
		if(button.async_queue)
			kill_fasync(&button.async_queue, SIGIO, POLL_IN);
	}
    else
        atomic_set(&button.status, BTN_KEEP);              // 상태 유지

    last_val = current_val;
}

/*
 * @description		: 장치 열기
 * @param - inode 	: 드라이브에 전달된 inode
 * @param - filp 	: 장치 파일, file 구조체에는 private_data라는 멤버 변수가 있음
 * 					  일반적으로 open 시 private_data를 장치 구조체를 가리키도록 설정합니다.
 * @return 			: 0 성공; 기타 실패
 */
static int btn_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * @description     : 장치에서 데이터 읽기 
 * @param – filp    : 열릴 장치 파일(파일 서술자)
 * @param – buf     : 사용자 공간으로 반환될 데이터 버퍼
 * @param – cnt     : 읽을 데이터 길이
 * @param – offt    : 파일 시작 주소에 대한 오프셋
 * @return          : 읽은 바이트 수, 음수일 경우 읽기 실패
 */
static ssize_t btn_read(struct file *filp, char __user *buf,
            size_t cnt, loff_t *offt)
{
    int ret;
	
	if (filp->f_flags & O_NONBLOCK) {	// 논블로킹 방식 접근
        if(BTN_KEEP == atomic_read(&button.status))
            return -EAGAIN;
    } else {							// 블로킹 방식 접근
	/* 대기 큐에 추가, 버튼이 눌리거나 떨어질 때만 깨어남 */
	ret = wait_event_interruptible(button.r_wait, BTN_KEEP != atomic_read(&button.status));
	if(ret)
		return ret;
	}
    /* 버튼 상태 정보를 애플리케이션으로 전송 */
    ret = copy_to_user(buf, &button.status, sizeof(int));

    /* 상태 재설정 */
    atomic_set(&button.status, BTN_KEEP);

    return ret;
}

 /*
 * @description		: fasync 함수, 비동기 알림 처리
 * @param – fd		: 파일 서술자
 * @param – filp	: 열릴 장치 파일(파일 서술자)
 * @param – on		: 모드
 * @return			: 음수는 함수 실행 실패를 의미
 */
static int btn_fasync(int fd, struct file *filp, int on)
{
	return fasync_helper(fd, filp, on, &button.async_queue);
}

/*
 * @description		: 장치에 데이터 쓰기 
 * @param - filp 	: 장치 파일, 열린 파일 서술자를 나타냅니다
 * @param - buf 	: 장치에 쓸 데이터
 * @param - cnt 	: 쓸 데이터 길이
 * @param - offt 	: 파일 시작 주소에 대한 오프셋
 * @return 			: 쓴 바이트 수, 음수일 경우 쓰기 실패
 */
static ssize_t btn_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 장치 닫기/해제
 * @param - filp 	: 닫을 장치 파일(파일 서술자)
 * @return 			: 0 성공; 기타 실패
 */
static int btn_release(struct inode *inode, struct file *filp)
{
	return btn_fasync(-1, filp, 0);
}

/*
 * @description     : poll 함수, 논블로킹 접근 처리
 * @param - filp    : 열릴 장치 파일(파일 서술자)
 * @param - wait    : 대기 목록(poll_table)
 * @return          : 장치 또는 리소스 상태,
 */
static unsigned int btn_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;

    poll_wait(filp, &button.r_wait, wait);

    if(BTN_KEEP != atomic_read(&button.status))	// 버튼이 눌리거나 떨어짐
		mask = POLLIN | POLLRDNORM;	// PLLIN 반환

    return mask;
}

/* 장치 작업 함수 */
static struct file_operations btn_fops = {
	.owner = THIS_MODULE,
	.open = btn_open,
	.read = btn_read,
	.write = btn_write,
	.release = 	btn_release,
	.poll = btn_poll,
	.fasync	= btn_fasync,
};

/*
 * @description	: 드라이브 진입 함수
 * @param 		: 없음
 * @return 		: 없음
 */
static int __init mybtn_init(void)
{
	int ret;
	
	/* 대기 큐 헤드 초기화 */
	init_waitqueue_head(&button.r_wait);
	
	/* 버튼 상태 초기화 */
	atomic_set(&button.status, BTN_KEEP);

	/* 장치 트리 파싱 */
	ret = btn_parse_dt();
	if(ret)
		return ret;
		
	/* GPIO 인터럽트 초기화 */
	ret = btn_gpio_init();
	if(ret)
		return ret;
		
	/* 문자 장치 드라이브 등록 */
	/* 1. 장치 번호 생성 */
	ret = alloc_chrdev_region(&button.devid, 0, BUTTON_CNT, BUTTON_NAME);	/* 장치 번호 요청 */
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", BUTTON_NAME, ret);
		goto free_gpio;
	}
	
	/* 2. cdev 초기화 */
	button.cdev.owner = THIS_MODULE;
	cdev_init(&button.cdev, &btn_fops);
	
	/* 3. cdev 추가 */
	ret = cdev_add(&button.cdev, button.devid, BUTTON_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4. 클래스 생성 */
	button.class = class_create(THIS_MODULE, BUTTON_NAME);
	if (IS_ERR(button.class)) {
		goto del_cdev;
	}

	/* 5. 장치 생성 */
	button.device = device_create(button.class, NULL, button.devid, NULL, BUTTON_NAME);
	if (IS_ERR(button.device)) {
		goto destroy_class;
	}
	
	/* 6. 타이머 초기화, 타이머 처리 함수 설정, 주기 설정되지 않아 타이머 활성화 안됨 */
	timer_setup(&button.timer, btn_timer_function, 0);
	
	return 0;

destroy_class:
	device_destroy(button.class, button.devid);
del_cdev:
	cdev_del(&button.cdev);
del_unregister:
	unregister_chrdev_region(button.devid, BUTTON_CNT);
free_gpio:
	free_irq(button.irq_num, NULL);
	gpio_free(button.btn_gpio);
	return -EIO;
}

/*
 * @description	: 드라이브 종료 함수
 * @param 		: 없음
 * @return 		: 없음
 */
static void __exit mybtn_exit(void)
{
	/* 문자 장치 드라이브 등록 해제 */
	cdev_del(&button.cdev);/*  cdev 삭제 */
	unregister_chrdev_region(button.devid, BUTTON_CNT); /* 장치 번호 등록 해제 */
	del_timer_sync(&button.timer);		/* 타이머 삭제 */
	device_destroy(button.class, button.devid);/*장치 등록 해제 */
	class_destroy(button.class); 		/* 클래스 등록 해제 */
	free_irq(button.irq_num, NULL);	/* 인터럽트 해제 */
	gpio_free(button.btn_gpio);		/* IO 해제 */
}

module_init(mybtn_init);
module_exit(mybtn_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

notificationApp.c 파일을 작성합니다:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

static int btn_fd;

/*
 * SIGIO 신호 처리 함수
 * @param – signum		: 신호 값
 * @return				: 없음
 */
static void sigio_signal_func(int signum)
{
    unsigned int btn_val = 0;

    read(btn_fd, &btn_val, sizeof(unsigned int));
    if (0 == btn_val)
        printf("버튼 눌림\n");
    else if (1 == btn_val)
        printf("버튼 떨어짐\n");

    printf("SIGIO 신호!\r\n");
}


/*
 * @description		: main 메인 프로그램
 * @param – argc		: argv 배열 요소 개수
 * @param – argv		: 구체적 매개변수
 * @return			: 0 성공; 기타 실패
 */
int main(int argc, char *argv[])
{
    int flags = 0;

    /* 전달된 매개변수 개수가 올바른지 확인 */
    if(2 != argc) {
        printf("사용법:\n"
               "\t./notificationApp /dev/btn\n"
              );
        return -1;
    }

    /* 장치 열기 */
    btn_fd = open(argv[1], O_RDONLY | O_NONBLOCK);  // 논블로킹 O_NONBLOCK
    if(0 > btn_fd) {
        printf("오류: %s 파일 열기 실패!\n", argv[1]);
        return -1;
    }

    /* SIGIO 신호 처리 함수 설정 */
    signal(SIGIO, sigio_signal_func);       // signal 함수, 신호 처리 함수 설정
    fcntl(btn_fd, F_SETOWN, getpid());			// 현재 프로세스의 프로세스 번호를 커널에 알림
    flags = fcntl(btn_fd, F_GETFD);			// 현재 프로세스 상태 가져오기
    fcntl(btn_fd, F_SETFL, flags | FASYNC);	// 프로세스 비동기 알림 기능 활성화


    /* 루프로 버튼 데이터 읽기 */
    while(1) {

        sleep(2);
    }

    /* 장치 닫기 */
    close(btn_fd);
    return 0;
}
  1. 실행 테스트 ======

먼저 Makefile 파일을 작성합니다:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)

obj-m := notification.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

그다음 notification.c와 notificationApp 파일을 컴파일합니다:

make
arm-none-linux-gnueabihf-gcc notificationApp.c -o notification

컴파일된 notificationApp과 notification.ko를 다음 위치로 복사합니다:

sudo cp notification notification.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

개발판을 켜고 다음 명령을 입력합니다:

cd lib/modules/5.4.31/
depmod
modprobe notification.ko

애플리케이션 테스트:

./notification /dev/btn

Btn0를 누르면 다음과 같은 결과가 나타납니다:

Btn0를 누를 때마다 SIGIO 신호를 캡처하고 버튼 값이 성공적으로 가져옵니다.

드라이브 언로드: rmmod notification.ko

태그: 리눅스 드라이버 시그널 처리 비동기 알림 파일 I/O 장치 드라이버

7월 7일 00:03에 게시됨