書名: Android系統級深入開發作者名: 韓超 梁泉本章字數: 5436字更新時間: 2018-12-29 13:39:29
3.1.3 Linux內核空間到用戶空間的接口
Linux內核中的驅動程序,是介于硬件到用戶空間之間的部分。在大多數情況下,驅動程序需要提供內核空間(Kernel Space)到用戶空間(User Space)的接口。
Linux內核空間到用戶空間的接口情況,通常分為以下幾種類型:
系統調用(System Call)
字符設備節點
塊設備節點
網絡設備
proc文件系統
sys文件系統
無用戶空間接口
1.系統調用
系統調用是指操作系統實現的所有系統調用所構成的集合,即程序接口。
Linux中系統調用分為:進程控制、文件系統控制、系統控制、內存管理、網絡管理、控制、用戶管理、進程間通信等類型。
在Linux操作系統中,系統調用的id通常在arch/{體系結構}/include/asm/目錄的unistd.h文件中。
每種體系結構的系統調用基本相同,在某些不常用的系統調用中,可能有一些不同的地方。
系統調用是內核空間到用戶空間最直接的接口,在需要增加內核到用戶空間的功能時,增加系統調用也是一種直接的方式。
由于系統調用是UNIX標準的內容,一般情況下不使用增加系統調用的方式增加內核空間用戶空間的接口。
Android系統沒有對標準的Linux增加系統調用。
2.字符設備節點
字符設備特殊文件進行I/O操作不經過操作系統的緩沖區,進行I/O操作時每次只傳輸一個字符。典型的字符設備如:鼠標、鍵盤、串口等。字符設備可以通過字符設備文件來訪問。
文件操作file_operations表示了對一個文件的操作。其結構在Linux源文件的include/linux目錄的fs.h文件中定義。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); };
對于一般的文件,可以使用通用的文件操作。在文件操作中,open函數指針對應open()系統調用;read函數指針對應read()系統調用;write函數指針對應write()系統調用;ioctl函數指針對應ioctl()系統調用;mmap函數指針對應mmap()系統調用;release函數指針對應close()系統調用,同時當文件的所有引用都被注銷后,也會被調用。
字符設備的注冊和注銷,通常使用fs.h中的以下兩個函數:
extern int register_chrdev(unsigned int, const char *, const struct file_operations *); extern void unregister_chrdev(unsigned int, const char *);
對于簡單的字符設備,也是驅動的設備節點,但是其文件操作和一般的文件是不一樣的,需要單獨實現。
字符設備的驅動程序結構如圖3-3所示。

圖3-3 字符設備的驅動程序結構
對于字符設備的驅動程序,可以基于字符設備的驅動程序框架構建(如A驅動程序),也可以基于特定的字符設備驅動框架來構建(如B驅動程序)。
基于字符設備的驅動程序框架構建:直接實現對字符設備的注冊函數進行注冊,這樣定義的字符設備一般具有一個主設備號,需要使用沒有分配或者沒有使用的主設備號。在注冊具體的字符設備時,構建file_operations中的各個成員函數指針,在用戶空間通過/dev/中的設備節點調用驅動程序的時候,實際上是調用內核中字符設備驅動程序的框架,字符設備驅動程序的框架再調用具體驅動程序注冊的回調函數。
基于特定的字符設備驅動框架來構建:這些驅動程序的框架通常對字符設備框架進行封裝,實現自己的框架,并且具有了自己的注冊機制。這樣在具體的驅動程序的實現中,通過這種驅動程序框架的注冊函數進行注冊,實現相關的函數。這種情況下,驅動程序的框架一般已經實現了主設備號,具體的驅動程序實現的是次設備號。在用戶空間通過/dev/中的設備節點調用驅動程序時,實際上是先調用內核中字符設備驅動程序的框架,字符設備驅動程序的框架再調用該類驅動程序的框架,該類驅動程序的框架最后調用具體驅動程序的實現。這種方式更為常用,例如,Misc驅動程序、幀緩沖驅動程序、TTY驅動程序等。
用戶空間對字符設備的訪問,通常使用如下的文件操作接口:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> int open( const char * pathname, int flags); int close(int fd); ssize_t read(int fd,void * buf ,size_t count); ssize_t write (int fd,const void * buf,size_t count); off_t lseek(int fildes,off_t offset ,int whence); int ioctl(int fd ,unsigned int cmd,…); void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
在Android系統中,包含了眾多的標準的字符設備和Android特有的字符設備,設備節點大部分在/dev/目錄中,例如在Android仿真器的系統中,使用Goldfish內核,dev目錄中的部分內容如下所示:
# ls -l /dev/ crw-rw-rw- root root 5, 2 2010-02-10 06:39 ptmx crw------- root root 7, 128 2010-02-10 06:39 vcsa crw------- root root 7, 0 2010-02-10 06:39 vcs crw------- root root 253, 2 2010-02-10 06:40 ttyS2 crw------- root root 253, 1 2010-02-10 06:41 ttyS1 crw------- root root 253, 0 2010-02-10 06:39 ttyS0 crw------- root root 4, 1 2010-02-10 06:09 tty1 crw-rw---- root system 4, 0 2010-02-10 06:09 tty0 crw------- root root 5, 1 2010-02-10 06:09 console crw-rw-rw- root root 5, 0 2010-02-10 06:09 tty crw------- root root 10, 53 2010-02-10 06:09 network_throughput crw------- root root 10, 54 2010-02-10 06:09 network_latency crw------- root root 10, 55 2010-02-10 06:09 cpu_dma_latency crw-rw-rw- root root 10, 59 2010-02-10 06:09 binder crw------- root root 10, 60 2010-02-10 06:09 device-mapper crw-rw-r-- system radio 10, 61 2010-02-10 06:09 alarm crw------- root root 10, 1 2010-02-10 06:09 psaux crw-rw-rw- root root 10, 62 2010-02-10 06:09 ashmem crw-rw---- root audio 10, 63 2010-02-10 06:09 eac crw------- root root 1, 11 2010-02-10 06:09 kmsg crw-rw-rw- root root 1, 9 2010-02-10 06:10 urandom crw-rw-rw- root root 1, 8 2010-02-10 06:09 random crw-rw-rw- root root 1, 7 2010-02-10 06:09 full crw-rw-rw- root root 1, 5 2010-02-10 06:09 zero crw-rw-rw- root root 1, 3 2010-02-10 06:09 null crw------- root root 1, 2 2010-02-10 06:09 kmem crw------- root root 1, 1 2010-02-10 06:09 mem crw------- root root 254, 0 2010-02-10 06:09 rtc0
自左至右,內容依次是設備屬性、設備所屬用戶、設備所屬組、設備主設備號、設備次設備號、日期、設備名稱。
在以上的設備節點中,主設備號為1的mem,null,zero,null等為Linux標準的內存設備(mem device)。主設備號為4和5的各個設備,分別為TTY終端設備,均為Linux標準的內容。
主設備號為10的各個設備,為Linux中的misc設備(雜項設備),psaux為標準的PS/2鼠標驅動,其中次設備號為53之后的各個設備如ashmem,binder等,為Android的特定設備。
另外,rtc,ttyS等為實時時鐘,串口驅動。
有一些設備在/dev/的次級目錄中,Android中幀緩沖(framebuffer)的設備節點沒有在標準的/dev/目錄中,而是在/dev/graphics目錄中,如下所示:
# ls -l /dev/graphics crw-rw---- root graphics 29, 0 2010-02-10 06:39 fb0
framebuffer設備的主設備號為29,次設備號根據設備數目依次生成。
目錄/dev/input中包含的是輸入設備的節點,如下所示:
# ls -l /dev/input crw-rw---- root input 13, 64 2010-02-10 06:39 event0 crw-rw---- root input 13, 32 2010-02-10 06:39 mouse0 crw-rw---- root input 13, 63 2010-02-10 06:39 mice
輸入設備的主設備號為13,其中次設備號64之后的設備為Event設備。
目錄/dev/mtd中包含了mtd字符設備的節點,如下所示:
# ls -l /dev/mtd crw------- root root 90, 5 2010-02-10 06:39 mtd2ro crw------- root root 90, 4 2010-02-10 06:39 mtd2 crw------- root root 90, 3 2010-02-10 06:39 mtd1ro crw------- root root 90, 2 2010-02-10 06:39 mtd1 crw------- root root 90, 1 2010-02-10 06:39 mtd0ro crw------- root root 90, 0 2010-02-10 06:39 mtd0
mtd的字符設備的主設備號為90,一般包含一個只讀設備和一個可讀寫設備,
目錄/dev/log中為Android特有的log驅動程序,如下所示:
# ls -l /dev/log crw-rw--w- root log 10, 56 2010-02-10 06:39 radio crw-rw--w- root log 10, 57 2010-02-10 06:39 events crw-rw--w- root log 10, 58 2010-02-10 06:39 main
Android的log驅動同樣是misc驅動程序,包含3個次設備,main為主要log設備, event為事件的log設備,radio為Modem端的設備。
3.塊設備驅節點
塊設備使用隨機訪問的方式傳輸數據,并且數據總是具有固定大小的塊。為了提高數據傳輸效率,塊設備驅動程序內部采用塊緩沖技術。典型的塊設備如:光盤、硬盤、軟盤等。塊設備可以通過塊文件來訪問。
塊設備的操作在include/linux/fs.h中定義:
struct block_device_operations { int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); long (*compat_ioctl) (struct file *, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, void **, unsigned long *); int (*media_changed) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); struct module *owner; };
塊設備的注冊和注銷函數:
extern int register_blkdev(unsigned int, const char *); extern void unregister_blkdev(unsigned int, const char *);
塊設備驅動程序的結構如圖3-4所示。

圖3-4 塊設備驅動程序的結構
對于塊設備,由于也是設備節點,也可以使用文件接口來訪問。然而,根據Linux操作系統的一般使用方式,通常使用文件系統,而不是直接訪問塊設備的節點。
Android的塊設備在/dev/block目錄中,主要的內容如下所示:
# ls -l /dev/block brw------- root root 31, 2 2010-02-10 06:39 mtdblock2 brw------- root root 31, 1 2010-02-10 06:39 mtdblock1 brw------- root root 31, 0 2010-02-10 06:39 mtdblock0 brw------- root root 7, 3 2010-02-10 06:39 loop3 brw------- root root 7, 2 2010-02-10 06:39 loop2 brw------- root root 7, 1 2010-02-10 06:39 loop1 brw------- root root 7, 0 2010-02-10 06:39 loop0 brw------- root root 1, 2 2010-02-10 06:39 ram2 brw------- root root 1, 1 2010-02-10 06:39 ram1 brw------- root root 1, 0 2010-02-10 06:39 ram0 brw------- root root 179, 0 2010-02-10 06:39 mmcblk0
其中,主設備號為1的為各個內存塊設備,主設備號為7的為各個回環塊設備,主設備號為31的為mtd設備中的塊設備,mmcblk0為SD卡的塊設備。
塊設備的主要使用方式是文件系統。在Android中,可以使用mount命令查看系統中各個被掛接的文件系統,如下所示:
# mount rootfs / rootfs ro 0 0 tmpfs /dev tmpfs rw,mode=755 0 0 devpts /dev/pts devpts rw,mode=600 0 0 proc /proc proc rw 0 0 sysfs /sys sysfs rw 0 0 none /acct cgroup rw,cpuacct 0 0 tmpfs /mnt/asec tmpfs rw,mode=755,gid=1000 0 0 none /dev/cpuctl cgroup rw,cpu 0 0 /dev/block/mtdblock0 /system yaffs2 ro 0 0 /dev/block/mtdblock1 /data yaffs2 rw,nosuid,nodev 0 0 /dev/block/mtdblock2 /cache yaffs2 rw,nosuid,nodev 0 0 /dev/block/vold/179:0 /mnt/sdcard vfat rw,dirsync,nosuid,nodev,noexec,uid=1000, gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,iocharset=iso8859-1, shortname=mixed,utf8,errors=remount-ro 0 0 /dev/block/vold/179:0 /mnt/secure/asec vfat rw,dirsync,nosuid,nodev,noexec,uid= 1000,gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,iocharset=iso88 59-1,shortname=mixed,utf8,errors=remount-ro 0 0 tmpfs /mnt/sdcard/.android_secure tmpfs ro,size=0k,mode=000 0 0
根據以上的內容可見,/dev/block/mtdblock0、/dev/block/mtdblock1 和/dev/block/mtdblock2這3個mtd塊設備分別為掛接到/system、/data和/cache目錄中。在實際的系統中, MTD設備通常是基于Flash設備的,這些設備使用的是yaffs的文件系統。
/dev/block/vold/179:0設備實際上就是/dev/mmcblk0設備被vold程序使用的結果,它被掛接到/sdcard中,這在實際的系統中是由SD卡構建的,在仿真器的環境中,使用的是文件模擬的方式。它使用的是vfat格式的文件系統。
在Android系統中,同樣可以使用df查看系統中各個盤的情況:
# df /dev: 47048K total, 0K used, 47048K available (block size 4096) /mnt/asec: 47048K total, 0K used, 47048K available (block size 4096) /system: 65536K total, 56876K used, 8660K available (block size 4096) /data: 65536K total, 22476K used, 43060K available (block size 4096) /cache: 65536K total, 1156K used, 64380K available (block size 4096) /mnt/sdcard: 64504K total, 1K used, 64502K available (block size 512) /mnt/secure/asec: 64504K total, 1K used, 64502K available (block size 512)
4.網絡設備
網絡設備是一種特殊的設備,與字符設備和塊設備不同,網絡設備并沒有文件系統的節點,也就是說網絡設備沒有設備文件。在Linux的網絡系統中,使用UNIX的socket機制。系統與驅動程序之間通過專有的數據結構進行訪問。系統內部支持數據的收發,對網絡設備的使用需要通過socket,而不是文件系統的節點。
網絡設備的核心是include/linux目錄中的頭文件netdevice.h中定義的struct net_device結構體。
網絡設備的注冊和注銷函數如下所示:
extern int register_netdev(struct net_device *dev); extern void unregister_netdev(struct net_device *dev);
網絡驅動和字符設備驅動、塊設備的最大不同點是它沒有文件系統的節點。Linux網絡驅動程序的調用,要通過struct net_device數據結構。一般的網絡驅動程序不會由應用程序調用,而是由Linux內核的網絡模塊調用。用戶空間的程序通過socket等通用網絡接口,間接調用網絡驅動程序。網絡設備的驅動程序結構如圖3-5所示。

圖3-5 網絡設備的驅動程序結構
對于網絡設備的訪問,通常需要使用Socket(套接字)相關的幾個函數:socket(),bind (),listen (),accept(),connect()。
#include <sys/types.h> #include <sys/socket.h> int socket (int family, int type, int protocol); int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); int listen( int s, int backlog ); int accept(int s, struct sockaddr *addr, socklen_t *addrlen); int connect( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen );
其中socket()的返回類型也是一個文件描述符,和open返回的類型一樣,表示一個在進程中打開的文件。這個文件描述符也可以用通常的文件操作函數來操作。網絡中使用的參數與幾個主要結構體sockaddr、sockaddr_in、in_addr,和網絡的地址、協議類型等有關系。
在Android的仿真器的環境中,使用ifconfig命令可以查詢系統中的網絡設備,仿真器中具有以太網,因此可以使用如下的命令得到。
# ifconfig eth0 eth0: ip 10.0.2.15 mask 255.255.255.0 flags [up broadcast running multicast]
在實際的系統中,可能具有Wifi網絡和電話網絡,同樣可以使用ifconfig命令得到。
5.proc文件系統接口
proc文件系統常常被放置于Linux系統上的/proc目錄中。常常用于查看有關硬件、進程的狀態,可以通過操作proc文件系統進行控制工作。
在include/proc_fs.h中,定義創建proc文件系統的函數:
extern struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent); struct proc_dir_entry *proc_create_data(const char *name, mode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data); extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
proc_dir_entry結構表示一個目錄項的入口,定義如下所示:
struct proc_dir_entry { unsigned int low_ino; unsigned short namelen; const char *name; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; loff_t size; const struct inode_operations *proc_iops; /* 文件系統操作 */ const struct file_operations *proc_fops; /* 文件系統節點操作 */ struct module *owner; struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; /* use count */ int pde_users; /* number of callers into module in progress */ spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */ struct completion *pde_unload_completion; struct list_head pde_openers; /* who did ->open, but not ->release */ };
其中讀、寫相關的兩個函數指針類型如下所示:
typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data); typedef int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);
在通常情況下,實現read_proc和write_proc可以分別實現對proc文件系統中的文件進行查看和控制的功能。
此外proc_dir_entry中包含了file_operations(文件操作)和inode_operations(文件系統節點操作)類型的結構,實現這兩個結構可以完成更加深入復雜的操作。事實上,可以使用proc實現和設備節點類似的功能。
Android系統對proc文件系統增加的內容不多。例如:ramconsole驅動可以利用proc中的文件查看系統的printk信息。
6.sys文件系統接口
sys文件系統是Linux內核中設計的較新的一種基于內存的文件系統,它的作用與proc有些類似,但除了與proc相同的具有查看和設定內核參數功能之外,還有為Linux統一設備模型作為管理之用。
sys文件系統只有讀和寫兩個接口,比較簡單,因此不能支持復雜的操作,例如:復雜的參數傳遞、大規模數據塊操作等。
sys文件系統被掛接到Linux文件系統的/sys目錄中,各個項目的內容如下所示。
/sys/block:系統中當前所有的塊設備所在。
/sys/bus:這是內核設備按總線類型分層放置的目錄結構,devices中的所有設備都連接于某種總線之下。
/sys/class:這是按照設備功能分類的設備模型。
/sys/devices:內核對系統中所有設備的分層次表達模型。
/sys/dev:字符設備和塊設備的主次號,鏈接到真正的設備(/sys/devices)中。
/sys/fs:按照設計是用于描述系統中所有文件系統。
/sys/kernel:內核所有可調整參數的位置。
/sys/module:系統中所有模塊的信息。
/sys/power:系統中電源選項。
可以通過sys文件系統操作其中的文件實現兩方面的功能:
顯示信息:可以通過cat命令。
控制:可以通過cat命令或者echo命令實現,使用重定向的方式輸入。
例如,在Linux標準的sys文件系統中,查看系統所支持的各種休眠模式的命令如下所示:
$ cat /sys/power/state standby mem disk
控制進入休眠的命令如下所示:
$ echo standby > /sys/power/state
sys文件系統的構建比較容易,主要使用include/linux/sysfs.h目錄中的__ATTR和__ATTR_RO宏來完成。
#define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode }, \ .show = _show, \ .store = _store, \ } #define __ATTR_RO(_name) { \ .attr = { .name = __stringify(_name), .mode = 0444 }, \ .show = _name##_show, \ }
創建sysfs中的文件,在內核中可以使用如下函數:
int __must_check sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
其中,在文件被讀取的時候將調用show接口,在文件被寫入的使用將調用store函數,其中字符串的內容,將有實現者解析和構成。
在Android系統中,一個重要的sys文件系統內容是/sys/power/中有關電源管理的內容,如下所示:
# ls -l /sys/power/ -rw-rw---- radio system 4096 2010-02-10 06:39 state -rw-rw---- radio system 4096 2010-02-10 06:39 wake_lock -rw-rw---- radio system 4096 2010-02-10 06:39 wake_unlock
wake_lock和wake_unlock為Android中特有屬性,用于控制系統是否可以睡眠。
7.無直接用戶空間接口
某些驅動程序只對Linux內核或者驅動程序的框架提供接口,不對用戶空間直接提供接口。
- Learning Selenium Testing Tools with Python
- FFmpeg入門詳解:音視頻流媒體播放器原理及應用
- NativeScript for Angular Mobile Development
- jQuery從入門到精通 (軟件開發視頻大講堂)
- Blender 3D Incredible Machines
- HTML5+CSS3網頁設計
- 劍指Java:核心原理與應用實踐
- Node.js:來一打 C++ 擴展
- Linux C編程:一站式學習
- 新一代SDN:VMware NSX 網絡原理與實踐
- Rust游戲開發實戰
- Beginning C++ Game Programming
- R Data Science Essentials
- Xamarin Blueprints
- Java EE Web應用開發基礎