動機
上次看完Linux Kernel Development 3rd,想說來補一下 大概有個感覺
syscall
要直接改kernel,因為是寫死的與sortirq一樣 看這裡
top half
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/slab.h>
/* interrupt handler */
/* IRQ of your network card to be shared */
#define SHARED_IRQ 19
static int irq = SHARED_IRQ;
module_param(irq, int, S_IRUGO);
/* default delay time in top half -- try 10 to get results */
static int delay = 0;
module_param(delay, int, S_IRUGO);
static atomic_t counter_bh, counter_th;
struct my_dat {
unsigned long jiffies; /* used for timestamp */
struct tasklet_struct tsk; /* used in dynamic tasklet solution */
struct work_struct work; /* used in dynamic workqueue solution */
} my_data;
static struct my_dat
static irqreturn_t my_interrupt (int irq, void *dev_id) {
top_half_fun();
tasklet_schedule(&t_name);
return IRQ_HANDLED;
}
static int __init my_generic_init(void)
{
atomic_set(&counter_bh, 0);
atomic_set(&counter_th, 0);
/* use my_data for dev_id */
if (request_irq(irq, my_interrupt, IRQF_SHARED, "my_int", &my_data))
return -1;
printk(KERN_INFO "successfully loaded\n");
return 0;
}
static void __exit my_generic_exit(void)
{
synchronize_irq(irq);
free_irq(irq, &my_data);
printk(KERN_INFO " counter_th = %d, counter_bh = %d\n",
atomic_read(&counter_th), atomic_read(&counter_bh));
printk(KERN_INFO "successfully unloaded\n");
}
/* https://www.cs.otago.ac.nz/cosc440/labs/lab08.pdf */
bottom half (tasklet)
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/init.h>
typedef struct simp_t {
int i;
int j;
} simp;
static simp t_data;
/* tasklet bottom half */
static void t_fun(unsignned long t_arg) {
simp *datum = &t_data;
printk(KERN_INFO "Entering t_fun, datum->i = %d, jiffies = %ld\n", datum->i, jiffies);
printk(KERN_INFO "Entering t_fun, datum->j = %d, jiffies = %ld\n", datum->j, jiffies);
}
DECLARE_TASKLET_OLD (t_name, t_fun);
static int __init my_init(void) {
printk(KERN_INFO "\nHello: my_init loaded at address 0x%p\n", my_init);
t_data.i = 100;
t_data.j = 200;
printk(KERN_INFO "scheduling my tasklet, jiffies = %ld\n", jiffies);
tasklet_schedule(&t_name);
return 0;
}
static void __exit my_exit(void) {
printk(KERN_INFO "\nHello: my_exit loaded at address 0x%p\n", my_exit);
}
module_init(my_init);
module_exit(my_exit);
/* https://www.cs.otago.ac.nz/cosc440/labs/lab08.pdf */
bottom half (workqueue)
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>
MODULE_LICENSE("GPL");
static struct workqueue_struct *queue;
static void work_func(struct work_struct *work)
{
printk(KERN_INFO "worker\n");
}
DECLARE_WORK(work, work_func);
int init_module(void)
{
queue = create_singlethread_workqueue("myworkqueue");
queue_work(queue, &work);
return 0;
}
void cleanup_module(void)
{
/* why is this needed? Why flush_workqueue doesn't work? (re-insmod panics)
* http://stackoverflow.com/questions/37216038/whats-the-difference-between-flush-delayed-work-and-cancel-delayed-work-sync */
/*flush_workqueue(queue);*/
cancel_work_sync(&work);
destroy_workqueue(queue);
}
/* https://github.com/cirosantilli/linux-kernel-module-cheat/blob/ad077d3943f79c0f6481dab929970613c33c31a7/kernel_module/workqueue_cheat.c */
device driver
major number是裝置的編號,像用ls -l
去看tty都是4
minor number是看major number決定他有什麼意義
block
類似array
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/blk-mq.h>
#include <linux/hdreg.h>
#ifndef SECTOR_SIZE
#define SECTOR_SIZE 512
#endif
static int dev_major = 0;
/* Just internal representation of the our block device
* can hold any useful data */
struct block_dev {
sector_t capacity;
u8 *data; /* Data buffer to emulate real storage device */
struct blk_mq_tag_set tag_set;
struct request_queue *queue;
struct gendisk *gdisk;
};
/* Device instance */
static struct block_dev *block_device = NULL;
static int blockdev_open(struct block_device *dev, fmode_t mode)
{
printk(">>> blockdev_open\n");
return 0;
}
static void blockdev_release(struct gendisk *gdisk, fmode_t mode)
{
printk(">>> blockdev_release\n");
}
int blockdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, unsigned long arg)
{
printk("ioctl cmd 0x%08x\n", cmd);
return -ENOTTY;
}
/* Set block device file I/O */
static struct block_device_operations blockdev_ops = {
.owner = THIS_MODULE,
.open = blockdev_open,
.release = blockdev_release,
.ioctl = blockdev_ioctl
};
/* Serve requests */
static int do_request(struct request *rq, unsigned int *nr_bytes)
{
int ret = 0;
struct bio_vec bvec;
struct req_iterator iter;
struct block_dev *dev = rq->q->queuedata;
loff_t pos = blk_rq_pos(rq) << SECTOR_SHIFT;
loff_t dev_size = (loff_t)(dev->capacity << SECTOR_SHIFT);
printk(KERN_WARNING "sblkdev: request start from sector %lld pos = %lld dev_size = %lld\n", blk_rq_pos(rq), pos, dev_size);
/* Iterate over all requests segments */
rq_for_each_segment(bvec, rq, iter)
{
unsigned long b_len = bvec.bv_len;
/* Get pointer to the data */
void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset;
/* Simple check that we are not out of the memory bounds */
if ((pos + b_len) > dev_size) {
b_len = (unsigned long)(dev_size - pos);
}
if (rq_data_dir(rq) == WRITE) {
/* Copy data to the buffer in to required position */
memcpy(dev->data + pos, b_buf, b_len);
} else {
/* Read data from the buffer's position */
memcpy(b_buf, dev->data + pos, b_len);
}
/* Increment counters */
pos += b_len;
*nr_bytes += b_len;
}
return ret;
}
/* queue callback function */
static blk_status_t queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data* bd)
{
unsigned int nr_bytes = 0;
blk_status_t status = BLK_STS_OK;
struct request *rq = bd->rq;
/* Start request serving procedure */
blk_mq_start_request(rq);
if (do_request(rq, &nr_bytes) != 0) {
status = BLK_STS_IOERR;
}
/* Notify kernel about processed nr_bytes */
if (blk_update_request(rq, status, nr_bytes)) {
/* Shouldn't fail */
BUG();
}
/* Stop request serving procedure */
__blk_mq_end_request(rq, status);
return status;
}
static struct blk_mq_ops mq_ops = {
.queue_rq = queue_rq,
};
static int __init myblock_driver_init(void)
{
/* Register new block device and get device major number */
dev_major = register_blkdev(dev_major, "testblk");
block_device = kmalloc(sizeof (struct block_dev), GFP_KERNEL);
if (block_device == NULL) {
printk("Failed to allocate struct block_dev\n");
unregister_blkdev(dev_major, "testblk");
return -ENOMEM;
}
/* Set some random capacity of the device */
block_device->capacity = (112 * PAGE_SIZE) >> 9; /* nsectors * SECTOR_SIZE; */
/* Allocate corresponding data buffer */
block_device->data = kmalloc(block_device->capacity << 9, GFP_KERNEL);
if (block_device->data == NULL) {
printk("Failed to allocate device IO buffer\n");
unregister_blkdev(dev_major, "testblk");
kfree(block_device);
return -ENOMEM;
}
printk("Initializing queue\n");
block_device->queue = blk_mq_init_sq_queue(&block_device->tag_set, &mq_ops, 128, BLK_MQ_F_SHOULD_MERGE);
if (block_device->queue == NULL) {
printk("Failed to allocate device queue\n");
kfree(block_device->data);
unregister_blkdev(dev_major, "testblk");
kfree(block_device);
return -ENOMEM;
}
/* Set driver's structure as user data of the queue */
block_device->queue->queuedata = block_device;
/* Allocate new disk */
block_device->gdisk = alloc_disk(1);
/* Set all required flags and data */
block_device->gdisk->flags = GENHD_FL_NO_PART_SCAN;
block_device->gdisk->major = dev_major;
block_device->gdisk->first_minor = 0;
block_device->gdisk->fops = &blockdev_ops;
block_device->gdisk->queue = block_device->queue;
block_device->gdisk->private_data = block_device;
/* Set device name as it will be represented in /dev */
strncpy(block_device->gdisk->disk_name, "blockdev\0", 9);
printk("Adding disk %s\n", block_device->gdisk->disk_name);
/* Set device capacity */
set_capacity(block_device->gdisk, block_device->capacity);
/* Notify kernel about new disk device */
add_disk(block_device->gdisk);
return 0;
}
static void __exit myblock_driver_exit(void)
{
/* Don't forget to cleanup everything */
if (block_device->gdisk) {
del_gendisk(block_device->gdisk);
put_disk(block_device->gdisk);
}
if (block_device->queue) {
blk_cleanup_queue(block_device->queue);
}
kfree(block_device->data);
unregister_blkdev(dev_major, "testblk");
kfree(block_device);
}
module_init(myblock_driver_init);
module_exit(myblock_driver_exit);
MODULE_LICENSE("GPL");
char
stream of chars
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#define MAX_DEV 2
static int mychardev_open(struct inode *inode, struct file *file);
static int mychardev_release(struct inode *inode, struct file *file);
static long mychardev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static ssize_t mychardev_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
static ssize_t mychardev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
static const struct file_operations mychardev_fops = {
.owner = THIS_MODULE,
.open = mychardev_open,
.release = mychardev_release,
.unlocked_ioctl = mychardev_ioctl,
.read = mychardev_read,
.write = mychardev_write
};
struct mychar_device_data {
struct cdev cdev;
};
static int dev_major = 0;
static struct class *mychardev_class = NULL;
static struct mychar_device_data mychardev_data[MAX_DEV];
static int mychardev_uevent(struct device *dev, struct kobj_uevent_env *env)
{
add_uevent_var(env, "DEVMODE=%#o", 0666);
return 0;
}
static int __init mychardev_init(void)
{
int err, i;
dev_t dev;
err = alloc_chrdev_region(&dev, 0, MAX_DEV, "mychardev");
dev_major = MAJOR(dev);
mychardev_class = class_create(THIS_MODULE, "mychardev");
mychardev_class->dev_uevent = mychardev_uevent;
for (i = 0; i < MAX_DEV; i++) {
cdev_init(&mychardev_data[i].cdev, &mychardev_fops);
mychardev_data[i].cdev.owner = THIS_MODULE;
cdev_add(&mychardev_data[i].cdev, MKDEV(dev_major, i), 1);
device_create(mychardev_class, NULL, MKDEV(dev_major, i), NULL, "mychardev-%d", i);
}
return 0;
}
static void __exit mychardev_exit(void)
{
int i;
for (i = 0; i < MAX_DEV; i++) {
device_destroy(mychardev_class, MKDEV(dev_major, i));
}
class_unregister(mychardev_class);
class_destroy(mychardev_class);
unregister_chrdev_region(MKDEV(dev_major, 0), MINORMASK);
}
static int mychardev_open(struct inode *inode, struct file *file)
{
printk("MYCHARDEV: Device open\n");
return 0;
}
static int mychardev_release(struct inode *inode, struct file *file)
{
printk("MYCHARDEV: Device close\n");
return 0;
}
static long mychardev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk("MYCHARDEV: Device ioctl\n");
return 0;
}
static ssize_t mychardev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
uint8_t *data = "Hello from the kernel world!\n";
size_t datalen = strlen(data);
printk("Reading device: %d\n", MINOR(file->f_path.dentry->d_inode->i_rdev));
if (count > datalen) {
count = datalen;
}
if (copy_to_user(buf, data, count)) {
return -EFAULT;
}
return count;
}
static ssize_t mychardev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
size_t maxdatalen = 30, ncopied;
uint8_t databuf[maxdatalen];
printk("Writing device: %d\n", MINOR(file->f_path.dentry->d_inode->i_rdev));
if (count < maxdatalen) {
maxdatalen = count;
}
ncopied = copy_from_user(databuf, buf, maxdatalen);
if (ncopied == 0) {
printk("Copied %zd bytes from the user\n", maxdatalen);
} else {
printk("Could't copy %zd bytes from the user\n", ncopied);
}
databuf[maxdatalen] = 0;
printk("Data from the user: %s\n", databuf);
return count;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Oleg Kutkov <elenbert@gmail.com>");
module_init(mychardev_init);
module_exit(mychardev_exit);
/* https://olegkutkov.me/2018/03/14/simple-linux-character-device-driver/ */