文章目錄
- 目的
- 相關資料參考
- 實驗
- 驅動程序-timer_dev.c
- 編譯文件-Makefile
- 測試程序-timer.c
- 分析
- 加載驅動-運行測試程序
- 總結
目的
通過定時器timer_list、字符設備、規避競爭關系-原子操作,綜合運用 實現一個程序,加深之前知識的理解。
- 實現字符設備驅動框架, 自動生成設備節點。
- 根據上一小節學到的知識, 實現秒計時。
- 通過原子變量來記錄遞增的秒數, 避免競爭的發生。
- 通過用戶空間和內核空間的數據交換, 將記錄的秒數傳遞到應用空間, 并通過應用程
序打印出來
相關資料參考
驅動-原子操作
驅動-Linux定時-timer_list
實驗
驅動程序-timer_dev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>struct device_test
{dev_t dev_num; //定義dev_t類型變量來表示設備號int major,minor; //定義int 類型的主設備號和次設備號struct cdev cdev_test; //定義字符設備struct class *class; //定義結構體變量class 類struct device *device; // 設備int sec; //秒};
atomic64_t v = ATOMIC_INIT(0);//定義原子類型變量v,并定義為0struct device_test dev1; static void function_test(struct timer_list *t);//定義function_test定時功能函數
DEFINE_TIMER(timer_test,function_test);//定義一個定時器
static void function_test(struct timer_list *t)
{atomic64_inc(&v);//原子變量v自增dev1.sec = atomic_read(&v);//將讀取到的原子變量v,賦值給secprintk("the sec is %d\n",dev1.sec);mod_timer(&timer_test,jiffies_64 + msecs_to_jiffies(1000));//使用mod_timer函數將定時時間設置為一秒后
}/*打開設備函數*/
static int open_test(struct inode *inode,struct file *file){file->private_data=&dev1;//設置私有數據printk("\n this is open_test \n");add_timer(&timer_test); //添加一個定時器return 0;};static ssize_t read_test(struct file *file, char __user *buf, size_t size, loff_t *off)
{if(copy_to_user(buf,&dev1.sec,sizeof(dev1.sec))){//使用copy_to_user函數將sec傳遞到應用層printk("copy_to_user error \n");return -1;}return 0;
}static int release_test(struct inode *inode,struct file *file)
{del_timer(&timer_test);//刪除一個定時器printk("\nthis is release_test \n");return 0;
}static struct file_operations fops_test = {.owner=THIS_MODULE,//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊.open = open_test,//將open字段指向chrdev_open(...)函數.read = read_test,//將open字段指向chrdev_read(...)函數.release = release_test,//將open字段指向chrdev_release(...)函數
};//定義file_operations結構體類型的變量cdev_test_opsstatic int __init timer_dev_init(void)//驅動入口函數
{if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0){printk("alloc_chrdev_region is error\n");} printk("alloc_chrdev_region is ok\n");dev1.major=MAJOR(dev1.dev_num);//通過MAJOR()函數進行主設備號獲取dev1.minor=MINOR(dev1.dev_num);//通過MINOR()函數進行次設備號獲取printk("major is %d\n",dev1.major);printk("minor is %d\n",dev1.minor);使用cdev_init()函數初始化cdev_test結構體,并鏈接到cdev_test_ops結構體cdev_init(&dev1.cdev_test,&fops_test);dev1.cdev_test.owner = THIS_MODULE;//將owner字段指向本模塊,可以避免在模塊的操作正在被使用時卸載該模塊 cdev_add(&dev1.cdev_test,dev1.dev_num,1);printk("cdev_add is ok\n");dev1.class = class_create(THIS_MODULE,"test");//使用class_create進行類的創建,類名稱為class_testdevice_create(dev1.class,NULL,dev1.dev_num,NULL,"test");//使用device_create進行設備的創建,設備名稱為device_testreturn 0;
}
static void __exit timer_dev_exit(void)//驅動出口函數
{cdev_del(&dev1.cdev_test);//使用cdev_del()函數進行字符設備的刪除unregister_chrdev_region(dev1.dev_num,1);//釋放字符驅動設備號 device_destroy(dev1.class,dev1.dev_num);//刪除創建的設備class_destroy(dev1.class);//刪除創建的類printk("module exit \n");}
module_init(timer_dev_init);//注冊入口函數
module_exit(timer_dev_exit);//注冊出口函數
MODULE_LICENSE("GPL v2");//同意GPL開源協議
MODULE_AUTHOR("wang fang chen "); //作者信息
編譯文件-Makefile
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += timer_dev.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean
測試程序-timer.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char *argv[]){int fd;//定義int類型的文件描述符fdint count;//定義int類型記錄秒數的變量countfd = open("/dev/test",O_RDWR);//使用open()函數以可讀可寫的方式打開設備文件while(1){ read(fd,&count,sizeof(count));//使用read函數讀取內核傳遞來的秒數sleep(1);printf("num is %d\n",count);}return 0;
}
編譯測試程序變成可執行文件:
aarch64-linux-gnu-gcc -o timer timer.c
分析
通過函數 read(fd,&count,sizeof(count)) 調用, 為什么count 值會變化。 有的人會問,count 默認值是0 ,read 讀取。
ssize_t read(int fd, void *buf, size_t count);
函數read 參數解釋:
- fd 參數代表文件描述符,是一個整數,用于標識要讀取的文件或設備。
- buf 參數是一個指向緩沖區的指針,read 函數會將讀取的數據存儲在這個緩沖區中。
- count 參數指定了最多要讀取的字節數。
這里好好思考一下如下,會不會有問題,需要理解清楚,思考清楚。
測試程序每隔1秒讀取count值 放到$count 緩沖區, 然后讀取;內核端每隔一秒增加sec 值,通過原子操作+1,然后內核層copy_to_user。 用戶層在緩沖區讀取的值就是內核層每隔一秒通過 buffer 傳遞過來的。
加載驅動-運行測試程序
insmod timer_dev.ko./timer
實際返回值,如下:
總結
這個程序實驗,用到了原子操作+定時器timer_list+字符設備操作。 簡要了解即可,加深前面知識印象。