内核,shell,文件系统,应用程序构成了基本的linux结构
Kernel
内核又分为内存管理,进程管理,设备驱动程序,文件系统和网络管理等
shell
提供了一个界面,用户通过这个界面访问操作系统内核的服务。
文件系统
linux有很丰富的文件系统,Linux 中最普遍使用的文件系统是 Ext2,还有nfs,ext4,ext3等等。linux将独立的文件系统组合成为了一个层次的树状结构,新的文件系统可以通过挂载到某一个目录进行访问,这些功能的基础是虚拟文件系统VFS,linux除进程以外全部视为文件。
应用程序
上层开发者根据对应的API,或者SDK来开发的软件
linux系统将设备分成三个基本类型
- 字符设备
- 块设备
- 网络接口
根据资料,我们对设备的所有操作基本上都可以简化成open、close、read、write、io control这几个操作。下面进行一个小的实验。
简单的驱动入门
创建一个文件夹
mkdir mydrvice
创建一个文件 mydrvice1.c
vim mydrvice1.c
写入以下内容
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/module.h>
//内核可以识别的许可证有“GPL”,“GPL v2”等等,如果模块没有显示地标记许可证,会被认为是私有开发
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin"); //模块作者
MODULE_DESCRIPTION("This is just a hello module!\n"); //模块的描述注释说明
// MODULE_VERSION 代码修订号
// MODULE_ALIAS 模块的别名
// MODULE_DEVICE_TABLE 告诉用户空间模块支持的设备
static int __init hello_init(void)
{
printk(KERN_EMERG "hello, init\n"); // printk可以用来调试内核,查看值
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_EMERG "hello, exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
编写Makefile
vim Makefile
ifneq ($(KERNELRELEASE),)
obj-m := mydrvice1.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif
然后make一下
make
输入insmod 安装驱动程序
使用insmod安装模块,内部调用了 sys_init_module系统调用,在sys_init_module内部调用了load_module,把mydrvice1.ko创建成一个内核模块,返回一个struct module结构体,内核中便以这个结构体代表这个内核模块。
module->state(module结构体下的state枚举变量)
- MODULE_STATE_LIVE //正常使用中
- MODULE_STATE_COMING //正在被加载
- MODULE_STATE_GOING //正在被卸载
load_module函数中完成模块的部分创建工作后,把状态置为 MODULE_STATE_COMING
sys_init_module函数中完成模块的全部初始化工作后(包括把模块加入全局的模块列表,调用模块本身的初始化函数),把模块状态置为MODULE_STATE_LIVE
使用rmmod工具卸载模块时,会调用系统调用 delete_module,会把模块的状态置为MODULE_STATE_GOING。
insmod mydrvice1.ko
输入dmesg
dmesg
结果输出
[ 253.346485] hello, init
输入rmmod 安装驱动程序
rmmod mydrvice1.ko
输入dmesg
dmesg
结果输出
[ 969.532449] hello, exit
字符设备驱动入门
创建char文件夹并且进入
mkdir char && cd char
编写char.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
static struct cdev chr_dev;
static dev_t ndev;
//linux内核中表示不同的设备是通过major 和minor number实现的,通过major和minor Number来加载相应的驱动程序。
//major number:表示不同的设备类型
//minor number:表示同一个设备的的不同分区
static int chr_open(struct inode* nd, struct file* filp)
{
int major ;
int minor;
major = MAJOR(nd->i_rdev);
minor = MINOR(nd->i_rdev);
printk("chr_open, major = %d, minor = %d\n", major, minor);
return 0;
}
static ssize_t chr_read(struct file* filp, char __user* u, size_t sz, loff_t* off)
{
printk("chr_read process!\n");
return 0;
}
struct file_operations chr_ops = {
//指定初始化 C99标准
.owner = THIS_MODULE, //THIS_MODULE是一个宏指向当前的本模块,#define THIS_MODULE (&__this_module)
.open = chr_open, //应该是把open操作变成了自定义的函数了
.read = chr_read //应该是把read操作变成了自定义的函数了
};
static int demo_init(void)
{
int ret;
cdev_init(&chr_dev, &chr_ops); //字符设备的注册
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); //动态的申请注册一个设备号
if(ret < 0 )
{
return ret;
}
printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&chr_dev, ndev, 1);//添加字符设备
if(ret < 0)
{
return ret;
}
return 0;
}
static void demo_exit(void)
{
printk("demo_exit process!\n");
cdev_del(&chr_dev);
unregister_chrdev_region(ndev, 1);
}
//实现模块加载和卸载入口函数
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin");
MODULE_DESCRIPTION("A simple device example!");
编写Makefile
ifneq ($(KERNELRELEASE),)
obj-m := char.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif
安装模块
insmod char.ko
输入dmesg
dmesg
显示
[ 3539.169209] demo_init(): major = 241, minor = 0
以利用major,minor直接创建设备节点,输入
mknod /dev/chr_dev c 241 0
验证
[root@localhost char]# ls /dev/chr_dev/dev/chr_dev
编写test.c来验证
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define CHAR_DEV_NAME "/dev/chr_dev"
int main()
{
int ret;
int fd;
char buf[32];
fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY); //只读 | O_NDELAY?
if(fd < 0)
{
printf("open failed!\n");
return -1;
}
//返回的文件描述符,然后开始读
read(fd, buf, 32);
close(fd);
return 0;
}
然后
gcc -c test.c -o test
再
./test
最后使用dmesg查看
[ 3853.632993] chr_read process!
控制两种模式
MODE_1 MODE_2
使用open打开设备,先write写入name,再read返回
如果是MODE_1,返回 hello name(3环)
如果是MODE_2,返回 welcome name(3环)
#include <linux/module.h> // included for all kernel modules
#include <linux/kernel.h> // included for KERN_INFO
#include <linux/init.h> // included for __init and __exit macros
#include <linux/scpi_protocol.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <linux/fs.h> // file_operation is defined in this header
#include <linux/device.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin");
MODULE_DESCRIPTION("Driver as a test case");
static int majorNumber;
static struct class* test_module_class = NULL;
static struct device* test_module_device = NULL;
#define DEVICE_NAME "test" //定义设备名称
#define CLASS_NAME "test_module"
//函数原型
static long test_module_ioctl(struct file *, unsigned int, unsigned long);
static int __init test_init(void);
static void __exit test_exit(void);
//linux/fs.h中的file_operations结构体列出了所有操作系统允许的对设备文件的操作。
//在我们的驱动中,需要将其中需要的函数进行实现。
//下面这个结构体就是向操作系统声明,那些规定好的操作在本模块里是由哪个函数实现的。
static const struct file_operations test_module_fo = {
.owner = THIS_MODULE,
.unlocked_ioctl = test_module_ioctl, //unlocked_ioctl是由本模块中的test_module_ioctl()函数实现的
};
//本模块ioctl回调函数的实现
static long test_module_ioctl(struct file *file, unsigned int cmd, unsigned long param)
{
switch(cmd)
{
case MODE_1:
filp->f_pos += (int)arg;
case MODE_2:
filp->f_pos += (int)arg;
}
/* ioctl回调函数中一般都使用switch结构来处理不同的输入参数(cmd) */
switch(cmd){
case 0:
{
printk(KERN_INFO "[TestModule:] Inner function (ioctl 0) finished.\n");
break;
}
default:
printk(KERN_INFO "[TestModule:] Unknown ioctl cmd!\n");
return -EINVAL;
}
return 0;
}
static int __init test_init(void){
printk(KERN_INFO "开始进行初始化\n");
// 在加载本模块时,首先向操作系统注册一个chrdev,也即字节设备,三个参数分别为:主设备号(填写0即为等待系统分配),设备名称以及file_operation的结构体。返回值为系统分配的主设备号。
majorNumber = register_chrdev(0, DEVICE_NAME, &test_module_fo);
if(majorNumber < 0){
printk(KERN_INFO "注册主设备号失败 \n");
return majorNumber;
}DEVICE_NAME
printk(KERN_INFO "注册主设备号成功 %d. \n", majorNumber);
//接下来,注册设备类
test_module_class = class_create(THIS_MODULE, CLASS_NAME);
if(IS_ERR(test_module_class)){
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_INFO "注册设备类失败\n");
return PTR_ERR(test_module_class);
}
printk(KERN_INFO "注册设备类成功\n");
//最后,使用device_create函数注册设备驱动
test_module_device = device_create(test_module_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(test_module_device)){ // Clean up if there is an error
class_destroy(test_module_class); // Repeated code but the alternative is goto statements
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ALERT "注册驱动失败\n");
return PTR_ERR(test_module_device);
}
printk(KERN_INFO "注册模块驱动成功\n");
return 0;
}
static void __exit test_exit(void)
{
//退出时,依次清理生成的device,class和chrdev。这样就将系统/dev下的设备文件删除,并自动注销了/proc/devices的设备。
printk(KERN_INFO "[TestModule:] Start to clean up module.\n");
device_destroy(test_module_class, MKDEV(majorNumber, 0));
class_destroy(test_module_class);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_INFO "[TestModule:] Clean up successful. Bye.\n");
}
module_init(test_init);
module_exit(test_exit);
实验
控制两种模式
MODE_1 MODE_2
使用open打开设备,先write写入name,再read返回
如果是MODE_1,返回 hello name(3环)
如果是MODE_2,返回 welcome name(3环)
编写char3.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include "test.h"
static struct cdev chr_dev;
static dev_t ndev;
static char name[32];
MODULE_LICENSE("GPL");
static int chr_open(struct inode* nd, struct file* filp)
{
int major;
int minor;
major = MAJOR(nd->i_rdev);
minor = MINOR(nd->i_rdev);
printk("chr_open, major = %d, minor = %d\n", major, minor);
return 0;
}
//filp是文件指针,count是请求传输的数据量,buff参数指向数据缓存,最后offp之处文件当前的访问位置。
//ssize_t xxx_read(struct file* filp,char __user* buff,size_t count,loff_t*offp);
static ssize_t chr_read(struct file* filp, char __user* buff, size_t count, loff_t* off)
{
int j;
for (j = 0; j < count; j++ )
{
buff[j] = name[j];
}
return 0;
}
static ssize_t chr_write(struct file* filp,const char __user* buff,size_t count,loff_t* off)
{
int i;
printk( "write--%ld\n", count );
printk( "write--%s\n", buff );
for (i = 0; i < count; i++ )
{
name[i] = buff[i];
}
printk( "write--name = %s\n", name );
return 0;
}
//本模块ioctl回调函数的实现
long char_ioctl(struct file *filp, unsigned int cmd, unsigned long param)
{
int i;
//ioctl回调函数中一般都使用switch结构来处理不同的输入参数(cmd)
printk("char_ioctl: cmd=%d\n", cmd);
switch(cmd)
{
case MODULE_ONE:
printk("1\n");
for(i=31 ; i>=0 ;i--){
if(name[i]!="\0"){
name[i+6] = name[i];
}
}
name[0]='H'; //没想到好的算法,暂时就先这样赋值
name[1]='e';
name[2]='l';
name[3]='l';
name[4]='o';
name[5]=' ';
break;
case MODULE_TWO:
printk("2\n");
for(i=31 ; i>=0 ;i--){
if(name[i]!="\0"){
name[i+8] = name[i];
}
}
name[0]='W';
name[1]='e';
name[2]='l';
name[3]='c';
name[4]='o';
name[5]='m';
name[6]='e';
name[7]=' ';
break;
}
return 0;
}
struct file_operations chr_ops = {
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read,
.write = chr_write,
.unlocked_ioctl = char_ioctl
};
static int demo_init(void)
{
int ret;
cdev_init(&chr_dev, &chr_ops); //字符设备的注册
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); //动态的申请注册一个设备号
if(ret < 0 )
{
printk("wrong 2\n");
return ret;
}
printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&chr_dev, ndev, 1);//添加字符设备
if(ret < 0)
{
printk("wrong 2\n");
return ret;
}
return 0;
}
static void demo_exit(void)
{
printk("demo_exit process!\n");
cdev_del(&chr_dev);
unregister_chrdev_region(ndev, 1);
}
//实现模块加载和卸载入口函数
module_init(demo_init);
module_exit(demo_exit);
编写test.h
#ifndef SCULL_H_
#define SCULL_H_
//定义幻数
#define SCULL_IOC_MAGIC '$'
//定义命令->
//数据清零
#define MODULE_ONE _IO(SCULL_IOC_MAGIC, 0)
#define MODULE_TWO _IO(SCULL_IOC_MAGIC, 1)
#endif
编写测试案例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "test.h"
#define CHAR_DEV_NAME "/dev/chr_dev"
int main()
{
int ret;
int fd;
char name[32]={"Kevin"};
char temp[50];
fd = open(CHAR_DEV_NAME, O_RDWR);
//fd = open(CHAR_DEV_NAME, O_RDWR);
if(fd < 0)
{
printf("open failed!\n");
return -1;
}
//返回的文件描述符,然后开始读
write(fd, name, 32);
int r = ioctl(fd, MODULE_ONE);
printf("r=%d\n", r);
read(fd, temp, 32);
printf("\n%s\n", temp);
write(fd, name, 32);
ioctl(fd, MODULE_TWO);
read(fd, temp, 32);
printf("\n%s\n", temp);
close(fd);
return 0;
}
编写Makefile
ifneq ($(KERNELRELEASE),)
obj-m := char3.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif
然后生成
make
再安装模块
insmod char3
以利用major,minor直接创建设备节点,输入
mknod /dev/chr_dev c 237 0
编译测试程序
gcc test.c -o test
测试
./test
输出
[root@localhost char2]# ./testr=0Hello KevinWelcome Kevin
在dmesg日志里面
[ 6416.836095] chr_open, major = 237, minor = 0
[ 6416.836097] write--32
[ 6416.836098] write--Kevin
[ 6416.836098] write--name = Kevin
[ 6416.836099] char_ioctl: cmd=9216
[ 6416.836099] 1
[ 6416.836155] write--32
[ 6416.836155] write--Kevin
[ 6416.836156] write--name = Kevin
[ 6416.836157] char_ioctl: cmd=9217
[ 6416.836157] 2