printf("ho_tari\n");
9일차 본문
2025.06.23
ctags
$ sudo apt-get install universal-ctags
$ sudo apt-get install cscope
#linux 폴더에서 실행
ctags -R
cscope -R
$ vi ~/.vimrc 로 .vimrc 파일을 연다.
set hlsearch
set autoindent
set cindent
set nu
set tabstop=4
set shiftwidth=4
set title
if has("syntax")
syntax on
endif
if filereadable("./tags")
set tags=./tags
elseif filereadable("../tags")
set tags=../tags
elseif filereadable("../../tags")
set tags=../../tags
elseif filereadable("../../../tags")
set tags=../../../tags
elseif filereadable("../../../../tags")
set tags=../../../../tags
else
set tags=/home/ubuntu/project/linux/tags
endif
set tags=/home/ubuntu/project/linux/tags
set csprg=/usr/bin/cscope
"set csto=0
"set cst
set nocsverb
if filereadable("./cscope.out")
cs add ./cscope.out
elseif filereadable("../cscope.out")
cs add ../cscope.out
elseif filereadable("../../cscope.out")
cs add ../../cscope.out
elseif filereadable("../../../cscope.out")
cs add ../../../cscope.out
elseif filereadable("../../../../cscope.out")
cs add ../../../../cscope.out
else
cs add /home/pi/project/linux/cscope.out
endif
set csverb
정의로 가기
Ctrl-]
Ctrl-t
gd => go definition
Ctrl+o to go back; Ctrl+i to go forward
search 현재 커서있는 단어
g + *
n : next 찾기
shift + n : previous
Ctrl+o to go back; Ctrl+i to go forward
line number로 이동
50 + shift + g
word로 이동
7 + w
- [cscope 사용법]
- 소스 디렉토리에서 ctags 생성
$ ctags -R
- -R 이라는 옵션이 하위디렉토리까지 생각해서 만든다는거다.
- 만약에 ctags * 라고 명령어를 실행하면, 현재 디렉토리 기준으로만 ctag가 생성된다.
- 소스 디렉토리에서 cscope.out 과 cscope.files 를 생성
ctrl + d 눌러서 빠져 나온다.$ cscope -R
- ex) 만약에 test/ 즉, test라는 폴더 안의 소스를 전부 분석하고 싶으면 cd test 로 test 폴더 들어가서 만들면 되겠지.
- ex) 만약에 test/ 즉, test라는 폴더 안의 소스를 전부 분석하고 싶으면 cd test 로 test 폴더 들어가서 만들면 되겠지.
핸들러 함수 VS 콜백 함수
구분 | 핸들러 함수 (Handler Function) | 콜백 함수 (Callback Function) |
정의 | 특정 이벤트나 신호, 인터럽트 등이 발생했을 때 이를 처리하기 위해 등록된 함수 | 다른 함수에 인자로 전달되어, 특정 조건이나 이벤트 발생 시 호출되는 함수 |
주요 용도 | 주로 이벤트, 인터럽트, 신호, 사용자 입력 등을 처리하는 데 사용 | 비동기 처리, 이벤트 처리, 작업 완료 시 추가 동작 등 다양한 상황에서 사용 |
등록 방식 | 시스템이나 프레임워크에 핸들러로 명시적으로 등록됨 (예: 인터럽트 핸들러, 이벤트 핸들러) | 함수의 인자로 직접 전달되거나, 내부적으로 등록되어 호출됨 |
실행 시점 | 해당 이벤트나 신호가 발생할 때 시스템이 자동으로 호출 | 해당 함수(예: 비동기 함수)의 작업이 끝나거나, 특정 조건이 만족될 때 호출 |
예시 | 인터럽트 핸들러, 이벤트 핸들러, 신호 핸들러 | setTimeout, 비동기 파일 읽기, Promise.then, 이벤트 리스너 등 |
추가 설명
- 핸들러 함수는 “어떤 이벤트나 신호가 발생했을 때 그에 대응해 처리하는 역할”을 하며, 운영체제나 프레임워크에 명시적으로 등록되어 동작합니다. 예를 들어, 인터럽트 핸들러는 하드웨어 인터럽트가 발생할 때 운영체제가 호출하는 함수입니다
- 콜백 함수는 “다른 함수에 인자로 전달되어, 그 함수의 작업이 끝난 후에 호출되는 함수”입니다. 콜백은 비동기 처리, 이벤트 처리 등에서 널리 사용되며, 함수의 제어 흐름을 유연하게 만들 수 있습니다
- 관계: 콜백 함수가 핸들러로 등록되어 사용될 수도 있습니다. 예를 들어, 이벤트 핸들러를 콜백 함수로 등록하는 경우가 많습니다
요약
- 핸들러 함수: 이벤트나 신호가 발생할 때 시스템이 자동으로 호출하도록 등록된 함수
- 콜백 함수: 다른 함수에 인자로 전달되어, 특정 조건이나 이벤트가 발생할 때 호출되는 함수
둘은 비슷해 보이지만, 핸들러는 “등록”과 “자동 호출”에 초점이 있고, 콜백은 “함수 전달”과 “유연한 실행”에 초점이 있습니다.
1. list_add() / list_add_tail()
void list_add(struct list_head *new, struct list_head *head);
void list_add_tail(struct list_head *new, struct list_head *head);
• 첫 번째 인자: new → 새로 리스트에 넣을 노드
• 두 번째 인자: head → 어디 근처에 넣을지를 결정하는 기준
즉, 이 함수는 new를 어디에 “넣을지” 결정하기 때문에, new가 주체입니다.
2. list_del() / list_move()
void list_del(struct list_head *entry);
void list_move(struct list_head *entry, struct list_head *head);
• 첫 번째 인자: entry → 이미 리스트 안에 존재하는 노드 (작업의 대상)
• list_del(entry) → entry를 삭제
• list_move(entry, head) → entry를 head 바로 뒤로 이동
이 함수들은 리스트에 이미 들어 있는 노드를 조작하는 것이기 때문에, 그 노드가 첫 번째 인자로 오는 것이 자연스럽습니다.
요약 표
함수 이름 첫 번째 인자 의미 동작 주체
list_add() new 새로 추가할 노드 추가 대상 노드
list_add_tail() new 새로 추가할 노드 추가 대상 노드
list_del() entry 삭제할 노드 조작 대상 노드
list_move() entry 이동시킬 노드 조작 대상 노드
list_move_tail() entry 리스트의 끝으로 이동시킬 노드 조작 대상 노드
비유로 이해하기
• list_add(new, head)는 “new야, 너 head 뒤에 가 있어” → new가 중심
• list_del(entry)는 “entry야, 너 삭제당해” → entry가 중심
보통 이런 설계는 왜 하나요?
• 일관성 유지와 가독성 때문입니다.
• 보통 어떤 함수가 무엇을 조작할지에 따라 그 “주체”를 첫 번째 인자로 넣는 것이 일반적입니다.

bh1750.c
/* bh1750_chrdrv.c */
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define BH1750_ADDR 0x23
#define CMD_POWER_ON 0x01
#define CMD_RESET 0x07
#define CMD_CONT_HIGHRES 0x10
#define DEVICE_NAME "bh1750"
#define CLASS_NAME "sensor"
struct bh1750_data {
struct i2c_client *client;
struct cdev cdev;
};
static dev_t dev_number;
static struct class *bh1750_class;
static int bh1750_read_lux(struct i2c_client *client, char *buf)
{
u8 data[2];
int ret;
u16 raw;
u32 lux_x100;
/* 전원 ON, 리셋, 연속 고해상도 모드 */
i2c_smbus_write_byte(client, CMD_POWER_ON);
i2c_smbus_write_byte(client, CMD_RESET);
i2c_smbus_write_byte(client, CMD_CONT_HIGHRES);
msleep(180);
ret = i2c_smbus_read_i2c_block_data(client,
CMD_CONT_HIGHRES,
2, data);
if (ret < 0)
return ret;
raw = (data[0] << 8) | data[1];
lux_x100 = (raw * 1000U + 6) / 12;
return sprintf(buf, "%u.%02u\n",
lux_x100 / 100,
lux_x100 % 100);
}
static ssize_t bh1750_char_read(struct file *filp,
char __user *user_buf,
size_t count,
loff_t *ppos)
{
struct bh1750_data *d = filp->private_data;
char tmp[16];
int len;
if (*ppos > 0)
return 0; /* EOF */
len = bh1750_read_lux(d->client, tmp);
if (len < 0)
return len;
if (copy_to_user(user_buf, tmp, len))
return -EFAULT;
*ppos += len;
return len;
}
static int bh1750_char_open(struct inode *inode, struct file *filp)
{
struct bh1750_data *d = container_of(inode->i_cdev,
struct bh1750_data, cdev);
filp->private_data = d;
return 0;
}
static const struct file_operations bh1750_fops = {
.owner = THIS_MODULE,
.open = bh1750_char_open,
.read = bh1750_char_read,
};
static int bh1750_probe(struct i2c_client *client)
{
struct bh1750_data *d;
int ret;
/* 드라이버 데이터 할당 */
d = devm_kzalloc(&client->dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->client = client;
/* 캐릭터 디바이스 번호 및 클래스 생성 */
if (!bh1750_class) {
ret = alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME);
if (ret < 0)
return ret;
bh1750_class = class_create(CLASS_NAME);
if (IS_ERR(bh1750_class)) {
unregister_chrdev_region(dev_number, 1);
return PTR_ERR(bh1750_class);
}
}
/* cdev 초기화 및 등록 */
cdev_init(&d->cdev, &bh1750_fops);
d->cdev.owner = THIS_MODULE;
ret = cdev_add(&d->cdev, dev_number, 1);
if (ret) {
class_destroy(bh1750_class);
unregister_chrdev_region(dev_number, 1);
return ret;
}
/* /dev 노드 생성 */
device_create(bh1750_class, NULL, dev_number, NULL, DEVICE_NAME);
i2c_set_clientdata(client, d);
dev_info(&client->dev, "%s: registered char device /dev/%s\n",
DEVICE_NAME, DEVICE_NAME);
return 0;
}
static void bh1750_remove(struct i2c_client *client)
{
struct bh1750_data *d = i2c_get_clientdata(client);
device_destroy(bh1750_class, dev_number);
cdev_del(&d->cdev);
class_destroy(bh1750_class);
unregister_chrdev_region(dev_number, 1);
}
static const struct i2c_device_id bh1750_id[] = {
{ "bh1750", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, bh1750_id);
static struct i2c_driver bh1750_driver = {
.driver = { .name = "bh1750" },
.probe = bh1750_probe,
.remove = bh1750_remove,
.id_table = bh1750_id,
};
module_i2c_driver(bh1750_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("BH1750 I2C Light Sensor with /dev Interface");
MODULE_LICENSE("GPL");



bh1750_read.c
// bh1750_read.c (using sysfs interface, continuous measurement)
// 유저스페이스에서 sysfs로 BH1750 조도 센서 값을 계속 읽어 출력
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
const char *sysfs_path = "/dev/bh1750";
char buf[32];
while (1) {
FILE *fp = fopen(sysfs_path, "r");
if (!fp) {
perror("fopen sysfs lux");
return EXIT_FAILURE;
}
if (fgets(buf, sizeof(buf), fp) != NULL) {
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n')
buf[len-1] = '\0';
printf("Ambient Light: %s lux\n", buf);
} else {
fprintf(stderr, "Failed to read lux value from %s\n", sysfs_path);
fclose(fp);
return EXIT_FAILURE;
}
fclose(fp);
sleep(1); // 1초 간격으로 측정
}
return EXIT_SUCCESS;
}

1. 모듈 등록 및 메타 정보
module_i2c_driver(bh1750_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("BH1750 I2C Light Sensor with /dev Interface");
MODULE_LICENSE("GPL");
- 일반 원리
- module_init/module_exit 대신 버스 전용 매크로(module_i2c_driver) 사용
- MODULE_LICENSE("GPL") 등으로 라이선스·저자·설명 기입
- bh1750_chrdrv.c
- I²C 버스에 붙는 bh1750_driver를 등록
- 모듈이 로드될 때 bh1750_probe 호출, 언로드될 때 자동으로 bh1750_remove
2. I²C 드라이버 구조체
static const struct i2c_device_id bh1750_id[] = {
{ "bh1750", 0 },
{ }
};
static struct i2c_driver bh1750_driver = {
.driver = { .name = "bh1750" },
.probe = bh1750_probe,
.remove = bh1750_remove,
.id_table = bh1750_id,
};
- 일반 원리
- .name / .id_table로 디바이스 매칭
- .probe() / .remove() 콜백 등록
- 적용 예
- 디바이스 트리나 i2c_board_info에서 "bh1750"가 있으면 bh1750_probe 실행
3. 드라이버 데이터 저장
struct bh1750_data {
struct i2c_client *client;
struct cdev cdev;
};
- 일반 원리
- 디바이스별 컨텍스트(struct)에 상태·핸들 저장
- devm_kzalloc() / i2c_set_clientdata() 로 관리
- 코드 내역
- client 포인터 → I²C 전송에 사용
- cdev 구조체 → 캐릭터 디바이스 등록에 사용
4. 캐릭터 디바이스 등록
/* 1) dev 번호 할당 & class 생성 */
ret = alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME);
bh1750_class = class_create(CLASS_NAME);
/* 2) cdev 초기화 & 등록 */
cdev_init(&d->cdev, &bh1750_fops);
cdev_add(&d->cdev, dev_number, 1);
/* 3) /dev 노드 생성 */
device_create(bh1750_class, NULL, dev_number, NULL, DEVICE_NAME);
- 일반 원리
- alloc_chrdev_region()
- cdev_init() + cdev_add()
- class_create() + device_create() → /dev/... 자동 생성
- 주의 사항
- class_create() 시점에 THIS_MODULE 인자는 최신 API에서 제거됨
- 성공 시 major/minor 번호가 dev_number에 채워짐
5. 파일 연산자(File Operations)
static const struct file_operations bh1750_fops = {
.owner = THIS_MODULE,
.open = bh1750_char_open,
.read = bh1750_char_read,
};
- 일반 원리
- open, read, write, ioctl 등 함수 포인터 테이블
- 사용자 공간의 open("/dev/bh1750") → bh1750_char_open 호출
- bh1750_chrdrv.c
- open에서 private_data에 bh1750_data 구조체 연결
- read에서 I²C 센서값을 읽어 버퍼에 복사
6. I²C 통신: SMBus API 사용
i2c_smbus_write_byte(client, CMD_POWER_ON);
i2c_smbus_write_byte(client, CMD_RESET);
i2c_smbus_write_byte(client, CMD_CONT_HIGHRES);
msleep(180);
i2c_smbus_read_i2c_block_data(client, CMD_CONT_HIGHRES, 2, data);
- 일반 원리
- SMBus API로 간단한 바이트 송수신 또는 블록 전송
- 포인트
- 센서에 명령 전송 → 측정 모드 설정
- msleep() 사용으로 커널 스케줄러에 제어 양보
7. Probe & Remove
static int bh1750_probe(struct i2c_client *client) { … }
static void bh1750_remove(struct i2c_client *client) { … }
- probe() 주요 작업
- devm_kzalloc()으로 driver data 할당
- char device 등록 (위 4번)
- i2c_set_clientdata() 연결
- remove() 주요 작업
- device_destroy(), cdev_del()
- class_destroy(), unregister_chrdev_region()
8. 데이터 흐름 요약
- 모듈 로드 → I²C driver 등록
- 디바이스 바인딩 → bh1750_probe()
- 유저 공간: open("/dev/bh1750") → read()
- 커널: SMBus 통해 센서 읽고, 포맷팅 후 user buffer에 복사
- 모듈 언로드 → bh1750_remove()
9. 확장 학습 포인트
- 동기 vs 비동기 I/O: read()가 블로킹/논블로킹 지원해 보기
- IOCTL 추가: 측정 모드 변경, 타이밍 조절 기능 구현
- Device Tree: .of_match_table 추가하여 DT 바인딩
- 전력 관리: pm_runtime_* API로 센서 전원 관리
I²C 주소 스캔 결과에서
- 23: 0x23 주소의 디바이스가 응답했다는 의미입니다.
- i2cdetect가 “ping” 방식으로 해당 주소에 읽기 시도를 해서 ACK(응답)를 받으면 16진수 주소를 표시합니다.
- UU: 그 주소(예: 0x23)에 이미 커널 드라이버가 바인딩(bind)되어 있어서, 유저 공간에서 직접 probing(읽기/쓰기)하지 않는다는 뜻입니다.
- 이미 /sys/bus/i2c/devices/…/driver 아래에 해당 디바이스가 등록되어 있으면, 안전을 위해 i2cdetect는 “사용 중(Used)” 표시로 바꿔버립니다.
요약
23 | 장치가 응답함 | 아직 커널 드라이버 미등록 상태 |
UU | 드라이버가 바인딩됨 | 드라이버에서 이미 사용 중이므로 스캔하지 않음 |
'(Telechips) AI 시스템 반도체 SW 개발자 교육 > SoC 시스템 반도체를 위한 임베디드 리눅스' 카테고리의 다른 글
2일차 (0) | 2025.06.27 |
---|---|
10일차 (0) | 2025.06.24 |
8일차 (0) | 2025.06.23 |
7일차 (0) | 2025.06.20 |
6일차 (0) | 2025.06.18 |