内核,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 elsePWD := $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M=$(PWD) modulesclean: 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 elsePWD := $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M=$(PWD) modulesclean: 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