日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當(dāng)前位置:首頁 > > 充電吧
[導(dǎo)讀]linux設(shè)備驅(qū)動讀書筆記 設(shè)備驅(qū)動簡介 機(jī)制:提供什么能力 策略:如何使用這些能力 在編寫驅(qū)動時, 程序員應(yīng)當(dāng)編寫內(nèi)核代碼來存取硬件, 但是不能強(qiáng)加特別的策略給用戶, 因?yàn)椴煌挠脩粲胁煌男枨?

linux設(shè)備驅(qū)動讀書筆記
設(shè)備驅(qū)動簡介 機(jī)制:提供什么能力
策略:如何使用這些能力
在編寫驅(qū)動時, 程序員應(yīng)當(dāng)編寫內(nèi)核代碼來存取硬件, 但是不能強(qiáng)加特別的策略給用戶, 因?yàn)椴煌挠脩粲胁煌男枨? 驅(qū)動應(yīng)當(dāng)做到使硬件可用, 將所有關(guān)于如何使用硬件的事情留給應(yīng)用程序
編寫驅(qū)動需要注意的地方:
必須注意并發(fā)/重入的問題
內(nèi)核空間和用戶空間不能直接操作,必須通過特別的函數(shù)(copy_from_user/copy_to_user)來操作
內(nèi)核線程只有一個非常小的堆棧; 它可能小到一個4096 字節(jié)的頁. 驅(qū)動模塊的函數(shù)必須與內(nèi)核函數(shù)共享這個堆棧. 因此, 聲明一個巨大的自動變量不是一個好主意; 如果需要大的結(jié)構(gòu), 應(yīng)當(dāng)在調(diào)用時動態(tài)分配.
以雙下劃線(__)開始的函數(shù)通常是一個低層的接口組件, 應(yīng)當(dāng)小心使用. 本質(zhì)上講, 雙下劃線告訴程序員:" 如果你調(diào)用這個函數(shù), 確信你知道你在做什么."
內(nèi)核代碼不能做浮點(diǎn)算術(shù)
每個進(jìn)程的系統(tǒng)??臻g分配的大小為2個連續(xù)的物理頁面(通常來講是8K),而task_struct占了大約1K(在棧的底部), 所以系統(tǒng)空間非常有限,在中斷/軟中斷/驅(qū)動程序中不允許嵌套太深或使用大量局部變量
編寫缺少進(jìn)程上下文的函數(shù)需要注意:
不允許存取用戶空間. 因?yàn)闆]有進(jìn)程上下文, 沒有和任何特定進(jìn)程相關(guān)聯(lián)的到用戶空間的途徑.
current指針在原子態(tài)沒有意義, 并且不能使用因?yàn)橄嚓P(guān)的代碼沒有和已被中斷的進(jìn)程的聯(lián)系.
不能進(jìn)行睡眠或者調(diào)度. 原子代碼不能調(diào)用 schedule 或者某種 wait_event, 也不能調(diào)用任何其他可能睡眠的函數(shù). 例如, 調(diào)用 kmalloc(..., GFP_KERNEL) 是違犯規(guī)則的. 旗標(biāo)也必須不能使用因?yàn)樗鼈兛赡芩?


重要的數(shù)據(jù)結(jié)構(gòu)的重要成員
struct task_struct {(得到當(dāng)前進(jìn)程task_struct結(jié)構(gòu)指針的宏為: current) volatile long state: 進(jìn)程狀態(tài). -1 unrunnable; 0 runnable; >0 stopped
mm_segment_t addr_limit: 線程地址空間: 0-0xBFFFFFFF for user-thead; 0-0xFFFFFFFF for kernel-thread
struct mm_struct *mm: 虛存管理與映射相關(guān)信息,是整個用戶空間的抽象
unsigned long sleep_time:
pid_t pid: 進(jìn)程pid
uid_t uid,euid,suid,fsuid:
gid_t gid,egid,sgid,fsgid:
gid_t groups[NGROUPS]:
kernel_cap_t cap_effective, cap_inheritable, cap_permitted:
int keep_capabilities:1:
struct user_struct *user:
char comm[16]: 命令名稱. 由當(dāng)前進(jìn)程執(zhí)行的程序文件的基本名稱( 截短到 15 個字符, 如果需要 )
struct tty_struct *tty:
unsigned int locks:
struct rlimit rlim[RLIM_NLIMITS]: 當(dāng)前進(jìn)程各種資源分配的限制, 如current->rlim[RLIMIT_STACK]是對用戶空間堆棧大小的限制
struct files_struct *files: 打開的文件
};
struct file_operations{
struct module *owner;是一個指向擁有這個結(jié)構(gòu)的模塊的指針. 這個成員用來當(dāng)模塊在被使用時阻止其被卸載. 一般初始化為: THIS_MODULE
loff_t (*llseek) (struct file *, loff_t, int);用作改變文件中的當(dāng)前讀/寫位置, 并且新位置作為(正的)返回值.
ssize_t (*read) (struct file *, char *, size_t, loff_t *);從設(shè)備中獲取數(shù)據(jù). 空指針導(dǎo)致read系統(tǒng)調(diào)用返回-EINVAL("Invalid argument") . 非負(fù)返回值代表了成功讀取的字節(jié)數(shù)
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);發(fā)送數(shù)據(jù)給設(shè)備. 空指針導(dǎo)致write 系統(tǒng)調(diào)用返回-EINVAL. 非負(fù)返回值代表成功寫的字節(jié)數(shù).
unsigned int (*poll) (struct file *, struct poll_table_struct *);3 個系統(tǒng)調(diào)用的后端: poll, epoll, 和 select. 都用作查詢對一個或多個文件描述符的讀或?qū)懯欠駮枞? poll 方法應(yīng)當(dāng)返回一個位掩碼指示是否非阻塞的讀或?qū)懯强赡艿? 如果一個驅(qū)動的 poll 方法為 NULL, 設(shè)備假定為不阻塞地可讀可寫.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);提供了發(fā)出設(shè)備特定命令的方法. 注意:有幾個 ioctl 命令被內(nèi)核識別而不會調(diào)用此方法.
int (*mmap) (struct file *, struct vm_area_struct *);請求將設(shè)備內(nèi)存映射到進(jìn)程的地址空間. 如果這個方法是 NULL,系統(tǒng)調(diào)用返回 -ENODEV.
int (*open) (struct inode *, struct file *);open一個設(shè)備文件. 如果這個項(xiàng)是 NULL, 設(shè)備打開一直成功
int (*release) (struct inode *, struct file *);在文件結(jié)構(gòu)被釋放時引用這個操作. 即在最后一個打開設(shè)備文件的文件描述符關(guān)閉時調(diào)用(而不是每次close時都調(diào)用)
int (*fsync) (struct file *, struct dentry *, int datasync);fsync系統(tǒng)調(diào)用的后端, 用戶調(diào)用來刷新任何掛著的數(shù)據(jù). 如果這個指針是 NULL, 系統(tǒng)調(diào)用返回 -EINVAL.
int (*fasync) (int, struct file *, int);通知設(shè)備它的 FASYNC 標(biāo)志(異步通知)的改變. 這個成員可以是NULL 如果驅(qū)動不支持異步通知.
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);包含多個內(nèi)存區(qū)的單個讀操作; 如果為 NULL, read方法被調(diào)用( 可能多于一次 ).
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);包含多個內(nèi)存區(qū)的單個寫操作; 如果為 NULL, write方法被調(diào)用( 可能多于一次 ).
}
struct file{
struct dentry *f_dentry;關(guān)聯(lián)到文件的目錄入口( dentry )結(jié)構(gòu). 設(shè)備驅(qū)動不需要關(guān)心, 除了作為 filp->f_dentry->d_inode 存取 inode 結(jié)構(gòu).
struct file_operations *f_op;和文件關(guān)聯(lián)的操作. 可改變之, 并在返回后新方法會起作用. 例如, 關(guān)聯(lián)到主編號1 (/dev/null, /dev/zero...)的open根據(jù)打開的次編號來更新filp->f_op
unsigned int f_flags;文件標(biāo)志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驅(qū)動應(yīng)當(dāng)檢查 O_NONBLOCK 標(biāo)志來看是否是請求非阻塞操作
mode_t f_mode;文件模式確定文件是可讀的或者是可寫的(或者都是), 通過位 FMODE_READ 和 FMODE_WRITE. 檢查是有內(nèi)核做的,所以驅(qū)動里不需要再次檢查
loff_t f_pos;當(dāng)前讀寫位置. 驅(qū)動可以讀這個值, 但是正常地不應(yīng)該改變它; 讀和寫應(yīng)當(dāng)使用它們的最后一個參數(shù)來更新一個位置. 一個例外是在 llseek 方法中, 它的目的就是改變文件位置.
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
void *private_data; 可自由使用或者忽略它.
}
struct inode{
kdev_t i_rdev;對于代表設(shè)備文件的節(jié)點(diǎn), 這個成員包含實(shí)際的設(shè)備編號. 需要這兩個函數(shù)來操作: unsigned int iminor(struct inode *inode)/unsigned int imajor(struct inode *inode)
struct char_device *i_cdev; 內(nèi)核的內(nèi)部結(jié)構(gòu), 代表字符設(shè)備. 當(dāng)節(jié)點(diǎn)是一個字符設(shè)備文件時, 這個成員包含一個指針, 指向這個結(jié)構(gòu) }

建立和運(yùn)行模塊
下圖展示了函數(shù)調(diào)用和函數(shù)指針在模塊中如何使用來增加新功能到一個運(yùn)行中的內(nèi)核.
編譯和加載
內(nèi)核版本的問題
linux/version.h中有下面的宏定義:

UTS_RELEASE

這個宏定義擴(kuò)展成字符串, 描述了這個內(nèi)核樹的版本. 例如, "2.6.10".

LINUX_VERSION_CODE

這個宏定義擴(kuò)展成內(nèi)核版本的二進(jìn)制形式, 版本號發(fā)行號的每個部分用一個字節(jié)表示. 例如, 2.6.10 的編碼是 132618 ( 就是, 0x02060a ). [4]有了這個信息, 你可以(幾乎是)容易地決定你在處理的內(nèi)核版本.

KERNEL_VERSION(major,minor,release)

這個宏定義用來建立一個整型版本編碼, 從組成一個版本號的單個數(shù)字. 例如, KERNEL_VERSION(2.6.10) 擴(kuò)展成 132618. 這個宏定義非常有用, 當(dāng)你需要比較當(dāng)前版本和一個已知的檢查點(diǎn).

特殊GNU make變量名
obj-m := module.o #最終模塊名
module-objs := file1.o file2.o #最終模塊用到的obj列表
make命令中的"M="選項(xiàng)使 makefile 在試圖建立模塊目標(biāo)(obj-m 變量中指定的)前, 回到你的模塊源碼目錄
Makefile示例:
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
加載卸載:
insmod:僅加載指定的模塊)
modprobe:加載指定的模塊及其相關(guān)模塊. 它查看要加載的模塊, 看是否它引用了當(dāng)前內(nèi)核沒有定義的符號. 如果未定義的symbols, modprobe 在模塊搜索路徑(/etc/modprobe.conf)中尋找并加載其他定義了symbols的模塊
rmmod: 從內(nèi)核中去除指定模塊
lsmod: 打印內(nèi)核中當(dāng)前加載的模塊的列表. 通過讀取/proc/modules或/sys/module 的 sysfs 虛擬文件系統(tǒng)工作
錯誤信息:
unresolved symbols: 加載是找不到symbols. 可能是使用了未定義的symbols; 也可能是需要使用modprobe試一下
-1 Invalid module format:編譯模塊用的內(nèi)核源代碼版本與當(dāng)前運(yùn)行的內(nèi)核的版本不匹配

調(diào)試技術(shù)
內(nèi)核的config配置
kernel hacking菜單
CONFIG_DEBUG_KERNEL:
這個選項(xiàng)只是使其他調(diào)試選項(xiàng)可用; 它應(yīng)當(dāng)打開, 但是它自己不激活任何的特性.
CONFIG_DEBUG_SLAB:
這個重要的選項(xiàng)打開了內(nèi)核內(nèi)存分配函數(shù)的幾類檢查. 激活這些檢查, 就可能探測到一些內(nèi)存覆蓋和遺漏初始化的錯誤.被分配的每一個字節(jié)在遞交給調(diào)用者之前都設(shè)成 0xa5, 隨后在釋放時被設(shè)成 0x6b.內(nèi)核還會在每個分配的內(nèi)存對象的前后放置特別的守護(hù)值; 如果這些值曾被改動, 內(nèi)核知道有人已覆蓋了一個內(nèi)存分配區(qū).
CONFIG_DEBUG_PAGEALLOC:
滿的頁在釋放時被從內(nèi)核地址空間去除(full pages are removed from the kernel address space when freed)(?). 這個選項(xiàng)會顯著拖慢系統(tǒng), 但是它也能快速指出某些類型的內(nèi)存損壞錯誤.
CONFIG_DEBUG_SPINLOCK
激活這個選項(xiàng), 內(nèi)核捕捉對未初始化的自旋鎖的操作, 以及各種其他的錯誤( 例如 2 次解鎖同一個鎖 ).
CONFIG_DEBUG_SPINLOCK_SLEEP
這個選項(xiàng)激活對持有自旋鎖時進(jìn)入睡眠的檢查. 實(shí)際上, 如果你調(diào)用一個可能會睡眠的函數(shù), 它就發(fā)出警告, 即便這個有疑問的調(diào)用沒有睡眠.
CONFIG_INIT_DEBUG
用__init (或者 __initdata) 標(biāo)志的項(xiàng)在系統(tǒng)初始化或者模塊加載后都被丟棄. 這個選項(xiàng)激活了對代碼的檢查, 這些代碼試圖在初始化完成后存取初始化時內(nèi)存.
CONFIG_DEBUG_INFO
這個選項(xiàng)使得內(nèi)核在建立時包含完整的調(diào)試信息. 如果你想使用 gdb 調(diào)試內(nèi)核, 你將需要這些信息. 如果你打算使用 gdb, 你還要激活 CONFIG_FRAME_POINTER.
CONFIG_MAGIC_SYSRQ
激活"魔術(shù) SysRq"鍵.
CONFIG_DEBUG_STACKOVERFLOW
CONFIG_DEBUG_STACK_USAGE
這些選項(xiàng)能幫助跟蹤內(nèi)核堆棧溢出. 堆棧溢出的確證是一個 oops 輸出, 但是沒有任何形式的合理的回溯. 第1個選項(xiàng)給內(nèi)核增加了明確的溢出檢查; 第 2 個使得內(nèi)核監(jiān)測堆棧使用并作一些統(tǒng)計(jì), 這些統(tǒng)計(jì)可以用魔術(shù) SysRq 鍵得到.
CONFIG_KALLSYMS
這個選項(xiàng)(在"Generl setup/Standard features"下)使得內(nèi)核符號信息建在內(nèi)核中; 缺省是激活的. 符號選項(xiàng)用在調(diào)試上下文中; 沒有它, 一個 oops 列表只能以 16 進(jìn)制格式給你一個內(nèi)核回溯, 這不是很有用.
CONFIG_IKCONFIG
CONFIG_IKCONFIG_PROC
這些選項(xiàng)(在"Generl setup"菜單)使得完整的內(nèi)核配置狀態(tài)被建立到內(nèi)核中, 可以通過 /proc 來使其可用. 大部分內(nèi)核開發(fā)者知道他們使用的哪個配置, 并不需要這些選項(xiàng)(會使得內(nèi)核更大). 但是如果你試著調(diào)試由其他人建立的內(nèi)核中的問題, 它們可能有用.
Power management/ACPI菜單
CONFIG_ACPI_DEBUG
這個選項(xiàng)打開詳細(xì)的 ACPI (Advanced Configuration and Power Interface) 調(diào)試信息, 如果你懷疑一個問題和 ACPI 相關(guān)可能會用到 Device drivers菜單
CONFIG_DEBUG_DRIVER
打開了驅(qū)動核心的調(diào)試信息, 可用以追蹤低層支持代碼的問題. Device drivers/SCSI device support菜單
CONFIG_SCSI_CONSTANTS
建立詳細(xì)的 SCSI 錯誤消息的信息. 如果你在使用 SCSI 驅(qū)動, 你可能需要這個選項(xiàng). Device drivers/Input device support菜單
CONFIG_INPUT_EVBUG
如果你使用一個輸入設(shè)備的驅(qū)動, 這個選項(xiàng)可能會有用. 然而要小心這個選項(xiàng)的安全性的隱含意義: 它記錄了你鍵入的任何東西, 包括你的密碼.
Profiling support菜單
CONFIG_PROFILING
剖析通常用在系統(tǒng)性能調(diào)整, 但是在追蹤一些內(nèi)核掛起和相關(guān)問題上也有用.
strace 命令
顯示所有的用戶空間程序發(fā)出的系統(tǒng)調(diào)用. 并以符號形式顯示調(diào)用的參數(shù)和返回值.當(dāng)一個系統(tǒng)調(diào)用失敗, 錯誤的符號值(例如, ENOMEM)和對應(yīng)的字串(Out of memory) 都會顯示.
-t: 來顯示每個系統(tǒng)調(diào)用執(zhí)行的時間
-T: 來顯示調(diào)用中花費(fèi)的時間
-e: 來限制被跟蹤調(diào)用的類型
-o: 來重定向輸出到一個文件. 缺省地, strace 打印調(diào)用信息到 stderr.

GDB調(diào)試
調(diào)試內(nèi)核:
gdb /usr/src/linux/vmlinux /proc/kcore

第一個參數(shù)是非壓縮的 ELF 內(nèi)核可執(zhí)行文件的名子, 不是 zImage 或者 bzImage

第二個參數(shù)是核心文件的名子.

注意事項(xiàng):
無法檢查module的相關(guān)內(nèi)容
不能修改內(nèi)核數(shù)據(jù), 不能單步,不能設(shè)置斷點(diǎn)
讀到的是內(nèi)核即時映象,內(nèi)核仍在運(yùn)行,所以有些數(shù)據(jù)可能會與即時值不匹配--刷新映象:core-file /proc/kcore
調(diào)試模塊(內(nèi)核版本2.6.7 以上)

Linux 可加載模塊是 ELF 格式的可執(zhí)行映象;ELF被分成幾個sections. 其中有 3 個典型的sections與調(diào)試會話相關(guān):

.text

這個節(jié)包含有模塊的可執(zhí)行代碼. 調(diào)試器必須知道在哪里以便能夠給出回溯或者設(shè)置斷點(diǎn).

.bss

在編譯時不初始化的任何變量在 .bss 中

.data

在編譯時需要初始化的任何變量在 .data 里.

為了gdb能夠調(diào)試可加載模塊需要通知調(diào)試器一個給定模塊的各個sections加載在哪里. 這個信息在 /sys/module/module_name/sections下. 包含名子為 .text , .bss, .data等文件; 每個文件的內(nèi)容是那個section的基地址.

gdb的add-symbol-file命令用來加載模塊相關(guān)信息

add-symbol-file 模塊名 text所在的基地址 -s .bss bss所在基地址 -s .data data所在基地址

add-symbol-file ../scull.ko 0xd0832000 -s .bss 0xd0837100 -s .data 0xd0836be0


KDB調(diào)試
KDB是來自oss.sgi.com的一個非官方補(bǔ)丁. 應(yīng)用KDB時不應(yīng)該運(yùn)行任何程序, 特別的, 不能打開網(wǎng)絡(luò). 一般地以單用戶模式啟動系統(tǒng)
進(jìn)入KDB:
Pause(或者 Break) 鍵啟動調(diào)試器
一個內(nèi)核 oops(異常?) 發(fā)生時
命中一個斷點(diǎn)時
命令:
bp function_name
在下一次內(nèi)核進(jìn)入function_name時停止
bt
打印出調(diào)用回溯中每個函數(shù)的參數(shù)
mds variable_name
mds address
查看變量/內(nèi)存數(shù)據(jù)
mm address value
將value賦給address所指向的內(nèi)存
......



內(nèi)核中的數(shù)據(jù)類型
不同體系結(jié)構(gòu)下各個類型的大小
arch Size: char short int long ptr long-long u8 u16 u32 u64
i386 1 2 4 4 4 8 1 2 4 8
alpha 1 2 4 8 8 8 1 2 4 8
armv4l 1 2 4 4 4 8 1 2 4 8
ia64 1 2 4 8 8 8 1 2 4 8
m68k 1 2 4 4 4 8 1 2 4 8
mips 1 2 4 4 4 8 1 2 4 8
ppc 1 2 4 4 4 8 1 2 4 8
sparc 1 2 4 4 4 8 1 2 4 8
sparc64 1 2 4 4 4 8 1 2 4 8
x86_64 1 2 4 8 8 8 1 2 4 8
應(yīng)當(dāng)安排有明確類型大小的數(shù)據(jù)類型, 如u8, u16, ... uint8_t, uint16_t, ...
接口特定的類型, 請參考原文
其他移植性問題:
Tick: HZ
頁大小: PAGE_SIZE
頁偏移: PAGE_SHIFT
字節(jié)序:
條件編譯
#include
#ifdef __BIG_ENDIAN
......
#endif
#ifdef __LITTLE_ENDIAN
......
#endif
轉(zhuǎn)換
#include
#include u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);
cpu_to_le16/le16_to_cpu/cpu_to_le64/....
cpus_to_le16/le16_to_cpus/cpus_to_le64/....
帶's'后綴的是有符號版
數(shù)據(jù)對齊:
存取不對齊的數(shù)據(jù)應(yīng)當(dāng)使用下列宏
#include
get_unaligned(ptr);
put_unaligned(val, ptr);
指針返回值:
有時候內(nèi)核函數(shù)會返回已編碼的指針值來指示錯誤, 這類返回值是否有效的測試應(yīng)當(dāng)使用下列宏
void *ERR_PTR(long error); 將錯誤碼轉(zhuǎn)換成指針形式
long IS_ERR(const void *ptr); 判斷一個返回值是否有效
long PTR_ERR(const void *ptr); 提取返回的錯誤碼, 在提取前需要判斷返回值是否有效
鏈表:
鼓勵使用內(nèi)核自帶的struct list_head結(jié)構(gòu)來構(gòu)造雙向鏈表
#include
struct list_head { struct list_head *next, *prev; };
LIST_HEAD(struct list_head);
編譯時初始化
INIT_LIST_HEAD(struct list_head*)
運(yùn)行時初始化
list_add(struct list_head *new, struct list_head *head);
在head后鏈入new. 常用來構(gòu)造FILO
list_add_tail(struct list_head *new, struct list_head *head);
在head前鏈入new, 常用來構(gòu)造FIFO
list_del(struct list_head *entry);
把entry從鏈表中脫鏈
list_del_init(struct list_head *entry);
把entry從鏈表中脫鏈并重新初始化entry
list_move(struct list_head *entry, struct list_head *head);
將entry移動到head后
list_move_tail(struct list_head *entry, struct list_head *head);
將entry移動到head前
list_empty(struct list_head *head);
判斷鏈表是否為空
list_splice(struct list_head *list, struct list_head *head);
從鏈表head后斷開, 將list鏈表鏈接進(jìn)去

list_entry(struct list_head *ptr, type_of_struct, field_name);
從list_head地址得到包含list_head的結(jié)構(gòu)體的開始地址.
類似的宏為container_of(pointer, container_type, container_field); 從結(jié)構(gòu)體成員地址得到結(jié)構(gòu)體指針
list_for_each(struct list_head *cursor, struct list_head *list)
這個宏創(chuàng)建一個for循環(huán), 執(zhí)行一次, cursor 指向鏈表中的下個入口項(xiàng)
list_for_each_prev(struct list_head *cursor, struct list_head *list)
這個版本反向遍歷鏈表.
list_for_each_safe(struct list_head *cursor, struct list_head *next, struct list_head *list)
如果循環(huán)可能刪除鏈表中的項(xiàng), 使用這個版本.
list_for_each_entry(type *cursor, struct list_head *list, member)
直接得到包含list_head的結(jié)構(gòu)體的地址
list_for_each_entry_safe(type *cursor, type *next, struct list_head *list, member)
如果循環(huán)可能刪除鏈表中的項(xiàng), 使用這個版本
舉例:
struct list_head todo_list;
...
void todo_add_entry(struct todo_struct *new)
{
struct list_head *ptr;
struct todo_struct *entry;

list_for_each(ptr, &todo_list)
{
entry = list_entry(ptr, struct todo_struct, list);
if (entry->priority < new->priority) {
list_add_tail(&new->list, ptr);
return;
}
}
list_add_tail(&new->list, &todo_struct)
}


模塊特殊宏/函數(shù)(注意大小寫)

module_init(initialization_function): 聲明模塊初始化函數(shù)
module_exit(cleanup_function): 聲明模塊注銷函數(shù)

EXPORT_SYMBOL(name): 聲明符號在模塊外可用

EXPORT_SYMBOL_GPL(name): 聲明符號僅對使用 GPL 許可的模塊可用.

MODULE_LICENSE("GPL"): 聲明模塊許可
MODULE_AUTHOR: 聲明誰編寫了模塊
MODULE_DESCRIPION: 一個人可讀的關(guān)于模塊做什么的聲明
MODULE_VERSION: 一個代碼修訂版本號; 看 的注釋以便知道創(chuàng)建版本字串使用的慣例
MODULE_ALIAS: 模塊為人所知的另一個名子
MODULE_DEVICE_TABLE: 來告知用戶空間, 模塊支持那些設(shè)備

module_param(name, type, perm): 聲明模塊加載時允許設(shè)置的參數(shù)(2.6.11之前版本中為MODULE_PARM)
module_param_array(name,type,num,perm): 聲明模塊加載時允許設(shè)置的數(shù)組參數(shù)
name: 是你的參數(shù)(數(shù)組)的名子
type: 是數(shù)組元素的類型
bool/invbool: 一個布爾型( true 或者 false)值(相關(guān)的變量應(yīng)當(dāng)是 int 類型). invbool 類型顛倒了值, 所以真值變成 false, 反之亦然.
charp: 一個字符指針值. 需要為其分配內(nèi)存(charp, NOT char)
int/long/short/uint/ulong/ushort: 基本的變長整型值. 以 u 開頭的是無符號值.
num: 一個整型變量
perm: 通常的權(quán)限值, 在 中定義. S_IRUGO: 可以被所有人讀取, 但是不能改變; S_IRUGO|S_IWUSR: 允許 root 改變參數(shù). 注意, 如果一個參數(shù)被 sysfs 修改, 模塊看到的參數(shù)值也改變了, 但模塊不會有任何通知
示例:
在模塊中聲明如下:
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
調(diào)用時如下:
insmod moduel_name howmany=10 whom="Mom"
模塊初始化函數(shù)原型為
static int __init function(void);
大部分注冊函數(shù)以 register_ 做前綴
__init/__initdata: 給定的函數(shù)/數(shù)據(jù)只是在初始化使用. 模塊加載后丟掉這個初始化函數(shù), 使它的內(nèi)存可做其他用途.
__devinit/__devinitdata: 內(nèi)核沒有配置支持 hotplug 設(shè)備時等同于__init/_initdata.
模塊注銷函數(shù)原型為
static void __exit function(void);
__exit/__exitdata: 如果模塊直接建立在內(nèi)核里, 或者如果內(nèi)核配置成不允許模塊卸載, 標(biāo)識為__exit 的函數(shù)被簡單地丟棄

container_of(pointer, container_type, container_field);通過一個結(jié)構(gòu)體成員的地址得到結(jié)構(gòu)體的地址
比如:
struct test{int a; int b; int c; inte}test_t;
&test_t == container_of(&(test_t.c), struct test, c)
__setup("test=", test_setup);
這個宏將test_setup這個函數(shù)放在特定的section中.
在執(zhí)行init/main.c::checksetup()時會去kernel boot commandline中尋找字符串"test=xxx": 如果有找到,就用"xxx"作為參數(shù)調(diào)用test_setup; 否則不運(yùn)行
在insmod中如果參數(shù)里帶有"test=xxx"也會運(yùn)行
void *kmalloc(size_t size, int flags); 試圖分配 size 字節(jié)的內(nèi)存; 返回值是指向那個內(nèi)存的指針或者如果分配失敗為NULL. flags 參數(shù)用來描述內(nèi)存應(yīng)當(dāng)如何分配
申請的空間大小限制: 大概為128K
void kfree(void *ptr);分配的內(nèi)存應(yīng)當(dāng)用 kfree 來釋放. 傳遞一個 NULL 指針給 kfree 是合法的.




get_free_page申請的page數(shù)限制: 2^MAX_ORDER, 2的MAX_ORDER次方個page. 通常MAX_ORDER=10, 也就是最多2^10=1024個page, 4Mbyte


int access_ok(int type, const void *addr, unsigned long size): 驗(yàn)證用戶空間有效性
type: VERIFY_READ/VERIFY_WRITE. 如果需要驗(yàn)證讀寫許可, 則只要 VERIFY_WRITE
addr: 一個用戶空間地址,
size: 需要驗(yàn)證的大小.
返回值: 1 是成功(存取沒問題); 0 是失敗(存取有問題). 如果它返回0, 驅(qū)動應(yīng)當(dāng)返回 -EFAULT
put_user(datum, ptr) 
__put_user(datum, ptr) 
寫 datum 到用戶空間. 它們相對(copy_to_user)快. 傳送的數(shù)據(jù)大小依賴 prt 參數(shù)的類型. 比如: prt 是一個char指針就傳送一個字節(jié)
put_user 檢查用戶空間確保能寫. 在成功時返回 0, 并且在錯誤時返回 -EFAULT.
__put_user 進(jìn)行更少的檢查(它不調(diào)用 access_ok),
驅(qū)動應(yīng)當(dāng)調(diào)用put_user來節(jié)省幾個周期; 或者拷貝幾個項(xiàng)時, 在第一次數(shù)據(jù)傳送之前調(diào)用access_ok一次, 之后使用__put_user
get_user(local, ptr) 
__get_user(local, ptr) 
從用戶空間讀單個數(shù)據(jù), 獲取的值存儲于本地變量 local;
如果使用上述四個函數(shù)時, 發(fā)現(xiàn)一個來自編譯器的奇怪消息, 例如"coversion to non-scalar type requested". 必須使用 copy_to_user 或者 copy_from_user.

unsigned long copy_to_user(void __user *to,const void *from,unsigned long count); 拷貝一整段數(shù)據(jù)到用戶地址空間. 任何存取用戶空間的函數(shù)必須是可重入的. 此函數(shù)可能導(dǎo)致睡眠
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count); 從用戶地址空間拷貝一整段數(shù)據(jù). 任何存取用戶空間的函數(shù)必須是可重入的. 此函數(shù)可能導(dǎo)致睡眠
這兩個函數(shù)的作用不僅限于拷貝數(shù)據(jù)到和從用戶空間: 它們還檢查用戶空間指針是否有效. 如果指針無效, 不進(jìn)行拷貝; 如果在拷貝中遇到一個無效地址, 只拷貝有效部分的數(shù)據(jù). 在第二種情況下, 返回值是未拷貝的數(shù)據(jù)數(shù). 驅(qū)動應(yīng)當(dāng)查看返回值, 并且如果它不是0, 就返回 -EFAULT 給用戶.
int capable(int capability); 
在進(jìn)行一個特權(quán)操作之前, 一個設(shè)備驅(qū)動應(yīng)當(dāng)檢查調(diào)用進(jìn)程有合適的能力
capability 取值有以下這些:
CAP_DAC_OVERRIDE
這個能力來推翻在文件和目錄上的存取的限制(數(shù)據(jù)存取控制, 或者 DAC).
CAP_NET_ADMIN
進(jìn)行網(wǎng)絡(luò)管理任務(wù)的能力, 包括那些能夠影響網(wǎng)絡(luò)接口的.
CAP_SYS_MODULE
加載或去除內(nèi)核模塊的能力.
CAP_SYS_RAWIO
進(jìn)行 "raw" I/O 操作的能力. 例子包括存取設(shè)備端口或者直接和 USB 設(shè)備通訊.
CAP_SYS_ADMIN
一個捕獲-全部的能力, 提供對許多系統(tǒng)管理操作的存取.
CAP_SYS_TTY_CONFIG
進(jìn)行 tty 配置任務(wù)的能力.
CAP_SYS_ADMIN
在任務(wù)缺乏一個更加特定的能力時, 可以選這個來測試
int printk(const char * fmt, ...);向console(而不是虛擬終端)打印一條消息, 并通過附加不同的記錄級別或者優(yōu)先級在消息上對消息的嚴(yán)重程度分類.沒有指定優(yōu)先級的printk語句缺省是DEFAULT_MESSAGE_LOGLEVEL, 在 kernel/printk.c里指定作為一個整數(shù). 在2.6.10內(nèi)核中, DEFAULT_MESSAGE_LOGLEVEL是KERN_WARNING, 但這個值在不同的內(nèi)核中可能不一樣.
按消息的嚴(yán)重程度從高到低為:
KERN_EMERG

用于緊急消息, 常常是那些崩潰前的消息.

KERN_ALERT

需要立刻動作的情形.

KERN_CRIT

嚴(yán)重情況, 常常與嚴(yán)重的硬件或者軟件失效有關(guān).

KERN_ERR

用來報(bào)告錯誤情況; 設(shè)備驅(qū)動常常使用 KERN_ERR 來報(bào)告硬件故障.

KERN_WARNING

有問題的情況的警告, 這些情況自己不會引起系統(tǒng)的嚴(yán)重問題.

KERN_NOTICE

正常情況, 但是仍然值得注意. 在這個級別一些安全相關(guān)的情況會報(bào)告.

KERN_INFO

信息型消息. 在這個級別, 很多驅(qū)動在啟動時打印它們發(fā)現(xiàn)的硬件的信息.

KERN_DEBUG

用作調(diào)試消息.

使用舉例:
printk(KERN_INFO "hello, worldn");//注意:消息優(yōu)先級與正文內(nèi)容之間沒有逗號
int printk_ratelimit(void); 在你認(rèn)為打印一個可能會常常重復(fù)的消息之前調(diào)用來避免重復(fù)輸出很多相同的調(diào)試信息. 如果這個函數(shù)返回非零值, 繼續(xù)打印你的消息, 否則跳過打印.
使用舉例
if (printk_ratelimit())
printk(KERN_NOTICE "The printer is still on firen");
int print_dev_t(char *buffer, dev_t dev); 
char *format_dev_t(char *buffer, dev_t dev);
從一個驅(qū)動打印消息, 你會想打印與感興趣的硬件相關(guān)聯(lián)的設(shè)備號.兩個宏定義都將設(shè)備號編碼進(jìn)給定的緩沖區(qū); 唯一的區(qū)別是 print_dev_t 返回打印的字符數(shù), 而 format_dev_t 返回緩存區(qū)

void set_current_state(int new_state); 設(shè)置當(dāng)前進(jìn)程的運(yùn)行狀態(tài)
new_state: TASK_INTERRUPTIBLE/TASK_RUNNING/TASK_INTERRUPTIBLE/TASK_UNTINTERRUPTIBLE/...
在新代碼中不鼓勵使用下面這種方式
current->state = TASK_INTERRUPTIBLE
int in_interrupt(void)
如果處理器當(dāng)前在中斷上下文(包括軟中斷和硬中斷)運(yùn)行就返回非零
int in_atomic(void)
若調(diào)度被禁止(即當(dāng)前狀態(tài)是原子態(tài), 包括硬中斷,軟件中斷以及持有自旋鎖時), 返回值是非零. 在持有自旋鎖這種情況, current 可能是有效的, 但是禁止存取用戶空間, 因?yàn)樗軐?dǎo)致調(diào)度發(fā)生.
無論何時使用 in_interrupt(), 應(yīng)當(dāng)真正考慮是否 in_atomic 是你實(shí)際想要的



主次設(shè)備號:
主編號標(biāo)識設(shè)備驅(qū)動; 次編號被內(nèi)核用來決定引用哪個設(shè)備
dev_t 類型(在 中定義)用來標(biāo)識設(shè)備編號 -- 同時包括主次部分
MAJOR(dev_t dev): 從dev_t中取得主設(shè)備號
MINOR(dev_t dev): 從dev_t中取得次設(shè)備號

MKDEV(int major, int minor): 講主次設(shè)備號轉(zhuǎn)換成dev_t

int register_chrdev_region(dev_t first, unsigned int count, char *name): 獲取一個或多個設(shè)備編號來使用
first 是你要分配的起始設(shè)備編號. first 的次編號部分常常是 0
count 是你請求的連續(xù)設(shè)備編號的總數(shù)
name 是應(yīng)當(dāng)連接到這個編號范圍的設(shè)備的名子; 它會出現(xiàn)在 /proc/devices 和 sysfs 中
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);動態(tài)分配一個主編號
dev 是一個只輸出的參數(shù), 它在函數(shù)成功完成時持有你的分配范圍的第一個數(shù).
fisetminor 應(yīng)當(dāng)是請求的第一個要用的次編號; 它常常是 0.
count 是你請求的連續(xù)設(shè)備編號的總數(shù)
name 是應(yīng)當(dāng)連接到這個編號范圍的設(shè)備的名子; 它會出現(xiàn)在 /proc/devices 和 sysfs 中
void unregister_chrdev_region(dev_t first, unsigned int count); 釋放設(shè)備編號
first 是你要分配的起始設(shè)備編號. first 的次編號部分常常是 0
count 是你請求的連續(xù)設(shè)備編號的總數(shù)
設(shè)備注冊:
struct cdev *cdev_alloc(void);為struct cdev申請內(nèi)存空間
void cdev_init(struct cdev *cdev, struct file_operations *fops);初始化struct cdev結(jié)構(gòu). 其成員owner應(yīng)當(dāng)設(shè)置為 THIS_MODULE
cdev 是需要初始化的struct cdev結(jié)構(gòu)
fops: 是關(guān)聯(lián)到這個驅(qū)動的方法集合(read/write等)
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);將設(shè)備注冊到內(nèi)核

dev 是struct cdev結(jié)構(gòu)

num 是這個設(shè)備響應(yīng)的第一個設(shè)備號

count 是應(yīng)當(dāng)關(guān)聯(lián)到設(shè)備的設(shè)備號的數(shù)目. 常常 count 是 1, 但是有多個設(shè)備號對應(yīng)于一個特定的設(shè)備的情形.

void cdev_del(struct cdev *dev);將設(shè)備注銷
設(shè)備注冊的老方法:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major 是感興趣的主編號
name 是驅(qū)動的名子(出現(xiàn)在 /proc/devices)
fops 是缺省的 file_operations 結(jié)構(gòu).
int unregister_chrdev(unsigned int major, const char *name);

major和name 必須和傳遞給register_chrdev的相同, 否則調(diào)用會失敗.


設(shè)備節(jié)點(diǎn):
devfs_handle_t devfs_register (devfs_handle_t dir,
            const char *name,
            unsigned int flags,
            unsigned int major, unsigned int minor,
            umode_t mode,
            void *ops, void *info);創(chuàng)建設(shè)備節(jié)點(diǎn)
dir:需要創(chuàng)建的設(shè)備文件所在目錄,默認(rèn)為/dev
name: 需要創(chuàng)建的設(shè)備文件名
flags: 通常取DEVFS_FL_DEFAULT
major: 主設(shè)備號
minor: 次設(shè)備號
mode: 此設(shè)備文件的讀寫權(quán)限
ops: 此設(shè)備的file_operations結(jié)構(gòu)
info: 
#define DEV_ID ((void*)123456)
#define DEV_NAME "XXXXXXXXXXXXXX"
#define DEV_MAJOR 200
#define DEV_IRQ IRQ_XXXX
#define DEV_IRQ_MODE SA_SHIRQ

...

//regist char device
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
ret = register_chrdev(DEV_MAJOR, DEV_NAME, &fops);
#else
cdev_init(&dev_char, &fops);
dev_char.owner = THIS_MODULE;
dev_char.ops = &fops;
ret = cdev_add(&dev_char, MKDEV(DEV_MAJOR, 0), 1);
#endif
if (ret < 0)
goto __mod_init_err1;
//make devfs
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
devfs_handle = devfs_register(NULL, DEV_NAME, DEVFS_FL_DEFAULT,
DEV_MAJOR, 0, S_IFCHR | S_IRUSR | S_IWUSR, &fops, NULL);
if (NULL == devfs_handle)
{
ret = -1;
goto __mod_init_err2;
}
#else
dev_class = class_create(THIS_MODULE, DEV_NAME);
if(IS_ERR(dev_class))
{
ret = PTR_ERR(dev_class);
goto __mod_init_err2;
}
class_device_create(dev_class, MKDEV(DEV_MAJOR, 0), NULL, DEV_NAME);
ret = devfs_mk_cdev(MKDEV(DEV_MAJOR, 0), S_IFCHR | S_IRUGO | S_IWUSR, DEV_NAME);
if(ret)
goto __mod_init_err3;
#endif

......

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
__mod_init_err3:
class_device_destroy(dev_class, MKDEV(DEV_MAJOR, 0));
class_destroy(dev_class);
#endif
__mod_init_err2:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
unregister_chrdev(DEV_MAJOR, DEV_NAME);
#else
cdev_del(&dev_char);
#endif
__mod_init_err1:
free_irq(DEV_IRQ, DEV_ID);

#endif//end of "ifndef INPUT_DEVICE"

__mod_init_err0:
return ret;


file_operations函數(shù):
ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) 1. 通常應(yīng)當(dāng)更新 *offp 中的文件位置來表示在系統(tǒng)調(diào)用成功完成后當(dāng)前的文件位置.
2. 如果"沒有數(shù)據(jù), 但是可能后來到達(dá)", 在這種情況下, read 系統(tǒng)調(diào)用應(yīng)當(dāng)阻塞.
3. 返回值
如果等于傳遞給 read 系統(tǒng)調(diào)用的 count 參數(shù), 請求的字節(jié)數(shù)已經(jīng)被傳送
如果是正數(shù), 但是小于 count, 只有部分?jǐn)?shù)據(jù)被傳送.
如果值為 0, 到達(dá)了文件末尾(沒有讀取數(shù)據(jù)).

如果值為負(fù)值表示有一個錯誤. 這個值指出了什么錯誤, 根據(jù) . 出錯的典型返回值包括 -EINTR( 被打斷的系統(tǒng)調(diào)用) 或者 -EFAULT( 壞地址 ).

如果一些數(shù)據(jù)成功傳送接著發(fā)生錯誤, 返回值必須是成功傳送的字節(jié)數(shù). 在函數(shù)下一次調(diào)用前錯誤不會報(bào)告. 這要求驅(qū)動記住錯誤已經(jīng)發(fā)生, 以便可以在以后返回錯誤狀態(tài).



ssize_t write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)

1. 通常應(yīng)當(dāng)更新 *offp 中的文件位置來表示在系統(tǒng)調(diào)用成功完成后當(dāng)前的文件位置.

2. 返回值:

如果值等于 count, 要求的字節(jié)數(shù)已被傳送

如果正值, 但是小于 count, 只有部分?jǐn)?shù)據(jù)被傳送

如果值為 0, 什么沒有寫. 這個結(jié)果不是一個錯誤

一個負(fù)值表示發(fā)生一個錯誤

如果一些數(shù)據(jù)成功傳送接著發(fā)生錯誤, 返回值必須是成功傳送的字節(jié)數(shù). 在函數(shù)下一次調(diào)用前錯誤不會報(bào)告. 這要求驅(qū)動記住錯誤已經(jīng)發(fā)生, 以便可以在以后返回錯誤狀態(tài).

ssize_t (*readv) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
ssize_t (*writev) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
struct iovec
{
void __user *iov_base;
__kernel_size_t iov_len;
};
每個 iovec 描述了一塊要傳送的數(shù)據(jù); 它開始于 iov_base (在用戶空間)并且有 iov_len 字節(jié)長. count 參數(shù)告訴有多少 iove結(jié)構(gòu).
若未定義此二函數(shù). 內(nèi)核使用 read 和 write 來模擬它們,

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
inode 和 filp 指針是對應(yīng)應(yīng)用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的相同參數(shù).
cmd 參數(shù)從用戶那里不改變地傳下來,
arg 是可選的參數(shù), 無論是一個整數(shù)還是指針(按照慣例應(yīng)該用指針), 均以unsigned long的形式傳遞進(jìn)來
返回值:
-ENIVAL("Invalid argument"): 命令參數(shù)不是一個有效的POSIX 標(biāo)準(zhǔn)(通常會返回這個)
-ENOTTY: 一個不合適的 ioctl 命令. 這個錯誤碼被 C 庫解釋為"設(shè)備的不適當(dāng)?shù)膇octl"(inappropriate ioctl for device)

ioctl的cmd參數(shù)應(yīng)當(dāng)是系統(tǒng)唯一的, 這是出于阻止向錯誤的設(shè)備發(fā)出其可識別但具體內(nèi)容無法解析的命令的考慮
cmd參數(shù)由這幾部分組成: type, number, direction, size
type
魔數(shù). 為整個驅(qū)動選擇一個數(shù)(參考ioctl-number.txt). 這個成員是 8 位寬(_IOC_TYPEBITS).
number
序(順序)號. 它是 8 位(_IOC_NRBITS)寬.
direction
數(shù)據(jù)傳送的方向(如果這個特殊的命令涉及數(shù)據(jù)傳送).
數(shù)據(jù)傳送方向是以應(yīng)用程序的觀點(diǎn)來看待的方向
_IOC_NONE: 沒有數(shù)據(jù)傳輸
_IOC_READ: 從系統(tǒng)到用戶空間
_IOC_WRITE: 從用戶空間到系統(tǒng)
_IOC_READ|_IOC_WRITE: 數(shù)據(jù)在2個方向被傳送
size
用戶數(shù)據(jù)的大小. 這個成員的寬度(_IOC_SIZEBITS)是依賴體系的. 通常是13或者14位.
命令號相關(guān)操作:
_IO(type,nr): 創(chuàng)建沒有參數(shù)的命令
_IOR(type, nre, datatype): 創(chuàng)建從驅(qū)動中讀數(shù)據(jù)的命令
_IOW(type,nr,datatype): 創(chuàng)建寫數(shù)據(jù)的命令
_IOWR(type,nr,datatype): 創(chuàng)建雙向傳送的命令
_IOC_TYPE(cmd):得到magic number
_IOC_NR(cmd):得到順序號
_IOC_DIR(cmd): 得到傳送方向
_IOC_SIZE(cmd): 得到參數(shù)大小
預(yù)定義命令(會被內(nèi)核自動識別而不會調(diào)用驅(qū)動中定義的ioctl)分為 3 類:
1. 可對任何文件發(fā)出的(常規(guī), 設(shè)備, FIFO, 或者 socket). 這類的magic number以'T'開頭
2. 只對常規(guī)文件發(fā)出的那些.
3. 對文件系統(tǒng)類型特殊的那些.
驅(qū)動開發(fā)只需注意第一類命令,以及以下這些
FIOCLEX
設(shè)置 close-on-exec 標(biāo)志(File IOctl Close on EXec).
FIONCLEX
清除 close-no-exec 標(biāo)志(File IOctl Not CLose on EXec).
FIOASYNC
設(shè)置或者復(fù)位異步通知. 注意直到 Linux 2.2.4 版本的內(nèi)核不正確地使用這個命令來修改 O_SYNC 標(biāo)志. 因?yàn)閮蓚€動作都可通過 fcntl 來完成, 沒有人真正使用 FIOASYNC 命令, 它在這里出現(xiàn)只是為了完整性.
FIOQSIZE
返回一個文件或者目錄的大小; 當(dāng)用作一個設(shè)備文件, 返回一個 ENOTTY 錯誤.
FIONBIO(File IOctl Non-Blocking I/O)
修改在 filp->f_flags 中的 O_NONBLOCK 標(biāo)志. 給這個系統(tǒng)調(diào)用的第 3 個參數(shù)用作指示是否這個標(biāo)志被置位或者清除. 注意常用的改變這個標(biāo)志的方法是使用 fcntl 系統(tǒng)調(diào)用, 使用 F_SETFL 命令.

unsigned int (*poll) (struct file *filp, poll_table *wait);
filp: 文件描述符指針
wait: 用于poll_wait函數(shù)
返回值: 可能不必阻塞就立刻進(jìn)行的操作
void poll_wait(struct file *, wait_queue_head_t *, poll_table *);
驅(qū)動通過調(diào)用函數(shù)poll_wait增加一個等待隊(duì)列到poll_table結(jié)構(gòu). 這個等待隊(duì)列是驅(qū)動定義并處理的, 進(jìn)程喚醒方式(只喚醒其中一個還是喚醒所有等待的進(jìn)程)也由驅(qū)動(read/write)來決定

返回的位掩碼:
POLLIN
設(shè)備可不阻塞地讀
POLLRDNORM
可以讀"正常"數(shù)據(jù). 一個可讀的設(shè)備返回( POLLIN|POLLRDNORM ).
POLLRDBAND
可從設(shè)備中讀取帶外數(shù)據(jù). 當(dāng)前只用在 Linux 內(nèi)核的一個地方(DECnet代碼), 并且通常對設(shè)備驅(qū)動不可用.
POLLPRI
可不阻塞地讀取高優(yōu)先級數(shù)據(jù)(帶外). 這個位使 select 報(bào)告在文件上遇到一個異常情況, 因?yàn)?selct 報(bào)告帶外數(shù)據(jù)作為一個異常情況.
POLLHUP
當(dāng)讀這個設(shè)備的進(jìn)程見到文件尾, 驅(qū)動必須設(shè)置POLLUP(hang-up). 一個調(diào)用 select 的進(jìn)程被告知設(shè)備是可讀的, 如同selcet功能所規(guī)定的.
POLLERR
一個錯誤情況已在設(shè)備上發(fā)生. 當(dāng)調(diào)用 poll, 設(shè)備被報(bào)告為可讀可寫, 因?yàn)樽x寫都返回一個錯誤碼而不阻塞.
POLLOUT
設(shè)備可被寫入而不阻塞.
POLLWRNORM
這個位和POLLOUT有相同的含義, 并且有時它確實(shí)是相同的數(shù). 一個可寫的設(shè)備返回( POLLOUT|POLLWRNORM).
POLLWRBAND
同POLLRDBAND, 這個位意思是帶有零優(yōu)先級的數(shù)據(jù)可寫入設(shè)備. 只有 poll 的數(shù)據(jù)報(bào)實(shí)現(xiàn)使用這個位, 因?yàn)橐粋€數(shù)據(jù)報(bào)看傳送帶外數(shù)據(jù).
例子:
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
這個代碼簡單地增加了 2 個 scullpipe 等待隊(duì)列到 poll_table, 接著設(shè)置正確的掩碼位, 根據(jù)數(shù)據(jù)是否可以讀或?qū)?
如果在進(jìn)程A得到poll/select/epoll通知后, 另外一個進(jìn)程B將數(shù)據(jù)讀走/將緩沖區(qū)填滿, 這時候進(jìn)程A進(jìn)來進(jìn)行讀寫操作,會如何?

int (*fasync) (int fd, struct file *filp, int mode);
異步通知. (常常假定異步能力只對 socket 和 tty 可用)
從用戶的角度看異步通知的設(shè)置過程:
1. 指定一個進(jìn)程作為文件的擁有者: fcntl 系統(tǒng)調(diào)用發(fā)出 F_SETOWN 命令, 進(jìn)程ID被保存在 filp->f_owner 給以后使用
2. 在設(shè)備中設(shè)置 FASYNC 標(biāo)志: fcntl 系統(tǒng)調(diào)用發(fā)出 F_SETFL 命令
這樣設(shè)置后設(shè)備有 新數(shù)據(jù)到達(dá)/緩沖有空間 的時候就會發(fā)送一個SIGIO信號到filp->f_owner 中的進(jìn)程(如果值為負(fù)值則發(fā)給整個進(jìn)程組).
舉例:
signal(SIGIO, &input_handler); /* dummy sample; sigaction() is better */
fcntl(STDIN_FILENO, F_SETOWN, getpid());
oflags = fcntl(STDIN_FILENO, F_GETFL); //get original setting
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
從內(nèi)核的角度看用戶設(shè)置過程
1. 當(dāng)發(fā)出 F_SETOWN, 一個值被賦值給 filp->f_owner.
2. 當(dāng)發(fā)出 F_SETFL 來打開 FASYNC, 驅(qū)動的fasync方法被調(diào)用. 無論何時filp->f_flags中的FASYNC的值有改變, 都會調(diào)用驅(qū)動中的fasync方法(這個標(biāo)志在文件打開時缺省地未設(shè)置).
3. 當(dāng)數(shù)據(jù)到達(dá), 向所有的注冊異步通知的進(jìn)程發(fā)出一個 SIGIO 信號.
從驅(qū)動的角度看內(nèi)核響應(yīng)過程:
1. 內(nèi)核的第一步與驅(qū)動無關(guān)
2. 內(nèi)核的第二步驅(qū)動應(yīng)當(dāng)用下列函數(shù)響應(yīng):
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
當(dāng) FASYNC 標(biāo)志因一個打開文件而改變, 這個函數(shù)用來從相關(guān)的進(jìn)程列表中添加或去除入口項(xiàng). 它的所有參數(shù)除了最后一個, 都直接來自fasync方法.
mode: 0: 去除入口項(xiàng); 其他: 添加入口項(xiàng)
fa: 是由驅(qū)動提供的一個struct fasync_struct結(jié)構(gòu). (*fa)在第一次使用之前應(yīng)該初始化成NULL, 不然可能會出錯. 從函數(shù)返回的時候會被分配一塊內(nèi)存, 在mode=0時free掉. 所以添加與去除入口項(xiàng)必須配對使用
void kill_fasync(struct fasync_struct **fa, int sig, int band);
數(shù)據(jù)到達(dá)時通知相關(guān)的進(jìn)程.
fa: 與fasync_help里的fa同
sig: 被傳遞的信號(常常是 SIGIO)
band: 異步狀況. 在網(wǎng)絡(luò)代碼里可用來發(fā)送"緊急"或者帶外數(shù)據(jù)
POLL_IN: 有新數(shù)據(jù)到達(dá). 等同于 POLLIN|POLLRDNORM.
POLL_OUT: 有空間可供寫入
舉例: (注意: 若設(shè)備允許多次打開, 每個打開的filp需要有獨(dú)立的async_queue)
struct fasync_struct *async_queue = NULL;
static int fasync(int fd, struct file *filp, int mode)
{
return fasync_helper(fd, filp, mode, &async_queue);
}
當(dāng)數(shù)據(jù)到達(dá), 用下面的語句來通知異步讀者.
if (async_queue)
kill_fasync(&async_queue, SIGIO, POLL_IN);
在release方法中應(yīng)該調(diào)用
/* remove this filp from the asynchronously notified filp's */
fasync(-1, filp, 0);
loff_t (*llseek) (struct file *, loff_t, int); 如果未定義llseek方法, 內(nèi)核缺省通過修改 filp->f_pos來實(shí)現(xiàn)移位
如果需要禁止lseek操作, 需要在open中調(diào)用
int nonseekable_open(struct inode *inode; struct file *filp);
并把file_operations::llseek設(shè)為no_llseek(loff_t no_llseek(struct file *file, loff_t offset, int whence))
舉例:
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence)
{
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = dev->size + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}

                
		
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護(hù)是驅(qū)動電源設(shè)計(jì)中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護(hù)成本,還影響了用戶體驗(yàn)。要解決這一問題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機(jī)驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計(jì)工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉