想要理解一個東西的本質,就要站在很高的層次上,去看清整體,明白整體是怎么回事,具體的細節可以不知道,可以不會寫這個代碼,不明白代碼功能是怎么實作的,但必須從宏觀的角度知道,這個東西大概是怎么回事
譬如:需要知道作業系統主要是用來干嘛的
答:管理底層硬體,提供運行環境
1. 記憶體分布
首先要有空間感
32位系統最大可訪問4G的空間,那4G空間如何給系統分配

1G分給內核
3G分給用戶
說明:最下面是0地址
2. 整體概念
作業系統分為三個大的層次:應用層、作業系統層、硬體層

3. linux應用編程API這里不做說明
4. linux驅動是內核的一部分
(1)驅動就是內核中的硬體設備管理模塊
(2)驅動作業在內核態
單核cpu同時只能執行一句代碼
整個系統既有應用程式又有作業系統
作業系統是一堆代碼,應用程式也是一堆代碼
這兩堆代碼不可能同時運行
| 作業在作業系統 | 內核態 |
|---|---|
| 作業在應用程式 | 用戶態 |
區別在于權限問題
| 內核態運行 |
|---|
cpu可以訪問任意訪問各種記憶體,想訪問哪段記憶體就訪問哪段記憶體,不用擔心訪問出錯,作業系統在設計時給作業系統賦予了最高的訪問限制
| 用戶態運行 |
|---|
cpu只能訪問被允許用戶態訪問的記憶體空間,不能任意訪問,否則會導致segmentation fault

(3)驅動程式故障可能導致整個內核崩潰
因為驅動時作業在內核態的,是沒有權限問題的
5. 內核和應用程式、根檔案系統的關聯
內核和應用程式
- 應用程式不屬于內核,而是在內核之上的
- 應用程式作業在用戶態
- 應用程式故障不會導致內核崩潰
- 應用程式通過內核定義的API介面來呼叫內核作業
總結:
1 應用程式是最終目標
2 內核就是為應用程式提供底層資源的管理者
內核與根檔案系統
根檔案系統為作業系統提供根目錄
皮之不存,毛將焉附
根目錄從根檔案系統提供根目錄
其他的檔案系統是掛載到根檔案系統上的

什么叫掛載
把需要掛載的檔案系統掛載到根目錄的某一個掛載節點上
行程1存放在根檔案系統中
作業系統在運行程序中,剛開始是一直在內核態的
最后才會跑到用戶態 應用程式中,所以會有一個程序
從內核態到用戶態的躍遷,那個躍遷的點就是行程1

行程1是整個系統第一個應用程式
所以沒有根檔案系統,就無法進入用戶態
說明:剛開始內核啟動是沒有根檔案的,內核裝載了根檔案系統后,把根檔案系統的根目錄作為整個作業系統的根目錄,所以就有了根目錄
總結:根檔案系統為作業系統提供 根目錄 行程1

6. 應用程式如何呼叫驅動
設備檔案會一步步索引找到對應的驅動
例如:作業系統裝了20個硬體設備,那就有20個驅動,每個驅動就是一個file_operation結構體,這20個file_operations結構體組成一個陣列
內核就是由陣列來管理驅動的,所以內核里面有一個陣列,陣列有255個元素,每個陣列元素內部都可以放一個驅動,每個驅動都是一個file_operations結構體

驅動設備檔案的創建
- 何為設備檔案
用來描述驅動,用來索引驅動,有了設備檔案之后可以通過設備檔案呼叫驅動
- 設備檔案怎么找到驅動
設備號 = 主設備號 + 次設備號 -> 相當于陣列的下標
- 使用mknod創建設備檔案:mknod /dev/xxx c 主設備號 次設備號

7. 開啟驅動開發之路
linux設備驅動分類
字符設備驅動、塊設備驅動、網路設備驅動
驅動開發的步驟
(1)驅動原始碼撰寫、Makefile撰寫、編譯
(2)insmod裝載模塊、測驗、rmmod卸載模塊
常用的模塊操作命令
(1)lsmod(list module,將模塊串列顯示),功能是列印出當前內核中已經安裝的模塊串列
(2)insmod(install module,安裝模塊),功能是向當前內核中去安裝一個模塊,用法是insmod xxx.ko
(3)modinfo(module information,模塊資訊),功能是列印出一個內核模塊的自帶資訊,,用法是modinfo xxx.ko
(4)rmmod(remove module,卸載模塊),功能是從當前內核中卸載一個已經安裝了的模塊,用法是rmmod xxx(注意卸載模塊時只需要輸入模塊名即可,不能加.ko后綴)
模塊的安裝
- 先lsmod再insmod看安裝前后系統內模塊記錄,實踐測驗標明內核會將最新安裝的模塊放在lsmod顯示的最前面,
- insmod與module_init宏,模塊源代碼中用module_init宏宣告了一個函式(在我們這個例子里是chrdev_init函式),
作用就是指定chrdev_init這個函式和insmod命令系結起來,也就是說當我們insmod module_test.ko時,
insmod命令內部實際執行的操作就是幫我們呼叫chrdev_init函式,
照此分析,那insmod時就應該能看到chrdev_init中使用printk列印出來的一個chrdev_init字串,但是實際沒看到,原因是ubuntu中攔截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了, - 模塊安裝時insmod內部除了幫我們呼叫module_init宏所宣告的函式外,實際還做了一些別的事
(譬如lsmod能看到多了一個模塊也是insmod幫我們在內部做了記錄),但是我們就不用管了,
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#define MAJOR 200
#define NAME "testchar"
int major;
// 自定義一個file_operations結構體變數,并且去填充
static const struct file_operations test_fops = {
};
// 模塊安裝函式
static int __init chrdev_init(void)
{
major = register_chrdev(0, NAME, &test_fops);
return 0;
}
// 模塊下載函式
static void __exit chrdev_exit(void)
{
unregister_chrdev(major, NAME);
}
module_init(chrdev_init); //宏定義 module.h中 insmod命令執行時,其實執行的是這個宏,除了執行函式還有別的事情,譬如lsmod查看等
module_exit(chrdev_exit); //宏定義 module.h中 rmmod命令執行時,其實執行的是這個宏
// MODULE_xxx這種宏作用是用來添加模塊描述資訊
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("CX"); // 描述模塊的作者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹資訊
MODULE_ALIAS("alias xxx"); // 描述模塊的別名資訊
函式修飾符
__init
本質上是個宏定義,在內核源代碼中就有#define __init xxxx,
這個__init的作用就是將被他修飾的函式放入.init.text段中去(本來默認情況下函式是被放入.text段中),
整個內核中的所有的這類函式都會被聯結器鏈接放入.init.text段中,
所以所有的內核模塊的__init修飾的函式其實是被統一放在一起的,內核啟動時統一會加載.init.text段
驅動編譯的Makefile分析
(1)KERN_DIR,變數的值就是我們用來編譯這個模塊的內核原始碼樹的目錄
(2)obj-m += module_test.o,這一行就表示我們要將module_test.c檔案編譯成一個模塊
(3)make -C $(KERN_DIR) M=pwd modules 這個命令用來實際編譯模塊,作業原理就是:利用make -C進入到我們指定的內核原始碼樹目錄下,然后在原始碼目錄樹下借用內核原始碼中定義的模塊編譯規則去編譯這個模塊,編譯完成后把生成的檔案還拷貝到當前目錄下,完成編譯,
總結:模塊的makefile非常簡單,本身并不能完成模塊的編譯,而是通過make -C進入到內核原始碼樹下借用內核原始碼的體系來完成模塊的編譯鏈接的,這個Makefile本身是非常模式化的,3和4部分是永遠不用動的,只有1和2需要動,1是內核原始碼樹的目錄,你必須根據自己的編譯環境
解釋說明
我們寫的Makefile相當于一個快捷方式,沒有什么用,只有參考到內核原始碼樹的Makefile來進行編譯
在編譯內核的modules,具體原理可以不知道
# 開發板的linux內核的原始碼樹目錄
KERN_DIR = /root/driver/kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
驅動中如何操控硬體
這是一個硬體簡單GPIO驅動
#define MYMAJOR 200
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON //虛擬地址
#define GPJ0DAT S5PV210_GPJ0DAT //虛擬地址
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
int mymajor;
// 自定義一個file_operations結構體變數,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 慣例,直接寫即可
};
// 模塊安裝函式
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
// 在module_init宏呼叫的函式中去注冊字符設備驅動
// major傳0進去表示要讓內核幫我們自動分配一個合適的空白的沒被使用的主設備號
// 內核如果成功分配就會回傳分配的主設備好;如果分配失敗會回傳負數
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
//insmod命令執行時所做的硬體操作
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
// 模塊卸載函式
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// 在module_exit宏呼叫的函式中去注銷字符設備驅動
unregister_chrdev(mymajor, MYNAME);
// rmmod命令執行時所做的硬體操作
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); // 滅
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx這種宏作用是用來添加模塊描述資訊
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston"); // 描述模塊的作者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹資訊
MODULE_ALIAS("alias xxx"); // 描述模塊的別名資訊
insmod裝載驅動,查看列印結果

這樣的寫法并不好,轉載時引腳輸出高電平,卸載時輸出低電平,可以改進一下
常規的寫法為
| 在驅動里只負責操作硬體,與控制邏輯相關的放入應用程式中 |
|---|
舉例說明:
驅動代碼如下
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/string.h>
#define MYMAJOR 200
#define MYNAME "testchar"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
int mymajor;
char kbuf[100]; // 內核空間的buf
static int test_chrdev_open(struct inode *inode, struct file *file)
{
// 這個函式中真正應該放置的是打開這個設備的硬體操作代碼部分
// 但是現在暫時我們寫不了這么多,所以用一個printk列印個資訊來做代表,
printk(KERN_INFO "test_chrdev_open\n");
rGPJ0CON = 0x11111111;
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮
return 0;
}
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "test_chrdev_release\n");
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
return 0;
}
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_read\n");
ret = copy_to_user(ubuf, kbuf, count);
if (ret)
{
printk(KERN_ERR "copy_to_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_to_user success..\n");
return 0;
}
// 寫函式的本質就是將應用層傳遞過來的資料先復制到內核中,然后將之以正確的方式寫入硬體完成操作,
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "test_chrdev_write\n");
// 使用該函式將應用層傳過來的ubuf中的內容拷貝到驅動空間中的一個buf中
//memcpy(kbuf, ubuf); // 不行,因為2個不在一個地址空間中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, ubuf, count);
if (ret)
{
printk(KERN_ERR "copy_from_user fail\n");
return -EINVAL;
}
printk(KERN_INFO "copy_from_user success..\n");
if (kbuf[0] == '1')
{
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
else if (kbuf[0] == '0')
{
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
return 0;
}
// 自定義一個file_operations結構體變數,并且去填充
static const struct file_operations test_fops = {
.owner = THIS_MODULE, // 慣例,直接寫即可
.open = test_chrdev_open, // 將來應用open打開這個設備時實際呼叫的
.release = test_chrdev_release, // 就是這個.open對應的函式
.write = test_chrdev_write,
.read = test_chrdev_read,
};
// 模塊安裝函式
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
mymajor = register_chrdev(0, MYNAME, &test_fops);
if (mymajor < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk(KERN_INFO "register_chrdev success... mymajor = %d.\n", mymajor);
return 0;
}
// 模塊卸載函式
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// 在module_exit宏呼叫的函式中去注銷字符設備驅動
unregister_chrdev(mymajor, MYNAME);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx這種宏作用是用來添加模塊描述資訊
MODULE_LICENSE("GPL"); // 描述模塊的許可證
MODULE_AUTHOR("aston"); // 描述模塊的作者
MODULE_DESCRIPTION("module test"); // 描述模塊的介紹資訊
MODULE_ALIAS("alias xxx"); // 描述模塊的別名資訊
應用程式代碼如下
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILE "/dev/test" // 剛才mknod創建的設備檔案名
char buf[100];
int main(void)
{
int fd = -1;
int i = 0;
fd = open(FILE, O_RDWR);
if (fd < 0)
{
printf("open %s error.\n", FILE);
return -1;
}
printf("open %s success..\n", FILE);
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("請輸入 on | off \n");
scanf("%s", buf);
if (!strcmp(buf, "on"))
{
write(fd, "1", 1);
}
else if (!strcmp(buf, "off"))
{
write(fd, "0", 1);
}
else if (!strcmp(buf, "flash"))
{
for (i=0; i<3; i++)
{
write(fd, "1", 1);
sleep(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
// 關閉檔案
close(fd);
return 0;
}

至此,由應用程式控制硬體原理的整體思路分析完成

有說的不對的地方,求大佬們幫我指出一下,歡迎在下方留言,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292718.html
標籤:其他
