Linux驱动学习笔记(一):helloworld驱动的编写和简单线程的调用

1.简单字符驱动

        字符设备驱动:因为软件操作设备是是以字节为单位进行的,是按照字节流进行读写操作的一种设备。典型的如LCD、蜂鸣器、SPI、触摸屏等驱动,都属于字符设备驱动的范畴。大部分的驱动程序都是属于字符设备驱动。

2.设备驱动程序功能

        应用程序位于用户空间,驱动程序位于内核空间。在Linux中用户空间不能直接调用内核空间的函数,所以必须要经过系统调用,应用程序才可以调用驱动程序的函数。

        

        

系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,运行在核心态,它完成以下的功能:

对设备初始化和释放;
把数据从内核传送到硬件和从硬件读取数据;
读取应用程序传送给设备文件的数据和回送应用程序请求的数据;
检测和处理设备出现的错误;
————————————————
版权声明:本文为CSDN博主「嵌入式悦翔园」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45172832/article/details/125534071

3.第一个操作源码helloworld

1、头文件

        初始化头文件

#include <linux/init.h>

        初始化加载模块头文件

#include <linux/module.h>

2、驱动的入口和出口

module_init();
module_exit();

3、开源信息声明和作者信息

MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR()  //添加模块作者信息

4、驱动基础框架

        

static int __init helloworld_init(void){
	
    
	return 0;
}

static void __exit helloworld_exit(void){
	
    
}

module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_AUTHOR("muhui");
MODULE_LICENSE("GPL");

5、先进入到你自己创建的文件夹中例如test/kernel/helloworld
6、安装环境依赖

sudo apt-get install build-essential linux-headers-$(uname -r)


build-essential Ubuntu系统中预安装的软件包,包含了常用的编译工具,例如gcc、g++、make、dpkg-dev等,可用于编译和构建软件

查看当前内核版本号

uname -r


linux-headers-$(uname -r):Ubuntu系统中用于安装当前内核版本头文件的软件包。头文件包含了内核的函数原型、宏定义、结构体定义等信息,可以用于编译内核模块和驱动程序。

按照这四步骤就可以搭建一个简单的基础框架了

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

static int __init helloworld_init(void){
	printk("helloworld_init\n");
	return 0;
}

static void __exit helloworld_exit(void){
	printk("helloworld_exit\n");
}
module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_AUTHOR("muhui");
MODULE_LICENSE("GPL");

        _init:该关键字标记表示该函数只会在模块初始化期间被调用,而不会在模块运行期间被调用。在驱动程序中,helloworld_init()函数被标记为_init.


        _exit:该关键字可以用于标记函数只会在模块退出期间被调用,而不会在模块运行期间被调用。在驱动程序中,helloworld_exit()函数被标记为_exit.


        module_init:该宏用于指定模块初始化函数,helloworld_int()函数被指定为驱动初始化函数。

        module_exit:该宏用于指定模块退出函数,helloworld_exit()函数被指定为驱动退出函数。

7、编写Makefile文件


描述helloworld.c编译成一个独立的lo文件源文件
在vscode中创建一个Makefile文件写入//obj-m +=helloworld.o;

# atemsys.ko: Provides usermode access to:
# 
#   - PCI configuration space
#   - Device IO memory
#   - Contiguous DMA memory
#   - Single device interrupt
# 
# Copyright (c) 2009 - 2018 acontis technologies GmbH, Ravensburg, Germany <info@acontis.com>
# All rights reserved.
#
# Author: K. Olbrich <k.olbrich@acontis.com>
#
# To compile and load the atemsys driver
#
# make modules 
# [ -c /dev/atemsys ] || sudo mknod /dev/atemsys c 101 0 
# sudo insmod atemsys.ko

CONFIG_MODULE_SIG=n

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

obj-m += helloworld.o

all: modules

modules:
    $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules

modules_install:
    $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules_install

clean:   
    $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules clean

obj-[x]:用于描述需要编译的目标的属性

obj-y:表示将指定对象编译为内核的一部分(即静态链接),这意味着该对象将包含在内核映像中,无法卸载或修改。通常情况下,obj-y用于编译内核的核心组件,例如进程调度器、文件系统、网络协议栈等。
obj-m:表示将指定对象编译为可加载模块(即动态链接),这意味着该对象为单独的模块编译,并在需要时加载到内核中。通常情况下,obj-m用于编译内核的外围组件,例如设备驱动程序、文件系统模块、网络协议栈模块等。
obj-$(CONFIG_YOUR_DEF):同时也可是一个变量。例如放在Kcongfig中共用户配置,本质还是obj-y和obj-m

8、编译成KO文件


编译当前目录的内核模块,生成对应的编译产物(最重要的是ko驱动文件)
在终端中

make -C /lib/modules/$(uname -r)/build M=`pwd` modules

可以用cd进/lib/modules/$(uname -r)/build中用ls查看内核的一个构建目录,查看完记得退回自己创建的文件夹中

-C:选项用于指定工作目录(或Makefile文件的位置),让命令在指定的目录下执行,而不是在当前目录下执行。
-M:选项用于指定模块源代码目录,让命令在指定的目录下查找模块源代码(pwd获取当前目录路径)
modules:参数用于指定要编译的目标是模块文件。Linux内核编译中,make命令可以使用modules参数来指定只编译模块文件,而不编译整个内核。


9、安装/查看/卸载驱动


驱动的安装卸载及对应的内核log查看
insmod命令安装内核模块

sudo insmod helloworld.ko

lsmod查看当前运行的内核模块
#用grep过滤

lsmod | grep -Ei helloworld

rmmod卸载内核模块

sudo rmmod helloworld

dmesg查看内核日志
#log 太多 用grep 过滤一下

dmesg | grep -Ei helloworld

4.简单线程调用

1、什么时线程

        在进行进程切换时,需要不断刷新cache(缓存),为了减少cahce刷新时的资源消耗,我们引入了轻量级进程——线程。

        进程称为最小的资源分配单位,线程称为CPU最小的任务调度单位。

2、线程的特点

        同意个进程创建的多个线程,它们共用同一个进程的地址空间。

        进程创建线程后,我们把原来进程也称为线程,并且为主线程。

3、函数例子

1)Helloworld驱动文件

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/poll.h>
#include<linux/proc_fs.h>
#include<linux/skbuff.h>
#include<linux/seq_file.h>

#include<linux/kdev_t.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/uaccess.h>

static int major = 200;

struct device *class_dev = NULL;
struct class *cls;

static int helloworld_open(struct inode* inode,struct file* file)
{
    printk("helloworld_open()\n");
    return 0;
}

static int helloworld_release(struct inode* inode,struct file* file)
{
    printk("helloworld_release()\n");
    return 0;
}

#define KMAX_LEN 32
char kbuf[KMAX_LEN] = "kernel";

static ssize_t helloworld_read(struct file *filep,char __user* buf,size_t size,loff_t*  pos)
{
    int error;
    if(size > strlen(kbuf))
    {
        size = strlen(kbuf);
    }

    if(copy_to_user(buf,kbuf,size))
    {
        error = -EFAULT;
        return error;
    }
    return size;
}

static ssize_t helloworld_write(struct file* filep,const char __user* buf,size_t size,loff_t* pos)
{
    int error;
    if(size > KMAX_LEN)
    {
        size = KMAX_LEN;
    }
    memset(kbuf,0,sizeof(kbuf));
    if(copy_from_user(kbuf,buf,size))
    {
        error = -EFAULT;
        return error;
    }
    printk("%s\n",kbuf);
    return size;
}

static struct file_operations helloworld_ops={
    .owner=THIS_MODULE,
    .open=helloworld_open,
    .read=helloworld_read,
    .write=helloworld_write,
    .release=helloworld_release,
};

static int __init helloworld_init(void){
    
    int ret;

    printk("helloworld_init()\n");
    ret = register_chrdev(major,"huimu",&helloworld_ops);
    if(ret<0)
    {
        printk("register_chrdev fail\n");
    }
    return 0;
}

static void __exit helloworld_exit(void){
    printk("helloworld_exit()\n");
    unregister_chrdev(major,"huimu");
    
    
}
module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_AUTHOR("muhui");
MODULE_LICENSE("GPL");


进入文件目录ko文件

make -C /lib/modules/$(uname -r)/build M=`pwd` modules
一般来说直接make就可以了

2)应用层test.c文件

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

int main()
{
    int fd;
    int len;
    char buf[64] ={0};
    char buff[64] ="huimu";

    fd = open("/dev/test",O_RDWR);

    if(fd<0)
    {
        perror("open fail\n");
        return 0;
    }
    len =read(fd,buf,64);
    buf[len]='\0';
    printf("read:%s len = %d\n",buf,len);
    strcpy(buf,"huimu");
    
    len = write(fd,buff,strlen(buff));
    printf("len = %d\n",len);
    
    printf("open ok\n");
    close(fd);
    return 0;
}

3)Makefile文件

# atemsys.ko: Provides usermode access to:
# 
#   - PCI configuration space
#   - Device IO memory
#   - Contiguous DMA memory
#   - Single device interrupt
# 
# Copyright (c) 2009 - 2018 acontis technologies GmbH, Ravensburg, Germany <info@acontis.com>
# All rights reserved.
#
# Author: K. Olbrich <k.olbrich@acontis.com>
#
# To compile and load the atemsys driver
#
# make modules 
# [ -c /dev/atemsys ] || sudo mknod /dev/atemsys c 101 0 
# sudo insmod atemsys.ko

CONFIG_MODULE_SIG=n

KERNELDIR ?= /lib/modules/$(shell uname -r)/build

obj-m += helloworld.o

all: modules

modules:
    $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules

modules_install:
    $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules_install

clean:   
    $(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules clean

4)编译模块

make -C /lib/modules/$(uname -r)/build M=`pwd` modules


5)加载模块

sudo insmod 文件名.ko(例如helloworld.ko)

lsmod查看当前运行的内核模块
#用grep过滤

lsmod | grep -Ei helloworld

6)创建设备节点文件

1、输入 sudo mknod /dev/test(创建的文件名) c 200(你自己写的节点数) 0
如果已经存在的话sudo rm /dev/test(文件名) 删除设备节点文件然后再创建

2、可以用ls /dev/test(文件名) -l这个命令来查看是否生成这个文件
vim /proc/devices可以进去查看200是否写入

3、创建测试文件touch test.c并编写测试内容
   编译测试文件gcc test.c或者gcc.test.c -o run
     进入管理者模式sudo su
   dmesg -c清除
   ./a.out或者./run运行,查看读取
   dmesg查看open和close查看写入


4、rmmod卸载内核模块

sudo rmmod helloworld

dmesg查看内核日志
#log 太多 用grep 过滤一下

dmesg | grep -Ei helloworld

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值