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内核或者驱动程序的框架提供接口,不对用户空间直接提供接口。