一个 Linux 系统,默认最大的主设备号是 255,结构体如下
// include/linux/fs.h
#define CHRDEV_MAJOR_HASH_SIZE 255
// /fs/char_dev.c
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
设备注册函数
// include/linux/fs.h
// 向内核注册了一个字符设备
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
/**
* __register_chrdev() - create and register a cdev occupying a range of minors
* @major: major device number or 0 for dynamic allocation 主设备号或者为 0 就是动态分配
* @baseminor: first of the requested range of minor numbers 次设备号,register_chrdev 直接使用了 0
* @count: the number of minor numbers required
* @name: name of this range of devices 设备名字
* @fops: file operations associated with this devices 文件结构体(Linux 万物皆文件,靠的就是这个结构体)
*
* If @major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If @major > 0 this function will attempt to reserve a device with the given
* major number and will return zero on success.
*
* Returns a -ve errno on failure.
*
* The name of this device has nothing to do with the name of the device in
* /dev. It only helps to keep track of the different owners of devices. If
* your module name has only one type of devices it's ok to use e.g. the name
* of the module here.
*/
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
chrdevbase.c
#include
#include
#include
#include
#include
#include
#define CHRDEVBASE_MAJOR 200 /* 主设备号 */
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
static char readbuf[100]; /* 读缓冲区 */
static char writebuf[100]; /* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};
/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
// printk("chrdevbase open!\r\n");
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue = 0;
/* 向用户空间发送数据 */
memcpy(readbuf, kerneldata, sizeof(kerneldata));
retvalue = copy_to_user(buf, readbuf, cnt);
if (retvalue == 0)
{
printk("kernel senddata ok!\r\n");
}
else
{
printk("kernel senddata failed!\r\n");
}
// printk("chrdevbase read!\r\n");
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrdevbase_write(struct file *filp,
const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue = 0;
/* 接收用户空间传递给内核的数据并且打印出来 */
retvalue = copy_from_user(writebuf, buf, cnt);
if (retvalue == 0)
{
printk("kernel recevdata:%s\r\n", writebuf);
}
else
{
printk("kernel recevdata failed!\r\n");
}
// printk("chrdevbase write!\r\n");
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int chrdevbase_release(struct inode *inode,
struct file *filp)
{
// printk("chrdevbase release!\r\n");
return 0;
}
/*
* 设备操作函数结构体
*/
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME,
&chrdevbase_fops);
if (retvalue < 0)
{
printk("chrdevbase driver register failed\r\n");
}
printk("chrdevbase_init()\r\n");
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
printk("chrdevbase_exit()\r\n");
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
/*
* LICENSE 和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chenzhiyong");
Makefile
obj-m += chrdevbase.o
#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#complie object
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#clean
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
make
,生成 *.ko
文件,它就是内核模块。insmod chrdevbase.ko
dmesg | tail -n 20
[ 9.261691] rfkill: input handler disabled
[ 345.463456] kauditd_printk_skb: 32 callbacks suppressed
[ 345.463458] audit: type=1400 audit(1697963648.793:43): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="/snap/core/16202/usr/lib/snapd/snap-confine" pid=2103 comm="apparmor_parser"
[ 345.463466] audit: type=1400 audit(1697963648.793:44): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="/snap/core/16202/usr/lib/snapd/snap-confine//mount-namespace-capture-helper" pid=2103 comm="apparmor_parser"
[ 346.309668] audit: type=1400 audit(1697963649.640:45): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.gnome-system-monitor.hook.configure" pid=2107 comm="apparmor_parser"
[ 346.777464] audit: type=1400 audit(1697963650.108:46): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.gnome-system-monitor.gnome-system-monitor" pid=2106 comm="apparmor_parser"
[ 348.399012] audit: type=1400 audit(1697963651.727:47): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap-update-ns.gnome-system-monitor" pid=2105 comm="apparmor_parser"
[ 348.403846] audit: type=1400 audit(1697963651.731:48): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap-update-ns.core" pid=2109 comm="apparmor_parser"
[ 348.405261] audit: type=1400 audit(1697963651.735:49): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap.core.hook.configure" pid=2110 comm="apparmor_parser"
[13529.015625] chrdevbase: loading out-of-tree module taints kernel.
[13529.015665] chrdevbase: module verification failed: signature and/or required key missing - tainting kernel
[13529.016265] chrdevbase driver register success
[13529.016266] chrdevbase_init()
cat /proc/devices
......
180 usb
189 usb_device
200 chrdevbase # 设备号 200 就是我们代码中的设备号
204 ttyMAX
216 rfcomm
226 drm
238 kfd
mknod /dev/chrdevbase c 200 0
test.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
static char usrdata[] = {"usr data!"};
/**
* @brief main 函数
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
char readbuf[100], writebuf[100];
if (argc != 3)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("Can't open file %s\r\n", filename);
return -1;
}
if (atoi(argv[2]) == 1)
{
/* 从驱动文件读取数据 */
retvalue = read(fd, readbuf, 50);
if (retvalue < 0)
{
printf("read file %s failed!\r\n", filename);
}
else
{
/* 读取成功,打印出读取成功的数据 */
printf("read data:%s\r\n", readbuf);
}
}
if (atoi(argv[2]) == 2)
{
/* 向设备驱动写数据 */
memcpy(writebuf, usrdata, sizeof(usrdata));
retvalue = write(fd, writebuf, 50);
if (retvalue < 0)
{
printf("write file %s failed!\r\n", filename);
}
}
/* 关闭设备 */
retvalue = close(fd);
if (retvalue < 0)
{
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
gcc -o test test.c
./test /dev/chrdevbase 1
, 往设备文件写: /test /dev/chrdevbase 2