20252803 滕森,完成MarkdownFlow的课堂任务

要求:通过网址生成任务,提交生成内容的实践过程。https://play.mdflow.run/share?id=6bf459c8b3dc3ecfaf23a6d1eb01d530

Linux 驱动程序

课程目标:

了解驱动程序在操作系统中的角色。
理解内核模块的基本概念。
亲手编写、编译、加载和卸载一个最简单的内核模块。
对后续深入学习的方向有初步认识。

第一部分:引言与概念铺垫

什么是驱动程序?(5分钟)

想象一下,硬件就像是各种不同的"乐器"——显卡是钢琴,声卡是小提琴,网卡是鼓。操作系统呢,就是乐队的"指挥"。但指挥看不懂每个乐器自己的语言,这时候就需要"乐手"——也就是驱动程序。

驱动程序就是那个能看懂乐谱(操作系统指令)并让乐器发声的"乐手"。

它的核心作用就是当翻译官和桥梁:

把操作系统的统一指令翻译成硬件能听懂的具体操作
把硬件的状态和数据处理成操作系统能理解的信息
最终目标就是让所有应用程序都能用统一的方式(比如read、write这些系统调用)来访问五花八门的硬件

为什么要在Linux上学习驱动编程?(5分钟)

你可能想问:为什么偏偏是Linux?我给你三个很实在的理由:

开源精神:Linux内核源码完全开放,这就像给你一本最权威的教科书,随时可以翻看学习
实践价值:现在物联网、嵌入式、自动驾驶这些热门领域,对驱动开发人才的需求特别大
加深理解:学会了驱动编程,你才能真正理解操作系统底层是怎么工作的

内核空间 vs 用户空间 (5分钟)

这是驱动编程与传统应用编程最大的不同点,你一定要搞清楚:

用户空间:

就是你平时写的那些C/C++/Python程序运行的地方
受到CPU保护,不能直接访问硬件
内存是隔离的、虚拟的,一个程序崩溃不会影响整个系统

内核空间:

操作系统核心和驱动程序运行的地方
拥有最高权限,可以直接操控硬件和所有内存
一旦出错(比如非法内存访问),会导致整个系统崩溃
交互方式:应用程序通过"系统调用"陷入内核,请求驱动提供服务。

第二部分:我们的第一个驱动——“Hello, Kernel!”
代码实现

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple hello world kernel module");

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Kernel! Module loaded successfully.\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel! Module unloaded.\n");
}

module_init(hello_init);
module_exit(hello_exit);

在这里插入图片描述

编译Makefile

obj-m += hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

在这里插入图片描述

运行过程
编译模块:

make

在这里插入图片描述

你会看到生成了hello.ko文件
在这里插入图片描述

加载模块:

sudo insmod hello.ko

查看加载结果:

dmesg | tail -5

你应该能看到:“Hello, Kernel! Module loaded successfully.”
在这里插入图片描述

查看已加载模块:

lsmod | grep hello

在这里插入图片描述

卸载模块:

sudo rmmod hello

确认卸载:

dmesg | tail -5

你会看到:“Goodbye, Kernel! Module unloaded.”
在这里插入图片描述

第三部分:更多实用示例
示例1:带参数的驱动模块
代码实现

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

static char *mystring = "default";
static int myint = 100;

module_param(myint, int, S_IRUGO);
module_param(mystring, charp, S_IRUGO);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module with parameters");

static int __init param_init(void)
{
    printk(KERN_INFO "Parameter module loaded\n");
    printk(KERN_INFO "myint = %d, mystring = %s\n", myint, mystring);
    return 0;
}

static void __exit param_exit(void)
{
    printk(KERN_INFO "Parameter module unloaded\n");
}

module_init(param_init);
module_exit(param_exit);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

运行过程
正常加载:

sudo insmod param_module.ko
dmesg | tail -3

输出默认值:myint = 100, mystring = default
在这里插入图片描述

带参数加载:

sudo insmod param_module.ko myint=200 mystring="custom_value"
dmesg | tail -3

输出:myint = 200, mystring = custom_value
在这里插入图片描述

示例2:简单的字符设备驱动
代码实现

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "simple_char"
#define BUFFER_SIZE 1024

static char device_buffer[BUFFER_SIZE];
static int buffer_pointer = 0;

MODULE_LICENSE("GPL");

static int device_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Simple char device opened\n");
    return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Simple char device closed\n");
    return 0;
}

static ssize_t device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
{
    int bytes_read = 0;
    
    if (buffer_pointer <= 0) {
        return 0;
    }
    
    if (copy_to_user(buffer, device_buffer, buffer_pointer)) {
        return -EFAULT;
    }
    
    bytes_read = buffer_pointer;
    buffer_pointer = 0;
    
    printk(KERN_INFO "Read %d bytes from device\n", bytes_read);
    return bytes_read;
}

static ssize_t device_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset)
{
    if (length > BUFFER_SIZE) {
        return -EINVAL;
    }
    
    if (copy_from_user(device_buffer, buffer, length)) {
        return -EFAULT;
    }
    
    buffer_pointer = length;
    printk(KERN_INFO "Write %zu bytes to device: %s\n", length, device_buffer);
    return length;
}

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

static int __init char_init(void)
{
    int ret;
    int major = 0;
    
    ret = register_chrdev(major, DEVICE_NAME, &fops);
    if (ret < 0) {
        printk(KERN_ALERT "Failed to register char device\n");
        return ret;
    }
    
    printk(KERN_INFO "Simple char device registered with major number %d\n", ret);
    return 0;
}

static void __exit char_exit(void)
{
    unregister_chrdev(0, DEVICE_NAME);
    printk(KERN_INFO "Simple char device unregistered\n");
}

module_init(char_init);
module_exit(char_exit);

在这里插入图片描述

在这里插入图片描述

运行过程
编译加载:

make
sudo insmod char_device.ko

在这里插入图片描述
查看设备ID:

dmesg | tail -n 10 

在这里插入图片描述

创建设备节点:

sudo mknod /dev/simple_char c 497 0
sudo chmod 666 /dev/simple_char

在这里插入图片描述

测试写入:

echo "Hello from userspace" > /dev/simple_char
dmesg | tail -3

你会看到:“Write 21 bytes to device: Hello from userspace”
在这里插入图片描述

测试读取:

cat /dev/simple_char
dmesg | tail -3

你会看到读取的内容和字节数
在这里插入图片描述

卸载清理:

sudo rmmod char_device
sudo rm /dev/simple_char

在这里插入图片描述

通过这三个例子,你已经掌握了Linux驱动开发的基本流程和核心概念。从最简单的Hello World到带参数的模块,再到有实际读写功能的字符设备,这是一个循序渐进的学习过程。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值