一、燒寫Linux系統到inand
1、燒寫u-boot到inand
tftp 30008000 u-boot.bin
movi write u-boot 30008000
2、燒寫Linux內核到inand
tftp 30008000 zImage-qt
movi write kernal 30008000
3、燒寫文件系統到inand
開發板中已經有文件系統,無須再燒,設置啓動參數即可。
4、設置啓動環境變量
setenv bootcmd "movi read kernel 30008000;bootm 30008000"
setenv bootargs "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"
二、掛載ubuntu系統的文件夾到開發板
目的:是爲了把編譯好驅動程序下載到共享目下。
1、進入開發板系統 root 123456
2、檢查開板的ip地址
3、在啓動腳本添加以下內容:vi /etc/init.d/rcS
mount -t nfs -o nolock 192.168.1.66:/nfs /mnt
三、Linux內核下的C語言編程
1、命名習慣
普通代碼:
#define PI 3.14159
int minVal,maxVal;
void sendData(void);
內核代碼:
#define PI 3.14159
int min_val,max_val;
void send_data(void);
2、GNU C與ANSI C
GNU編譯器是專門爲Linux內核所設計的一款編譯器,而且Linux內核中也使用大量只有GNU編譯才支持的C語言語法,因此只有GNU編譯器才能編譯內核代碼和Linux驅動程序。
a、零長數組
typedef struct {
int len;
char data[0];
}Data;
Data* dp = malloc(sizeof(Data)+1024);
dp->len = 1024;
send(sockfd,dp,sizeof(dp->len+sizeof(*dp)),0);
b、case範圍
switch(n)
{
case 1 ... 5: break;
case 6 ... 9: break;
}
c、typeof
定義一個宏求兩個變量的最大值。
#define MIN(a,b) ((a)>(b)?(a):(b))
#define MIN(type,a,b) ({type _a=num1++;type _b=num2++;_a>_b?_a:_b;})
MIN(int,num1++,num2++) => num1++ > num2++ ? num1++ : num2++;
#define MIN(a,b) ({typeof(a) _a=a; typeof(b) _b=b; _a>_b?_a:_b;})
定義一個宏交換兩個變量值。
#define swap(a,b) ({typeof(a) t=a; a=b; b =t;})
d、可變參長數宏
int printf(const char *format, ...);
#define debug(ftm,arg,...) printf(ftm,##arg)
e、標號元素
ANSI C
struct Student stu = {
name:"hehe",
sex:'m',
age:18
};
GNU C
struct Student stu = {
.name = "hehe",
.sex = 'm',
.age = 18
};
3、do{ } while(0)
定義一個宏函數,用來釋放堆內存。
#define FREE_HEAP(p) do{free(p); p = NULL;}while(0)
int func(void)
{
if(NULL != p)
FREE_HEAP(p)
else
return -1;
}
4、goto語句
一般的企業代碼中禁止使用goto語句,因爲goto可以跳轉到函數內的任意位置,會打破原有分支、循環結構、或業務邏輯,所以goto是應用層代碼中是一種非常危險的語句,但在內核中卻有它獨到的功能。
int example(void)
{
if(!register_a())
{
goto err1;
}
if(!register_b())
{
goto err2:
}
if(!register_c())
{
goto err3:
}
// work
err3:
unregister_c();
err2:
unregister_b();
err1:
unretister_a();
err:
return ret;
}
在內核中,資源的申請與釋放必須是逆序,而goto語句能夠把錯誤處理的代碼寫的安全、嚴謹而有簡潔。
四、內核模塊
通過分析Linux內核的編譯過程,瞭解到一個實事:Linux內核是由很多模塊組成了。
1、內核以模塊的形式組成的好處
a、可以靈活的進行裁剪
b、可以動態加載或卸載
c、可以儘量控制內核的大小
2、內核模塊的特點
a、內核模塊代碼最終是要加入內核中的
b、內核模塊代碼工作在內核態。
c、內核模塊代碼一旦出錯,可能會導致整個內核(操作)崩潰。
d、在Linux系統中驅動程序是一個內核模塊,它按照內核模塊的格式進行編寫。
3、內核模塊的結構
a、模塊加載函數(必須)
模塊加載內核時自動執行此函數,相當於內核模塊的入口函數。
此函數一般要做些初始化工作、申請內存、資源,註冊相關信息。
b、模塊卸載函數(必須)
模塊被卸載時自動執行。
此函數一般要做些資源的釋放、銷燬的工作。
c、模塊聲明許可證(必須)
d、模塊作者信息聲明(可選)
e、其它一些自定義函數,可以被模塊加載函數調用。
4、內核模塊的加載與卸載
insmod hello.ko
rmmod hello.ko
五、printfk函數
printfk是在內核中運行的向控制檯輸出顯示信息的函數,與printf函數的用法基本一致。
內核中的消息是分等級的,定義在include/linux/kernal.h
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
printk 默認的等級是 KERN_NOTICE
在操作系統中可以設置顯示消息等級,數字越大,級別超低。
cat /proc/sys/kernel/printk 可以看當前系統設置的消息等級
echo "5" > /proc/sys/kernel/printk
一、模塊參數
在加載模塊時可以像應用程序一樣附加一些參數。
定義變量:用來存儲數據。
註冊變量:讓內核允許加載模塊時變量被賦值。
module_param(變量名,類型,權限);
類型:
byte、short、ushort、int、uint、long、ulong、charp
權限:
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
傳遞參數:insmod xxx.ko 變量名=數據
模塊導出符號:
EXPORT_SYMBOL_GPL(符號);
導出的符號(變量名、函數名)可以被其他模塊使用,使用前聲明一下即可。
二、驅動概述
什麼是驅動:就是控制硬件工作的軟件。
在Linux系統下,驅動程序工作在內核中,以內核模塊形式存在,它能能夠控制硬件工作,或者提供控制硬件的接口。
驅動程序提供接口歸內核調用,然後內核再統一抽象成文件(設備文件),代應用的程序以操作文件式進行控制硬件。
應用程序工作在用戶態,驅動程序工作在內核態,它們之間不能直接聯繫,需要按照內核提供的接口(open/read/write/close)來傳遞數據。
在Linux系統下一切皆文件。
三、驅動的類型
字符設備:在IO過程中以字符或字節爲單位進行數據傳輸,數據也要按照一定的順序進行讀寫。
操作這種設備的接口有:open/close/read/write
常見的字符設備有:鼠標、鍵盤、串口、LED燈、溫度傳感器、溼度傳感器等。
字符設備由於功能雜亂,設備也混亂,一般都是小廠家生產的沒有統一的接口,所以大多數據需要編寫驅動的都是字符設備。
塊設備:以塊爲單位進行數據的讀寫,用記在讀寫數據時必須最少讀一塊,這種設備一般都帶緩衝區,而且數據量一般都很大,數據在讀寫時一般不會立即到達硬件。
操作這種設備的接口有:open/close/read/write/sync/fsync/fdatasync。
常見的塊設備有:硬盤、SD卡、Nand、iNand。
雖然存儲介質不同,但都有一個控制器不需要外部直接操作,只需要按照控制器的接口來操作即可。
網絡設備:具有網絡通信協議的設備,網絡設備沒有設備文件,必須使用socket進行操作。
操作這種設備的接口有:send/recv/sendto/recvfrom。
網絡設備都非常標準的、規範,所以有了萬能的網卡驅動,頂多需要移植一下,也不需要編寫驅動。
四、設備文件
在Linux系統會把硬件抽象成文件來製作(網上除外)。
查看設備文件:ls -l /dev
每個字母表示文件的類型:
- 普通文件
d 目錄文件
l 鏈接文件
c 字符設備文件
b 塊設備文件
p 管道文件
s socket文件
設備號:主設備號+從設備號組成
主設備號:表示硬件的類型,1~254
從設備號:硬件的編號,0~255
它是在驅動程序中確定的,可以讓內核自動生成,也可以手動確定,或者手動創建。
注意:設備不能重複。
五、字符設備驅動
1、字符設備驅動分析
drivers\char\Cs5535_gpio.c
a、按照內核模塊格式編寫
b、創建設備號、初始化、添加
c、實現文件操作函數
2、設備號
dev_t dev_id = MKDEV(主設備號,從設備號);
功能:生成一個設備號
返回值:主設備號+從設備號的合體
注意:不一定能使用。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:註冊希望使用的設備號,如果已經被佔用則失敗。
from:希望使用的設備號,MKDEV的返回值
count:設備的數量
name:設備文件名
返回值:成功返回0
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
dev:內核分配的設備號,返回值
baseminor:設備號的起始值
count:設備的數量
name:設備名
返回值:成功返回0
MAJOR(dev_id) 解析出主設備號
MINOR(dev_id) 解析出從設備號
void unregister_chrdev_region(dev_t from, unsigned count)
功能:釋放設備號
from:設備號
count:數量
3、字符設備結構體
struct cdev {
struct kobject kobj; // 內嵌的對象
struct module *owner; // 模塊名
const struct file_operations *ops; // 文件操作結構體(函數指針)
struct list_head list;
dev_t dev; // 設備號
unsigned int count; // 設備數量
};
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
功能:初始化字符設備結構體
cdev:字符設備結構體
fops:文件操作結構體
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:添加字符設備到內核
p:被初始化過的字符設備結構體
dev:註冊過的設備號
count:設備數量
struct file_operations {
// 模塊名
struct module *owner;
// 設備文件位置指針
loff_t (*llseek) (struct file *, loff_t, int);
// read函數
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// write
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// poll
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// ioctl
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
// mmap
int (*mmap) (struct file *, struct vm_area_struct *);
// open
int (*open) (struct inode *, struct file
// flush
int (*flush) (struct file *, fl_owner_t id);
// close
int (*release) (struct inode *, struct file *);
}
六、設備文件
1、手動創建
mknod 創建設備文件的命令。
cat /proc/devices 查看內核分配的主設備號
mknod /dev/char_v1 c 250 0
2、自動創建
內核提供一組函數用於自動創建設備文件,設備類對動對應的數據結構:struct class
class_create->*__class_create
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
功能:在內核中創建設備類 /sys/class/xxx
owner:模塊名
key:設備類的名字
返回值:設備類指針
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
功能:/sys/class/xxx/在dev目錄下自動創建設備文件
class:設備類指針,class_create函數的返回值
parent:設備結構體指針,兼容各種類型的設備,此處填寫字符設備結構指針。
devt:設備號
drvdata:創建設備文件時的附加信息
fmt:設備文件名
void device_destroy(struct class *class, dev_t devt)
功能:刪除設備
class:設備類指針,class_create函數的返回值
devt:設備號
void class_destroy(struct class *cls)
功能:從內核中刪除設備類
一、驅動程序中操作GPIO
在內核中已經把各個GPIO定義爲宏,不需要再找地址了,在頭文件 arch/arm/mach-sv5pv210/include/mach/gpio.h。
int gpio_request(unsigned gpio, const char *label)
功能:申請GPIO資源
gpio:GPIO管腳的編號,在gpio.h頭文件中有定義
label:給GPIO管腳取個名字
void gpio_free(unsigned gpio)
功能:釋放GPIO資源
int gpio_direction_input(unsigned gpio)
功能:設置GPIO管腳爲輸入模式
int gpio_direction_output(unsigned gpio, int value)
功能:設置GPIO管腳爲輸出模式
int gpio_get_value(unsigned gpio)
功能:從GPIO管腳讀取數據
void gpio_set_value(unsigned gpio, int value)
功能:向GPIO管腳寫入數據
二、ioctl
int ioctl(int d, int request, ...);
功能:從應用層來調用內核層的ioctl函數,對設備進行設置。
d:文件描述符,open函數的返回值
request:命令
...:附加參數,存儲在用戶層,以指針的方式交給內核層。
返回值:成功返回0,失敗返回-1。
int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long arg);
功能:響應應用層的ioctl函數,對硬件進行設置。
cmd:對應應用層request
arg:實際上是一個指針,它第指向用戶層的一段數據。
三、內核的內存管理
unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
功能:從用戶層拷貝數據到內核層
to:指向內核層空間的指針
from:指向用戶層空間的指針
n:要拷貝的字節數據
返回值:成功拷貝的字節數
long copy_to_user(void __user *to,const void *from, unsigned long n)
功能:從內核層拷貝數據到用戶層
to:指向用戶層空間的指針
from:指向內核層空間的指針
n:要拷貝的字節數據
返回值:成功拷貝的字節數
void *kmalloc(int size)
功能:與標準C中的用法一致,其實就是調用malloc,kmalloc只是一個宏名。
void kfree(void *where)
功能:釋放內存