#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/spinlock.h>
#include <linux/blk_types.h>
#include <linux/err.h>
#include "cls.h"
static int CLS_MAJOR = 0; // save the major number of the device
struct queue_limits queue_limit = {
.logical_block_size = 1024,
};
// static void cls_request(struct request_queue *q)
// {
// printk("CLS get request!");
// return;
// }
static int cls_open(struct gendisk *gdisk, fmode_t mode)
{
printk("CLS Device Drive open !\n");
return 0;
}
static void cls_release(struct gendisk *gd)
{
printk("CLS Device Drive released!\n");
}
static int cls_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned cmd, unsigned long arg)
{
printk("CLS Device Drive ioctl!\n");
return 0;
}
// multi queue에 넣어주는 함수
static blk_status_t cls_enqueue(struct blk_mq_hw_ctx *ctx, const struct blk_mq_queue_data *data){
blk_status_t ret = 0;
printk(KERN_INFO "PUT DATA TO CLS QUEUE!");
return ret;
}
static struct block_device_operations cls_fops = {
.owner = THIS_MODULE,
// .open = cls_open,
// .release = cls_release,
// .ioctl = cls_ioctl
};
static struct blk_mq_ops cls_mq_ops = {
.queue_rq = cls_enqueue
};
static struct cls_dev *cls_alloc(void)
{
// request queue랑 gendisk를 할당해주고 그 struct에 각각의 구조체를 연결해서 반환
struct cls_dev *mydev;
struct gendisk *disk;
int error;
printk(KERN_INFO "CLS : CLS DEVICE START TO ALLOCATE");
mydev = kzalloc(sizeof(*mydev), GFP_KERNEL); // kzmalloc로 커널 공간에 메모리 할당
// GFP_KERNEL : 흔히 사용되는 flag로 메모리가 충분하지 않으면 sleep 상태가 된다.
if(!mydev){
printk(KERN_WARNING "CLS : FAIL TO MALLOC TO DEVICE STRUCT!");
return NULL;
}
spin_lock_init(&mydev->cls_lock);
// 2. tag set 할당
printk(KERN_INFO "CLS : START ALLOCATE TAG SET");
mydev->tag_set.ops = &cls_mq_ops;
mydev->tag_set.nr_hw_queues = 1;
mydev->tag_set.queue_depth = 32;
mydev->tag_set.numa_node = NUMA_NO_NODE;
mydev->tag_set.cmd_size = 0;
mydev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE;
mydev->tag_set.driver_data = mydev;
error = blk_mq_alloc_tag_set(&mydev->tag_set);
if(error){
printk(KERN_WARNING "CLS : FAIL TO ALLCOCATE TAG SET");
kfree(mydev);
return NULL;
}
printk(KERN_INFO "CLS : FINISH ALLOCATE TAG SET");
// 3. gendisk 할당
printk(KERN_INFO "CLS : START ALLOCATE DISK");
disk = blk_mq_alloc_disk(&mydev->tag_set, &queue_limit, mydev->queue);
disk->major = CLS_MAJOR;
disk->fops = &cls_fops;
disk->first_minor = 0;
disk->minors = 2;
disk->private_data = mydev;
snprintf(disk->disk_name, 32, "cls");
// blk_mq_alloc_disk > queue allocate,
// alloc_disk_node를 호출
// alloc_disk_node : disk에 kzalloc_node를 해서 넣어주고, bio(block i/o) initialize
//
// genhd.c의 alloc disk node를 호출
// 그 함수에서 queue를 할당
if(IS_ERR(disk)){
printk(KERN_WARNING "CLS : FAIL TO ALLCOCATE DISK");
blk_mq_free_tag_set(&mydev->tag_set);
kfree(mydev);
return NULL;
}
printk(KERN_INFO "CLS : FINISH ALLOCATE DISK");
mydev->gdisk = disk;
mydev->queue = disk->queue;
printk(KERN_INFO "CLS : START ADD DISK");
error = add_disk(disk); // 여기에서 뭔가 warning이 발생
if(error){
// 여기로 들어옴..
// WARN_ON(!disk->minors) 에서 걸리는 듯
// 즉 major가 0이 아닌데 minors 가 0이다.. 이걸 설정해주자
printk(KERN_WARNING "CLS : FAIL TO ADD DISK with %d",error);
blk_mq_free_tag_set(&mydev->tag_set);
kfree(mydev);
return NULL;
}
printk(KERN_INFO "CLS : FINISH ADD DISK");
return mydev;
}
static int __init cls_init(void)
{
int result;
struct cls_dev *mydev;
result = register_blkdev(CLS_MAJOR, "cls");
// error handling
if(result < 0) {
printk(KERN_WARNING "CLS: Fail to get major number!\n");
return result;
}
if(CLS_MAJOR == 0){
CLS_MAJOR = result;
}
mydev = cls_alloc();
if(!mydev){
printk(KERN_WARNING "CLS: Fail to add disk!\n");
return -1;
}
printk(KERN_INFO "DEVICE : CLS is successfully initialized with major number %d\n",CLS_MAJOR);
return 0;
}
static void __exit cls_exit(void)
{
unregister_blkdev(CLS_MAJOR,BLKDEV_NAME);
printk(KERN_INFO "DEVICE : CLS is successfully unregistered!\n");
}
module_init(cls_init);
module_exit(cls_exit);
MODULE_AUTHOR("MinyoungKim");
MODULE_DESCRIPTION("Virtual Block Device Driver");
MODULE_LICENSE("GPL");
240630 성공했다.
어제 밤에 계속 문제가 생긴 것은 add_disk() 전에 set_capacity를 해줘서 문제가 있는 것이었다.
어떤 커널 메시지도 없이
CLS: START ADD DISK 에서 아무것도 되지 않았다
다행히 https://stackoverflow.com/questions/13518404/add-disk-hangs-on-insmod
add_disk() hangs on insmod
I am writing a Linux block device driver and I have a lot of the initialisation stuff working. However, when I finally call add_disk(), the module hangs during insmod. The offending snippet is her...
stackoverflow.com
여기에서 나와 정확히 같은 문제를 가진 사람이 있었고, 어떤 사람이 답변을 달아줘서 똑같이 하니 되었다!!
아직 실제 data를 쓰거나 할 수는 없지만, 일단 /dev/cls가 생겼다는 것에 너무 큰 기쁨을 느낀다.
이 맛에 코딩하지..
240629 다양한 블로그 글과 구글링을 통해 많은 정보를 가졌지만, 한 가지 한계점이 있었다.
바로 linux 6.9.5 버전에선 alloc_disk(1); 이 사라졌다는 것이다.
원래 대부분의 virtual device driver 코드를 보면, Request Queue를 따로 할당해주고, disk를 따로 할당해 disk를 /dev에 올리는 방법을 사용하고 있었다.
하지만, linux 6.9.5 버전에선 alloc_disk를 찾을 수 없었고, 대신 blk_mq_alloc_disk()라는 함수로 disk allocate를 수행하고 있다.
아마도 single cpu를 사용하는 컴퓨터가 거의 없고 대부분 multi-core processor이다 보니 이를 더 잘 활용하기 위해 multi-queue 더 잘 활용하려는 시도가 아니었을까 싶다.
하지만, 문제는 저 함수 안에서 queue를 초기화 해주고 대신 tag_set 이라는 새로운 구조체를 통해 다양한 인자를 전달해주고 있었다. 내가 저 argument가 모두 어떤 역할을 파악하지 못 해서 이를 사용하는데 큰 한계가 있었다. linux source에 다양한 디바이스 드라이버 코드를 확인하며 이해하려고 노력했지만 역부족이었다.
희망적인 것은 modulization의 개념을 이해하였으며, 어디서 문제가 일어나고 있는지 파악했다는 것이다.
add_disk를 통해 disk를 /dev에 올릴 때, disk_add_dev 함수에서 문제가 발생하고 있었다.
아마도 disk->major, disk->minors에 관련해서 뭔가 문제가 발생하고 있는 것 같다.
major은 register_blkdev를 통해 가져오는 값을 그대로 할당하고 있기 때문에 괜찮은 것 같은데, minors가 문제다.
minors를 설정하지 않고, first_minor만 설정하면 insmod 자체가 불가능하고, minors = 1로 설정하면 insmod하면 무한루프에 빠지는 건지 컴퓨터가 먹통이 되어 버린다.
major, minor 번호가 device를 구분하기 위해 사용한다고 하는데, 실제 gdisk struct에 보면 이것을 driver에서 explict 하게 설정하지 말라고 되어 있다. 근데 커널 소스의 다양한 디바이스 드라이버들이 이걸 explicit하게 설정하고 있기도 해서 뭐가 문제인지 잘 파악이 되지 않는다.
충분한 시간을 가지고 tag set, block device I/O를 어떻게 hardware I/O로 변환해 넣어주는지 등에 대해 공부해야겠다고 생각했다.