Nameless Site

But one day, you will stand before its decrepit gate,without really knowing why.

0%

Linux有关笔记

以下内容为操作系统课设期间遇到的一些问题的补充

__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
/* 内核版本:v2.6.32.61  file:arch/score/kernel/signal.c 45行 */
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. 用户空间的程序如何对内核空间进行访问?
上面说到用户态和内核态是两个隔离的空间,虽然从逻辑上被抽像的隔离,但无可避免的是,总是会有那么一些用户空间需要访问内核空间的资源,怎么办呢?

Linux内部结构

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
/**
* copy_to_user: - Copy a block of data into user space.
* @to: Destination address, in user space.
* @from: Source address, in kernel space.
* @n: Number of bytes to copy.
*
* Context: User context only. This function may sleep.
*
* Copy data from kernel space to user space.
*
* Returns number of bytes that could not be copied.
* On success, this will be zero.
*/
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
/* access_ok: - Checks if a user space pointer is valid
* @type: Type of access: %VERIFY_READ or %VERIFY_WRITE. Note that
* %VERIFY_WRITE is a superset of %VERIFY_READ - if it is safe
* to write to a block, it is always safe to read from it.
* @addr: User space pointer to start of block to check
* @size: Size of block to check
*
* Context: User context only. This function may sleep.
*
* Checks if a pointer to a block of memory in user space is valid.
*
* Returns true (nonzero) if the memory block may be valid, false (zero)
* if it is definitely invalid.
*
* Note that, depending on architecture, this function probably just
* checks that the pointer is in the user space range - after calling
* this function, memory access functions may still return -EFAULT.
*/
#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 /* CONFIG_MMU */

其功能是检查用户空间是否合法,它的第一个参数: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 文件系统

设备驱动

基础知识

系统调用是操作系统内核和应用程序之间的接口,而设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:

  1. 对设备初始化和释放.
  2. 把数据从内核传送到硬件和从硬件读取数据.
  3. 读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
  4. 检测和处理设备出现的错误.

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_tint);
int (*read) (struct inode * ,struct file *, charint);
int (*write) (struct inode * ,struct file *, off_tint);
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 intunsigned 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 register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops)
返回值提示操作成功还是失败。负的返回值表示错误;0 或正的返回值表明操作成功。
major参数是被请求的主设备号,name 是设备的名称,该名称将出现在 /proc/devices 中,
fops是指向函数指针数组的指针,这些函数是调用驱动程序的入口点,
在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,
它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,
*/
int result = 0;
int err = 0;
/*
宏定义:#define MKDEV(major,minor) (((major) << MINORBITS) | (minor))
成功执行返回dev_t类型的设备编号,dev_t类型是unsigned int 类型,32位,用于在驱动程序中定义设备编号,
高12位为主设备号,低20位为次设备号,可以通过MAJOR和MINOR来获得主设备号和次设备号。
在module_init宏调用的函数中去注册字符设备驱动
major传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;
/*
file_operations这个结构体变量,让cdev中的ops成员的值为file_operations结构体变量的值。
这个结构体会被cdev_add函数想内核注册cdev结构体,可以用很多函数来操作他。
如:
cdev_alloc:让内核为这个结构体分配内存的
cdev_init:将struct cdev类型的结构体变量和file_operations结构体进行绑定的
cdev_add:向内核里面添加一个驱动,注册驱动
cdev_del:从内核中注销掉一个驱动。注销驱动
*/
//注册字符设备驱动,设备号和file_operations结构体进行绑定
cdev_init(&globalvar.devm, &globalvar_fops);
/*
#define THIS_MODULE (&__this_module)是一个struct module变量,代表当前模块,
与那个著名的current有几分相似,可以通过THIS_MODULE宏来引用模块的struct module结构,
比如使用THIS_MODULE->state可以获得当前模块的状态。
现在你应该明白为啥在那个岁月里,你需要毫不犹豫毫不迟疑的将struct usb_driver结构里的owner设置为THIS_MODULE了吧,
这个owner指针指向的就是你的模块自己。
那现在owner咋就说没就没了那?这个说来可就话长了,咱就长话短说吧。
不知道那个时候你有没有忘记过初始化owner,
反正是很多人都会忘记,
于是在2006年的春节前夕,在咱们都无心工作无心学习等着过春节的时候,Greg坚守一线,去掉了 owner,
于是千千万万个写usb驱动的人再也不用去时刻谨记初始化owner了。
咱们是不用设置owner了,可core里不能不设置,
struct usb_driver结构里不是没有owner了么,
可它里面嵌的那个struct device_driver结构里还有啊,设置了它就可以了。
于是Greg同时又增加了usb_register_driver()这么一层,
usb_register()可以通过将参数指定为THIS_MODULE去调用它,所有的事情都挪到它里面去做。
反正usb_register() 也是内联的,并不会增加调用的开销。
*/
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; // 阻塞唤醒标志置 0
}
/*
定义在/include/linux/device.h
创建class并将class注册到内核中,返回值为class结构指针
在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。
省去了利用mknod命令手动创建设备节点
*/
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);
/*
参数列表包括要释放的主设备号和相应的设备名。
参数中的这个设备名会被内核用来和主设备号参数所对应的已注册设备名进行比较,如果不同,则返回 -EINVAL。
如果主设备号超出了所允许的范围,则内核同样返回 -EINVAL。
*/
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
rmmod 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
#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/chardev0", O_RDWR, S_IRUSR | S_IWUSR);
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) {
//static ssize_t my_read(struct file* filp, char* buf, size_t len, loff_t* off)
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
/*
参考:深入浅出linux设备驱动开发
*/
#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);
/*
结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。
该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
设备"gobalvar"的基本入口点结构变量gobalvar_fops
*/
struct file_operations globalvar_fops =
{
/*
标记化的初始化格式这种格式允许用名字对这类结构的字段进行初始化,这就避免了因数据结构发生变化而带来的麻烦。
这种标记化的初始化处理并不是标准 C 的规范,而是对 GUN 编译器的一种(有用的)特殊扩展
*/
//用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
.read=globalvar_read,
//发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
.write=globalvar_write,
//尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
.open=globalvar_open,
//当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。
.release=globalvar_release,
};
//内核模块的初始化
static int globalvar_init(void)
{

/*
int register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops)
返回值提示操作成功还是失败。负的返回值表示错误;0 或正的返回值表明操作成功。
major参数是被请求的主设备号,name 是设备的名称,该名称将出现在 /proc/devices 中,
fops是指向函数指针数组的指针,这些函数是调用驱动程序的入口点,
在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,
它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,
*/
int result = 0;
int err = 0;
/*
宏定义:#define MKDEV(major,minor) (((major) << MINORBITS) | (minor))
成功执行返回dev_t类型的设备编号,dev_t类型是unsigned int 类型,32位,用于在驱动程序中定义设备编号,
高12位为主设备号,低20位为次设备号,可以通过MAJOR和MINOR来获得主设备号和次设备号。
在module_init宏调用的函数中去注册字符设备驱动
major传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;
/*
file_operations这个结构体变量,让cdev中的ops成员的值为file_operations结构体变量的值。
这个结构体会被cdev_add函数想内核注册cdev结构体,可以用很多函数来操作他。
如:
cdev_alloc:让内核为这个结构体分配内存的
cdev_init:将struct cdev类型的结构体变量和file_operations结构体进行绑定的
cdev_add:向内核里面添加一个驱动,注册驱动
cdev_del:从内核中注销掉一个驱动。注销驱动
*/
//注册字符设备驱动,设备号和file_operations结构体进行绑定
cdev_init(&globalvar.devm, &globalvar_fops);
/*
#define THIS_MODULE (&__this_module)是一个struct module变量,代表当前模块,
与那个著名的current有几分相似,可以通过THIS_MODULE宏来引用模块的struct module结构,
比如使用THIS_MODULE->state可以获得当前模块的状态。
现在你应该明白为啥在那个岁月里,你需要毫不犹豫毫不迟疑的将struct usb_driver结构里的owner设置为THIS_MODULE了吧,
这个owner指针指向的就是你的模块自己。
那现在owner咋就说没就没了那?这个说来可就话长了,咱就长话短说吧。
不知道那个时候你有没有忘记过初始化owner,
反正是很多人都会忘记,
于是在2006年的春节前夕,在咱们都无心工作无心学习等着过春节的时候,Greg坚守一线,去掉了 owner,
于是千千万万个写usb驱动的人再也不用去时刻谨记初始化owner了。
咱们是不用设置owner了,可core里不能不设置,
struct usb_driver结构里不是没有owner了么,
可它里面嵌的那个struct device_driver结构里还有啊,设置了它就可以了。
于是Greg同时又增加了usb_register_driver()这么一层,
usb_register()可以通过将参数指定为THIS_MODULE去调用它,所有的事情都挪到它里面去做。
反正usb_register() 也是内联的,并不会增加调用的开销。
*/
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; // 阻塞唤醒标志置 0
}
/*
定义在/include/linux/device.h
创建class并将class注册到内核中,返回值为class结构指针
在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。
省去了利用mknod命令手动创建设备节点
*/
my_class = class_create(THIS_MODULE, "chardev0");
device_create(my_class, NULL, dev, NULL, "chardev0");
return 0;
}
/*
在大部分驱动程序中,open 应完成如下工作:
● 递增使用计数。--为了老版本的可移植性
● 检查设备特定的错误(诸如设备未就绪或类似的硬件问题)。
● 如果设备是首次打开,则对其进行初始化。
● 识别次设备号,并且如果有必要,更新 f_op 指针。
● 分配并填写被置于 filp->private_data 里的数据结构。
*/
static int globalvar_open(struct inode *inode,struct file *filp)
{
try_module_get(THIS_MODULE);//模块计数加一
printk("This chrdev is in open\n");
return(0);
}
/*
release都应该完成下面的任务:
● 释放由 open 分配的、保存在 filp->private_data 中的所有内容。
● 在最后一次关闭操作时关闭设备。字符设备驱动程序
● 使用计数减 1。
如果使用计数不归0,内核就无法卸载模块。
并不是每个 close 系统调用都会引起对 release 方法的调用。
仅仅是那些真正释放设备数据结构的 close 调用才会调用这个方法,
因此名字是 release 而不是 close。内核维护一个 file 结构被使用多少次的计数器。
无论是 fork 还是 dup 都不创建新的数据结构(仅由 open 创建),它们只是增加已有结构中的计数。
*/
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);
/*
参数列表包括要释放的主设备号和相应的设备名。
参数中的这个设备名会被内核用来和主设备号参数所对应的已注册设备名进行比较,如果不同,则返回 -EINVAL。
如果主设备号超出了所允许的范围,则内核同样返回 -EINVAL。
*/
unregister_chrdev_region(MKDEV(major, 0), 1);//注销设备
}
/*
ssize_t read(struct file *filp, char *buff,size_t count, loff_t *offp);
参数 filp 是文件指针,参数 count 是请求传输的数据长度。
参数 buff 是指向用户空间的缓冲区,这个缓冲区或者保存将写入的数据,或者是一个存放新读入数据的空缓冲区。
最后的 offp 是一个指向“long offset type(长偏移量类型)”对象的指针,这个对象指明用户在文件中进行存取操作的位置。
返回值是“signed size type(有符号的尺寸类型)”

主要问题是,需要在内核地址空间和用户地址空间之间传输数据。
不能用通常的办法利用指针或 memcpy来完成这样的操作。由于许多原因,不能在内核空间中直接使用用户空间地址。
内核空间地址与用户空间地址之间很大的一个差异就是,用户空间的内存是可被换出的。
当内核访问用户空间指针时,相对应的页面可能已不在内存中了,这样的话就会产生一个页面失效
*/
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;
}
/*
down_interruptible 可以由一个信号中断,但 down 不允许有信号传送到进程。
大多数情况下都希望信号起作用;否则,就有可能建立一个无法杀掉的进程,并产生其他不可预期的结果。
但是,允许信号中断将使得信号量的处理复杂化,因为我们总要去检查函数(这里是 down_interruptible)是否已被中断。
一般来说,当该函数返回 0 时表示成功,返回非 0 时则表示出错。
如果这个处理过程被中断,它就不会获得信号量 , 因此,也就不能调用 up 函数了。
因此,对信号量的典型调用通常是下面的这种形式:
if (down_interruptible (&sem))
return -ERESTARTSYS;
返回值 -ERESTARTSYS通知系统操作被信号中断。
调用这个设备方法的内核函数或者重新尝试,或者返回 -EINTR 给应用程序,这取决于应用程序是如何设置信号处理函数的。
当然,如果是以这种方式中断操作的话,那么代码应在返回前完成清理工作。

使用down_interruptible来获取信号量的代码不应调用其他也试图获得该信号量的函数,否则就会陷入死锁。
如果驱动程序中的某段程序对其持有的信号量释放失败的话(可能就是一次出错返回的结果),
那么其他任何获取该信号量的尝试都将阻塞在那里。
*/
if(down_interruptible(&globalvar.sem)) //P 操作
{
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);
/*
read 和 write 代码要做的工作,就是在用户地址空间和内核地址空间之间进行整段数据的拷贝。
这种能力是由下面的内核函数提供的,它们用于拷贝任意的一段字节序列,这也是每个 read 和 write 方法实现的核心部分:
unsigned long copy_to_user(void *to, const void *from,unsigned long count);
unsigned long copy_from_user(void *to, const void *from,unsigned long count);
虽然这些函数的行为很像通常的 memcpy 函数,但当在内核空间内运行的代码访问用户空间时,则要多加小心。
被寻址的用户空间的页面可能当前并不在内存,于是处理页面失效的程序会使访问进程转入睡眠,直到该页面被传送至期望的位置。
例如,当页面必须从交换空间取回时,这样的情况就会发生。对于驱动程序编写人员来说,
结果就是访问用户空间的任何函数都必须是可重入的,并且必须能和其他驱动程序函数并发执行。
这就是我们使用信号量来控制并发访问的原因.
这两个函数的作用并不限于在内核空间和用户空间之间拷贝数据,它们还检查用户空间的指针是否有效。
如果指针无效,就不会进行拷贝;另一方面,如果在拷贝过程中遇到无效地址,则仅仅会复制部分数据。
在这两种情况下,返回值是还未拷贝完的内存的数量值。
如果发现这样的错误返回,就会在返回值不为 0 时,返回 -EFAULT 给用户。
负值意味着发生了错误,该值指明发生了什么错误,错误码在<linux/errno.h>中定义。
比如这样的一些错误:-EINTR(系统调用被中断)或 -EFAULT (无效地址)。
*/
if(copy_to_user(buf,globalvar.rd,len))
{
printk(KERN_ALERT"copy failed\n");
/*
up递增信号量的值,并唤醒所有正在等待信号量转为可用状态的进程。
必须小心使用信号量。被信号量保护的数据必须是定义清晰的,并且存取这些数据的所有代码都必须首先获得信号量。
*/
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); //V 操作
return len;
}
static ssize_t globalvar_write(struct file *filp,const char *buf,size_t len,loff_t *off)
{
if(down_interruptible(&globalvar.sem)) //P 操作
{
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); //V 操作
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);
//V 操作
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 //主设备号,没有被使用 查看/proc/devices可知

//指定license版本
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");
//try_module_get(THIS_MODULE);//模块计数加一
filp->private_data = &globalvar;
return 0;
}

//文件释放函数
static int my_release(struct inode* inode, struct file* filp)
{
printk("Character device is released.\n");
//module_put(THIS_MODULE); //模块计数减一
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("the read buffer is %s\n", globalvar.buf);
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;
//printk(KERN_DEBUG "len: %d buf:%send.\n",strlen(dev->buf), dev->buf);
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 "hello %s!!!\n", name);
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);
//printk(KERN_DEBUG "buf:%s\n", globalvar.buf);
cdev_init(&(globalvar.devm), &my_fops); //注册字符设备驱动,设备号和file_operations结构体进行绑定
globalvar.devm.owner = THIS_MODULE; //通过THIS_MODULE宏来引用模块的struct 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命名的目录代表就是那个进程。这些目录下众多文件包含了进程的信息和统计信息,由内核数据映射而来。

img

  • limits:实际的资源限制
  • maps:映射的内存区域
  • sched:CPU调度的各种统计
  • schedstat:CPU运行时间,延迟和时间分片
  • smaps:映射内存区域的使用统计
  • stat:进程状态和统计。包括总的CPU和内存使用情况
  • statm:以页为单位的内存使用总结
  • status:stat和statm的信息,用户可读
  • task:每个任务的统计目录

系统级别的统计,与性能观察相关的系统级别的文件

img

  • 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: