以下内容为操作系统课设期间遇到的一些问题的补充
__user含意 # define __user __attribute__((noderef, address_space(1)))
_user这个特性,即attribute ((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1,即用户程序空间的。 这里把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。
attribute 是gnu c编译器的一个功能,它用来让开发者使用此功能给所声明的函数或者变量附加一个属性,以方便编译器进行错误检查,其实就是一个内核检查器。
linux把操作系统内存和用户区内存隔离开, 用户程序只能通过系统调用访问系统功能, 内核态可以访问用户内存,但是要做检查,因为用户区内存是不可靠的,甚至是危险的。_user就表示这个意思。
以下内容来自
首先看一下linux内核4.20.1源码:
linux/linux/compile_types.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # define __user __attribute__((noderef, address_space(1))) # define __kernel __attribute__((address_space(0))) # define __safe __attribute__((safe)) # define __force __attribute__((force)) # define __nocast __attribute__((nocast)) # define __iomem __attribute__((noderef, address_space(2))) # define __must_hold(x) __attribute__((context(x,1,1))) # define __acquires(x) __attribute__((context(x,0,1))) # define __releases(x) __attribute__((context(x,1,0))) # define __acquire(x) __context__(x,1) # define __release(x) __context__(x,-1) # define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0) # define __percpu __attribute__((noderef, address_space(3))) # define __rcu __attribute__((noderef, address_space(4))) # define __private __attribute__((noderef))
Sparse 诞生于 2004 年, 是由linux之父开发的, 目的就是提供一个静态检查代码的工具, 从而减少linux内核的隐患. 其实在Sparse之前, 已经有了一个不错的代码静态检查工具(“SWAT”), 只不过这个工具不是免费软件, 使用上有一些限制.所以 linus 还是自己开发了一个静态检查工具.(参考 ,原文 )
Sparse通过 gcc 的扩展属性 attribute 以及自己定义的 context 来对代码进行静态检查.
宏名称
宏定义
检查点
__bitwise
attribute ((bitwise))
确保变量是相同的位方式(比如 bit-endian, little-endiandeng)
__user
attribute ((noderef, address_space(1)))
指针地址必须在用户地址空间
__kernel
attribute ((noderef, address_space(0)))
指针地址必须在内核地址空间
__iomem
attribute ((noderef, address_space(2)))
指针地址必须在设备地址空间
__safe
attribute ((safe))
变量可以为空
__force
attribute ((force))
变量可以进行强制转换
__nocast
attribute ((nocast))
参数类型与实际参数类型必须一致
__acquires(x)
attribute ((context(x, 0, 1)))
参数x 在执行前引用计数必须是0,执行后,引用计数必须为1
__releases(x)
attribute ((context(x, 1, 0)))
与 __acquires(x) 相反
__acquire(x)
context (x, 1)
参数x 的引用计数 + 1
__release(x)
context (x, -1)
与 __acquire(x) 相反
__cond_lock(x,c)
((c) ? ({ __acquire(x); 1; }) : 0)
参数c 不为0时,引用计数 + 1, 并返回1
__user 的使用 如果使用了 __user 宏的指针不在用户地址空间初始化, 或者指向内核地址空间, 设备地址空间等等, Sparse会给出警告.
1 2 static int setup_sigcontext (struct pt_regs *regs, struct sigcontext __user *sc)
关于get_ds, set_fs, get_fs函数的使用 在linux内核编程时,进行系统调用(如文件操作)时如果要访问用户空间的参数,可以用set_fs,get_ds等函数实现访问:
get_ds获得kernel的内存访问地址范围(IA32是4GB),
get_fs是取得当前的地址访问限制值。
set_fs是设置当前的地址访问限制值
进程由用户态进入核态,linux进程的task_struct结构中的成员addr_limit也应该由0xBFFFFFFF变为0xFFFFFFFF(addr_limit规定了进程有用户态核内核态情况下的虚拟地址空间访问范围,在用户态,addr_limit成员值是0xBFFFFFFF也就是有3GB的虚拟内存空间,在核心态,是0xFFFFFFFF,范围扩展了1GB)。使用这三个函数是为了安全性。为了保证用户态的地址所指向空间有效,函数会做一些检查工作。如果set_fs(KERNEL_DS),函数将跳过这些检查。
具体用法参考示例
copy_to_user和copy_from_user 首先解决一个问题:
1. 为什么要划分为内核空间和用户空间? Linux Kernel是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。 对于Kernel这么一个高安全级别的东西,显然是不容许其它的应用程序随便调用或访问的,所以需要对Kernel提供一定的保护机制,这个保护机制用来告诉那些应用程序,你只可以访问某些许可的资源,不许可的资源是拒绝被访问的,于是就把Kernel和上层的应用程序抽像的隔离开,分别称之为Kernel Space和User Space。
2. 用户空间的程序如何对内核空间进行访问? 上面说到用户态和内核态是两个隔离的空间,虽然从逻辑上被抽像的隔离,但无可避免的是,总是会有那么一些用户空间需要访问内核空间的资源,怎么办呢?
http://blog.csdn.net/ysgjiangsu/article/details/49995229 从上图结构中可以看出,Kernel Space层从下至上包括: Arch:对应Kernel里arch目录,含有诸如x86, ia64, arm, s390等体系结构的支持; Device Driver:对应Kernel里drivers目录,含有block, char, net, usb等不同硬件驱动的支持; 在Arch和Driver之上,是对内存,进程,文件系统,网络协议栈等的支持;
最上一层是System Call Interface,系统调用接口,正如其名,这层就是用户空间与内核空间的桥梁,用户空间的应用程序通过System Call这个统一入口来访问系统中的硬件资源,通过此接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
3.copy_to_user()在内核定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned long copy_to_user (void __user *to, const void *from, unsigned long n) { if (access_ok(VERIFY_WRITE, to, n)) n = __copy_to_user(to, from, n); return n; }
其功能是将内核空间的内容复制到用户空间,所复制的内容是从from来,到to去,复制n个位。 其中又牵扯到两个函数:access_ok() 和__copy_to_user() ,好我们继续往下深入,先来看看第一个函数access_ok() 的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #ifdef CONFIG_MMU #define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0)) #else static inline int access_ok (int type, const void *addr, unsigned long size) { extern unsigned long memory_start, memory_end; unsigned long val = (unsigned long )addr; return ((val >= memory_start) && ((val + size) < memory_end)); } #endif
其功能是检查用户空间是否合法,它的第一个参数:type,有两种 类型:VERIFY_READ 和VERIFY_WRITE,前者为可读,后者可写,注意:如果标志为可写(VERIFY_WRITE)时,必然可读!因为可写是可读的超集 (%VERIFY_WRITE is a superset of %VERIFY_READ)。 检查过程如下:addr为起始地址,size为所要复制的大小,那么从addr到addr+size则是所要检查的空间,如果它的范围在memory_start和memory_end之间的话,则返回真。至于memory_start详细信息,我没有读。 到此为止,如果检查合法,那么OK,我们来实现真正的复制功能:__copy_to_user() ,其源码定义如下:
1 2 3 4 5 static inline __kernel_size_t __copy_to_user(void __user *to, const void *from, __kernel_size_t n) { return __copy_user((void __force *)to, from, n); }
又遇到一个函数:__copy_user() ,这个函数才真正在做底层的复制工作__copy_user 宏__copy_user在include/asm-i386/uaccess.h中定义,是作为从用户空间和内核空间进行内存复制的关键。这个宏扩展为汇编后如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 000 #define __copy_user(to,from,size) 001 do {002 int __d0, __d1;003 __asm__ __volatile__(004 "0: rep; movsl\n" 005 " movl %3,%0\n" 006 "1: rep; movsb\n" 007 "2:\n" 008 ".section .fixup,\"ax\"\n" 009 "3: lea 0(%3,%0,4),%0\n" 010 " jmp 2b\n" 011 ".previous\n" 012 ".section __ex_table,\"a\"\n" 013 " .align 4\n" 014 " .long 0b,3b\n" 015 " .long 1b,2b\n" 016 ".previous" 017 : "=&c" (size), "=&D" (__d0), "=&S" (__d1)018 : "r" (size & 3 ), "0" (size / 4 ), "1" (to), "2" (from)019 : "memory" );020 } while (0 )
这段代码的主要操作就是004-007行,它的主要功能是将from处长度为size的数据复制到to处。
文件系统 详见深入理解 Linux 文件系统
设备驱动 基础知识 系统调用是操作系统内核和应用程序之间的接口,而设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:
对设备初始化和释放.
把数据从内核传送到硬件和从硬件读取数据.
读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
检测和处理设备出现的错误.
Linux支持三中不同类型的设备:字符设备(character devices)、块设备(block devices)和网络设备(network interfaces)。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待.
用户进程是通过设备文件来与实际的硬件打交道,每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备。另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备, 比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序.。
设备驱动程序工作的基本原理:
用户进程利用系统调用对设备进行诸如read/write操作,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
最后,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,你只有重新启动机器了。
添加新模块的基本步骤 写设备驱动源代码 在设备驱动程序中有一个非常重要的结构file_operations,该结构的每个域都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct file_operations { int (*seek) (struct inode * ,struct file *, off_t ,int ); int (*read) (struct inode * ,struct file *, char ,int ); int (*write) (struct inode * ,struct file *, off_t ,int ); int (*readdir) (struct inode * ,struct file *, struct dirent * ,int ); int (*select) (struct inode * ,struct file *, int ,select_table *); int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long ); int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *); int (*open) (struct inode * ,struct file *); int (*release) (struct inode * ,struct file *); int (*fsync) (struct inode * ,struct file *); int (*fasync) (struct inode * ,struct file *,int ); int (*check_media_change) (struct inode * ,struct file *); int (*revalidate) (dev_t dev); }
编写设备驱动程序的主要工作是编写子函数,并填充** file_operations**的各个域 。
例如:
1 2 3 4 5 6 Struct file_operations my_fops={ .read=my_read, .write=my_write, .open=my_open, .release=my_release }
然后再定义函数my_read,my_write,my_open,my_release相应的函数体.对于可卸载的内核模块(LKM),至少还有两个基本的模块:。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 static int globalvar_init (void ) { int result = 0 ; int err = 0 ; dev_t dev = MKDEV(major, 0 ); if (major) { result = register_chrdev_region(dev, 1 , "charmem" ); } else { result = alloc_chrdev_region(&dev, 0 , 1 , "charmem" ); major = MAJOR(dev); } if (result < 0 ) return result; cdev_init(&globalvar.devm, &globalvar_fops); globalvar.devm.owner = THIS_MODULE; err = cdev_add(&globalvar.devm, dev, 1 ); if (err) printk(KERN_INFO "Error %d adding char_mem device" , err); else { printk("globalvar register success\n" ); sema_init(&globalvar.sem,1 ); init_waitqueue_head(&globalvar.outq); globalvar.rd = globalvar.buffer; globalvar.wr = globalvar.buffer; globalvar.end = globalvar.buffer + MAXNUM; globalvar.flag = 0 ; } my_class = class_create(THIS_MODULE, "chardev0" ); device_create(my_class, NULL , dev, NULL , "chardev0" ); return 0 ; } static void globalvar_exit (void ) { device_destroy(my_class, MKDEV(major, 0 )); class_destroy(my_class); cdev_del(&globalvar.devm); unregister_chrdev_region(MKDEV(major, 0 ), 1 ); }
编译安装模块 Makefile:
1 2 3 4 5 6 7 obj-m += globalvar.o #obj-m 指编译成外部模块 build: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
然后
1 2 make insmod globalvar.ko
卸载模块:
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main (int argc, char ** argv) { int fd; char buf[1024 ]; int start, len; fd = open("/dev/mydev" , O_RDWR); if (fd == -1 ) { printf ("Open device error!\n" ); return -1 ; } if (argc == 4 && strncmp (argv[1 ], "read" , 4 ) == 0 ) { start = atoi(argv[2 ]); len = atoi(argv[3 ]); lseek(fd, start, SEEK_SET); read(fd, buf, len); printf ("Character device read : %s\n" , buf); } else if (argc == 4 && strncmp (argv[1 ], "write" , 5 ) == 0 ) { start = atoi(argv[2 ]); lseek(fd, start, SEEK_CUR); write(fd, argv[3 ], strlen (argv[3 ])); } else { printf ("Usage : ./executable file <read | write> <start_offset> <len | string>\n" ); } close(fd); return 0 ; }
参考代码来自
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/wait.h> #include <linux/semaphore.h> #include <linux/sched.h> #include <linux/cdev.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/device.h> #define MAXNUM 100 #define MAJOR_NUM 456 struct dev { struct cdev devm ; struct semaphore sem ; wait_queue_head_t outq; int flag; char buffer[MAXNUM+1 ]; char *rd,*wr,*end; }globalvar; static struct class *my_class ;int major=MAJOR_NUM;static ssize_t globalvar_read (struct file *,char *,size_t ,loff_t *) ;static ssize_t globalvar_write (struct file *,const char *,size_t ,loff_t *) ;static int globalvar_open (struct inode *inode,struct file *filp) ;static int globalvar_release (struct inode *inode,struct file *filp) ;struct file_operations globalvar_fops ={ .read=globalvar_read, .write=globalvar_write, .open=globalvar_open, .release=globalvar_release, }; static int globalvar_init (void ) { int result = 0 ; int err = 0 ; dev_t dev = MKDEV(major, 0 ); if (major) { result = register_chrdev_region(dev, 1 , "charmem" ); } else { result = alloc_chrdev_region(&dev, 0 , 1 , "charmem" ); major = MAJOR(dev); } if (result < 0 ) return result; cdev_init(&globalvar.devm, &globalvar_fops); globalvar.devm.owner = THIS_MODULE; err = cdev_add(&globalvar.devm, dev, 1 ); if (err) printk(KERN_INFO "Error %d adding char_mem device" , err); else { printk("globalvar register success\n" ); sema_init(&globalvar.sem,1 ); init_waitqueue_head(&globalvar.outq); globalvar.rd = globalvar.buffer; globalvar.wr = globalvar.buffer; globalvar.end = globalvar.buffer + MAXNUM; globalvar.flag = 0 ; } my_class = class_create(THIS_MODULE, "chardev0" ); device_create(my_class, NULL , dev, NULL , "chardev0" ); return 0 ; } static int globalvar_open (struct inode *inode,struct file *filp) { try_module_get(THIS_MODULE); printk("This chrdev is in open\n" ); return (0 ); } static int globalvar_release (struct inode *inode,struct file *filp) { module_put(THIS_MODULE); printk("This chrdev is in release\n" ); return (0 ); } static void globalvar_exit (void ) { device_destroy(my_class, MKDEV(major, 0 )); class_destroy(my_class); cdev_del(&globalvar.devm); unregister_chrdev_region(MKDEV(major, 0 ), 1 ); } static ssize_t globalvar_read (struct file *filp,char *buf,size_t len,loff_t *off) { if (wait_event_interruptible(globalvar.outq,globalvar.flag!=0 )) { return -ERESTARTSYS; } if (down_interruptible(&globalvar.sem)) { return -ERESTARTSYS; } globalvar.flag = 0 ; printk("into the read function\n" ); printk("the rd is %c\n" ,*globalvar.rd); if (globalvar.rd < globalvar.wr) len = min(len,(size_t )(globalvar.wr - globalvar.rd)); else len = min(len,(size_t )(globalvar.end - globalvar.rd)); printk("the len is %d\n" ,len); if (copy_to_user(buf,globalvar.rd,len)) { printk(KERN_ALERT"copy failed\n" ); up(&globalvar.sem); return -EFAULT; } printk("the read buffer is %s\n" ,globalvar.buffer); globalvar.rd = globalvar.rd + len; if (globalvar.rd == globalvar.end) globalvar.rd = globalvar.buffer; up(&globalvar.sem); return len; } static ssize_t globalvar_write (struct file *filp,const char *buf,size_t len,loff_t *off) { if (down_interruptible(&globalvar.sem)) { return -ERESTARTSYS; } if (globalvar.rd <= globalvar.wr) len = min(len,(size_t )(globalvar.end - globalvar.wr)); else len = min(len,(size_t )(globalvar.rd-globalvar.wr-1 )); printk("the write len is %d\n" ,len); if (copy_from_user(globalvar.wr,buf,len)) { up(&globalvar.sem); return -EFAULT; } printk("the write buffer is %s\n" ,globalvar.buffer); printk("the len of buffer is %d\n" ,strlen (globalvar.buffer)); globalvar.wr = globalvar.wr + len; if (globalvar.wr == globalvar.end) globalvar.wr = globalvar.buffer; up(&globalvar.sem); globalvar.flag=1 ; wake_up_interruptible(&globalvar.outq); return len; } module_init(globalvar_init); module_exit(globalvar_exit); MODULE_LICENSE("GPL" );
mydev:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 #include <linux/kernel.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/uaccess.h> #define MAXNUM 1024 #define MAJOR_NUM 300 MODULE_LICENSE("GPL" ); struct dev { struct cdev devm ; char buf[MAXNUM + 1 ]; char * rd, * wr, * end; }globalvar; static struct class * my_class ;static int my_open (struct inode* inode, struct file* filp) { printk("Character device is open.\n" ); filp->private_data = &globalvar; return 0 ; } static int my_release (struct inode* inode, struct file* filp) { printk("Character device is released.\n" ); return 0 ; } static void mydev_exit (void ) { printk("Unload module: mydev.\n" ); device_destroy(my_class, MKDEV(MAJOR_NUM, 0 )); class_destroy(my_class); cdev_del(&globalvar.devm); cdev_del(&globalvar.devm); unregister_chrdev_region(MKDEV(MAJOR_NUM, 0 ), 1 ); } static ssize_t my_read (struct file* filp, char __user* buf, size_t len, loff_t * off) { printk(KERN_INFO "Character device start read.\n" ); int rtn = 0 ; int free_mem = MAXNUM - *off; struct dev * dev = filp->private_data; if (len <= free_mem) { if (copy_to_user(buf, dev->buf + *off, len)) { printk(KERN_ALERT "1.Copy to user buffer failed.\n" ); return -EFAULT; } *off += len; rtn = len; } else { if (copy_to_user(buf, dev->buf + *off, free_mem)) { printk(KERN_ALERT"2.Copy to user buffer failed.\n" ); return -EFAULT; } *off += free_mem; rtn = free_mem; } printk(KERN_INFO "Character device has read %d bytes.\n" , rtn); return rtn; } static ssize_t my_write (struct file* filp, const char __user * buf, size_t len, loff_t * off) { printk(KERN_INFO "Start write.\n" ); int rtn = 0 ; int free_mem = MAXNUM - *off; struct dev * dev = filp->private_data; if (len <= free_mem) { if (copy_from_user(dev->buf + *off, buf, len)) { printk(KERN_ALERT "1.Copy frome user buffer failed.\n" ); return -EFAULT; } *off += len; rtn = len; } else { if (copy_from_user(dev->buf + *off, buf, free_mem)) { printk(KERN_ALERT"2.Copy frome user buffer failed.\n" ); return -EFAULT; } *off += free_mem; rtn = free_mem; } printk("Character device has written %d bytes.\n" , rtn); return rtn; } static loff_t my_llseek (struct file* filp, loff_t off, int whence) { printk(KERN_INFO "Character device start lseek.\n" ); loff_t rtn = 0 ; switch (whence) { case SEEK_SET: rtn = off; break ; case SEEK_CUR: rtn = filp->f_pos + off; break ; case SEEK_END: rtn = MAXNUM + off; break ; default : return -EINVAL; } if (rtn < 0 || rtn > MAXNUM) return -EINVAL; printk("Character device set offset at %d.\n" , rtn); filp->f_pos = rtn; return rtn; } struct file_operations my_fops = { .open = my_open, .read = my_read, .write = my_write, .release = my_release, .llseek = my_llseek, }; static int __init mydev_init (void ) { printk(KERN_DEBUG "Load module: mydev\n" ); int rtn; dev_t devno = MKDEV(MAJOR_NUM, 0 ); rtn = register_chrdev_region(devno, 1 , "mydev" ); if (rtn < 0 ) { printk(KERN_ALERT "Register character device error." ); return rtn; } memset (globalvar.buf, '\0' , MAXNUM + 1 ); cdev_init(&(globalvar.devm), &my_fops); globalvar.devm.owner = THIS_MODULE; rtn = cdev_add(&(globalvar.devm), devno, 1 ); if (rtn) printk(KERN_ALERT "Error %d adding mydev device.\n" , rtn); else { printk(KERN_INFO "Character device register success.\n" ); globalvar.rd = globalvar.buf; globalvar.wr = globalvar.buf; globalvar.end = globalvar.buf + MAXNUM; } my_class = class_create(THIS_MODULE, "mydev" ); device_create(my_class, NULL , devno, NULL , "mydev" ); return 0 ; } module_init(mydev_init); module_exit(mydev_exit);
/proc 基础知识 这是一个提供内核统计信息的文件系统接口。由内核动态创建,不需要任何存储设备。多数为只读,提供观察数据,一部分可写用于控制内核行为。
/proc包含很多目录,其中以进程ID命名的目录代表就是那个进程。这些目录下众多文件包含了进程的信息和统计信息,由内核数据映射而来。
limits:实际的资源限制
maps:映射的内存区域
sched:CPU调度的各种统计
schedstat:CPU运行时间,延迟和时间分片
smaps:映射内存区域的使用统计
stat:进程状态和统计。包括总的CPU和内存使用情况
statm:以页为单位的内存使用总结
status:stat和statm的信息,用户可读
task:每个任务的统计目录
系统级别的统计,与性能观察相关的系统级别的文件
cpuinfo:物理处理器信息,包含所有虚拟CPU、型号、时钟频率和缓存大小
diskstats:对于所有磁盘设备的磁盘IO统计
interrupts:每个CPU的中断计数器
loadavg:负载平均值
meminfo:系统内存使用明细
net/dev:网络接口统计
net/tcp:活跃的TCP套接字信息
schedstat:系统级别的CPU调度器统计
self:关联当前进程ID路径的链接符号。为了方便使用
slabinfo:内核slab分配器缓存统计
stat:内核和系统资源的统计
zoneinfo:内存区信息
/proc/(pid)/stat 可以通过查看/usr/src/linux/Documentation/filesystems/proc.txt文件来获得更多的信息
[root@localhost ~]# cat /proc/6873/stat
6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0 [root@localhost ~]#
每个参数意思为: 参数 解释 pid=6873 进程(包括轻量级进程,即线程)号 comm=a.out 应用程序或命令的名字 task_state=R 任务的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead ppid=6723 父进程ID pgid=6873 线程组号 sid=6723 c该任务所在的会话组ID tty_nr=34819(pts/3) 该任务的tty终端的设备号,INT(34817/256)=主设备号,(34817-主设备号)=次设备号 tty_pgrp=6873 终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID。 task->flags=8388608 进程标志位,查看该任务的特性 min_flt=77 该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数 cmin_flt=0 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目 maj_flt=0 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数 cmaj_flt=0 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目 utime=1587 该任务在用户态运行的时间,单位为jiffies stime=1 该任务在核心态运行的时间,单位为jiffies cutime=0 累计的该任务的所有的waited-for进程曾经在用户态运行的时间,单位为jiffies cstime=0 累计的该任务的所有的waited-for进程曾经在核心态运行的时间,单位为jiffies priority=25 任务的动态优先级 nice=0 任务的静态优先级 num_threads=3 该任务所在的线程组里线程的个数 it_real_value=0 由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位. start_time=5882654 该任务启动的时间,单位为jiffies vsize=1409024(B) 该任务的虚拟地址空间大小(很多说明上在此误导为vsize的单位为page实际是不正确的) rss=56(page) 该任务当前驻留物理地址空间的大小 Number of pages the process has in real memory,minu 3 for administrative purpose. 这些页可能用于代码,数据和栈。 rlim=4294967295(bytes) 该任务能驻留物理地址空间的最大值 start_code=134512640 该任务在虚拟地址空间的代码段的起始地址 end_code=134513720 该任务在虚拟地址空间的代码段的结束地址 start_stack=3215579040 该任务在虚拟地址空间的栈的结束地址 kstkesp=0 esp(32 位堆栈指针) 的当前值, 与在进程的内核堆栈页得到的一致. kstkeip=2097798 指向将要执行的指令的指针, EIP(32 位指令指针)的当前值. pendingsig=0 待处理信号的位图,记录发送给进程的普通信号 block_sig=0 阻塞信号的位图 sigign=0 忽略的信号的位图 sigcatch=082985 被俘获的信号的位图 wchan=0 如果该进程是睡眠状态,该值给出调度的调用点 nswap 被swapped的页数,当前没用 cnswap 所有子进程被swapped的页数的和,当前没用 exit_signal=17 该进程结束时,向父进程所发送的信号 task_cpu(task)=0 运行在哪个CPU上 task_rt_priority=0 实时进程的相对优先级别 task_policy=0 进程的调度策略,0=非实时进程,1=FIFO实时进程;2=RR实时进程
rss是实际占用内存,以页为单位存放,一般是4K每页,所以要乘以4 vsize则是以B的单位存放,转化到K所以除1024
/proc/cpuinfo /proc/cpuinfo文件分析
在Linux 系统中,提供了proc文件系统 显示系统的软硬件信息。如果想了解系统中CPU的提供商和相关配置信息,则可以通过/proc/cpuinfo文件得到。本文章针对该文件进行简单的总结。
基于不同指令集(ISA)的CPU产生的/proc/cpuinfo文件不一样,基于X86指令集CPU的/proc/cpuinfo文件包含如下内容:
processor : 0 vendor_id :GenuineIntel cpu family :6 model :26 model name :Intel(R) Xeon(R) CPU E5520 @ 2.27GHz stepping :5 cpu MHz :1600.000 cache size : 8192 KB physical id :0 siblings :8 core id : 0 cpu cores :4 apicid :0 fpu :yes fpu_exception :yes cpuid level : 11 wp :yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx rdtscp lm constant_tsc ida nonstop_tsc pni monitor ds_cpl vmx est tm2 cx16 xtpr popcnt lahf_lm bogomips :4522.12 clflush size :64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual power management :
以上输出项的含义如下:
processor :系统中逻辑处理核的编号。对于单核处理器,则课认为是其CPU编号,对于多核处理器则可以是物理核、或者使用超线程技术虚拟的逻辑核 vendor_id :CPU制造商 cpu family :CPU产品系列代号 model :CPU属于其系列中的哪一代的代号 model name:CPU属于的名字及其编号、标称主频 stepping :CPU属于制作更新版本 cpu MHz :CPU的实际使用主频 cache size :CPU二级缓存大小 physical id :单个CPU的标号 siblings :单个CPU逻辑物理核数 core id :当前物理核在其所处CPU中的编号,这个编号不一定连续 cpu cores :该逻辑核所处CPU的物理核数 apicid :用来区分不同逻辑核的编号,系统中每个逻辑核的此编号必然不同,此编号不一定连续 fpu :是否具有浮点运算单元(Floating Point Unit) fpu_exception :是否支持浮点计算异常 cpuid level :执行cpuid指令前,eax寄存器中的值,根据不同的值cpuid 指令会返回不同的内容 wp :表明当前CPU是否在内核态支持对用户空间的写保护(Write Protection) flags :当前CPU支持的功能bogomips :在系统内核启动时粗略测算的CPU速度(Million Instructions Per Second) clflush size :每次刷新缓存的大小单位 cache_alignment :缓存地址对齐单位 address sizes :可访问地址空间位数 power management :对能源管理的支持,有以下几个可选支持功能:
ts: temperature sensor
fid: frequency id control
vid: voltage id control
ttp: thermal trip
tm:
stc:
100mhzsteps:
hwpstate: