linux kernel笔记

 

 

文章目录

    • 关于linux内核中的__attribute__关键字
    • Linux kernel启动参数
    • gdt / ldt
    • PCB

 

关于linux内核中的__attribute__关键字

part I: 原文出处:http://www.jollen.org/blog/2006/10/_gcc___attribute.html

gcc 提供一種「定義函數屬性(attribute)」的語法,也就是前一篇日記我們所提到的 __attribute__標籤。__attribute__用來讓我們定義函數的行為,以便告訴 gcc 在編譯時期對此函數做一些特殊的處理或檢查動作。

以 asmlinkage 的例子來說,asmlinkage 的定義是(/usr/include/linux/linkage.h ):

#if defined __i386__

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))) #elif defined __ia64__

#define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))

#else

#define asmlinkage CPP_ASMLINKAGE

#endif

由此可知,以 sys_nice() 的原型宣告來說,以下的二行程式碼是等價的:

asmlinkage sys_nice(...);

__attribute__((regparm(0))) sys_nice();

__attribute__寫在函數宣告之前或之後都可以(左括號前或右括號後),並接著使用一對「雙括號」來註明此函數的屬性。語法:

__attribute__((keywords)) functon_name(...);

屬性的關鍵字如下(節錄自 gcc 3.4.6 manual):

noreturn,noinline, always_inline, pure, const, nothrow, format, format_arg, no_instrument_function, section, constructor, destructor, used, unused, deprecated, weak, malloc,alias, warn_unused_result, nonnull.
regparm 的用法與用途

regparm 的語法是:regparm(number),regparm 屬性只在 Intel 386 平臺上有作用,用來指定最多可以有多少個(“number” )參數(arguments)能以暫存器來傳遞,regparm(0) 表示參數都不能透過暫存器來傳遞,因此所有參數都會透過堆疊來傳遞。

更多關於__attribute__

又如,在 ARM 平臺上宣告 __attribute((interrupt)) 表示此函數是一個 interrupt handler。

其它的屬性說明可參閱 gcc 的手冊。

__attribute__是重要的 gcc 用法,對系統程式(system software)的開發尤其重要,應仔細閱讀 gcc 手冊。

=================================

part II: 原文出处:http://jimmy-lee.blog.hexun.com/8001013_d.html

__attrubte__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

#define __u8    unsigned char
#define __u16   unsigned short


struct str_struct{
        __u8    a;
        __u8    b;
        __u8    c;
        __u16   d;
} __attribute__ ((packed));


typedef struct {
        __u8    a;
        __u8    b;
        __u8    c;
        __u16   d;
} __attribute__ ((packed)) str;


typedef struct {
        __u8    a;
        __u8    b;
        __u8    c;
        __u16   d;
}str_temp __attribute__ ((packed));

typedef struct {
        __u8    a;
        __u8    b;
        __u8    c;
        __u16   d;
}str_nopacked;

int main(void)
{
        printf("sizeof str = %d/n", sizeof(str));
        printf("sizeof str_struct = %d/n", sizeof(struct str_struct));
        printf("sizeof str_temp = %d/n", sizeof(str_temp));
        printf("sizeof str_nopacked = %d/n", sizeof(str_nopacked));
        return 0;
}

编译运行:

[root@localhost root]# ./packedtest  
sizeof str = 5
sizeof str_struct = 5
sizeof str_temp = 6
sizeof str_nopacked = 6

GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
__attribute__书写特征是:__attribute__前后都有两个下划线,并且后面会紧跟一对括弧,括弧里面是相应的__attribute__参数。
__attribute__语法格式为:

__attribute__ ((attribute-list))

其位置约束:放于声明的尾部“;”之前。

函数属性(Function Attribute):函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容之功效。

GNU CC需要使用 –Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。

packed属性:使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。

=======================================

part III: 原文出处:http://www.wangchao.net.cn/bbsdetail_149537.html

让GCC编译关键字“attribute”给你带来方便
直接引入我们的主角(粗体部分):

int
my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));

my_printf是一个你自己写的函数,比如可能是对vsnprintf等函数进行了封装等等。粗体部分关键字__attribute__可以为函数声明赋属性值,其目的是让编译程序可以优化处理。
关键字__attribute__可以为函数(Function Attributes),变量(Variable Attributes)和结构成员(Type Attributes)赋属性。具体可以查看gcc手册(http://gcc.gnu.org/onlinedocs/)。这里用到的是函数属性,其语 法为:__attribute__ ((attribute-list)),并置于函数声明尾部“;”之前。

format (archetype, string-index, first-to-check)

format属性告诉编译器,按照printf, scanf, strftime或strfmon的参数表格式规则对该函数的参数进行检查。“archetype”指定是哪种风格;“string-index”指定传 入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。
例如:上述例子中,函数my_printf对其参数按“printf”的参数格式进行检查,my_printf的第二个参数的格式化字符串,从my_printf的第三个参数开始进行检查。
还有,在编译时只用指定了-Wformat选项,才会出现警告信息。(我们一般都是-Wall的,没问题)
这样,利用这样一个GCC的编译关键字就能在编译时对你指定的函数进行某种规则检查。赶快行动,在你的程序中加入这些,让编译器来为你做检查吧。(我在程序中加入这些,马上编译,立刻就发现一个long型变量使用%d进行格式化的错误)
引出的一个题外话:如果上述函数my_printf是一个类的成员函数,那么上述声明就应该写成:

int
my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 3, 4)));

为什么现在是3,4?
因为类成员函数有一个隐含的参数“this”指针作为函数的第一个参数。(C++基础,寒!自己看了半天都没看出来,还是向ruiheng老大请教才明白)

Linux kernel启动参数

在Linux中,给kernel传递参数以控制其行为总共有三种方法:

1.build kernel之时的各个configuration选项。

2.当kernel启动之时,可以参数在kernel被GRUB或LILO等启动程序调用之时传递给kernel。

3.在kernel运行时,修改/proc或/sys目录下的文件。

这里我简单讲的就是第二种方式了,kernel在grub中配置的启动参数。

首先,kernel有哪些参数呢? 在linux的源代码中,有这样的一个文档Documentation/kernel-parameters.txt,它介绍了kernel的各个参数及其意义。

其次,kernel启动参数以空格分隔,而且是严格区分大小写的(如:mem和MEM是不一样的)。

再次,对于module特有的kernel参数写法是这样的,[module name].[parameter=XX],例如,igb.max_vfs=7这个kernel启动参数的效果就是相当于这样来动态加载module: modprobe igb max_vfs=7

另外,kernel是怎样处理这些启动参数的呢? 启动参数通常是这样的形式:

name[=value_1][,value_2][,value_10]

“name”是关键字,内核用它来识别应该把”关键字”后面的值传递给谁,也就是如何处理这个值,是传递给处理进程还是作为环境变量或者抛给”init”。值的个数限制为10,你可以通过再次使用该关键字使用超过10个的参数。 首先,kernel检查关键字是不是 ‘root=’, ‘nfsroot=’, ‘nfsaddrs=’, ‘ro’, ‘rw’, ‘debug’或’init’,然后内核在bootsetups数组里搜索于该关键字相关联的已注册的处理函数,如果找到相关的已注册的处理函数,则调用这些函数并把关键字后面的值作为参数传递给这些函数。比如,你在启动时设置参数name=a,b,c,d,内核搜索bootsetups数组,如果发现”name”已注册,则调用”name”的设置函数如name_setup(),并把a,b,c,d传递给name_setup()执行。 所有型如”name=value”参数,如果没有被上面所述的设置函数接收,将被解释为系统启动后的环境变量,比如”TERM=vt100″启动参数就会被作为一个启动后的环境变量。所有没有被内核设置函数接收也没又被设置成环境变量的参数都将留给init进程处理,比如”single”。

下面简单总结一下我在工作中常用到的一些kernel启动参数吧。

根磁盘相关启动参数:

root #指出启动的根文件系统 如:root=/dev/sda1

ro #指定根设备在启动过程中为read-only,默认情况下一般都是这样配的

rw #和ro类似,它是规定为read-write,可写

rootfstype #根文件系统类型,如:rootfstype=ext4

Console和kernel log相关启动参数:

console #console的设备和选项,如:console=tty0 console=ttyS0

debug #enable kernel debugging 启动中的所有debug信息都会打印到console上

quiet #disable all log messages 将kernel log level设置为KERN_WARNING,在启动中只非常严重的信息

loglevel #设置默认的console日志级别,如:loglevel=7 (0~7的数字分别为:KERN_EMERG,…,KERN_DEBUG)

time #设置在每条kernel log信息前加一个时间戳

内存相关的启动参数:

mem #指定kernel使用的内存量,mem=n[KMG]

hugepages #设置大页表页(4MB大小)的最多个数,hugepages=n

CPU相关的启动参数:

mce # Enable the machine check exception feature.

nosmp #Run as a single-processor machine. 不使用SMP(多处理器)

max_cpus #max_cpus=n, SMP系统最多能使用的CPU个数(即使系统中有大于n个的CPU)

Ramdisk相关的启动参数:

initrd #指定初始化ramdisk的位置,initrd=filename

noinitrd #不使用initrd的配置,即使配置了initrd参数

初始化相关启动参数:

init #在启动时去执行的程序,init=filename,默认值为/sbin/init

PCI相关的启动参数:

pci #pci相关的选项,我常使用pci=assign_buses,也使用过pci=nomsi

SELinux相关启动参数:

enforcing #SELinux enforcing状态的开关,enforcing=0表示仅仅是记录危险而不是阻止访问,enforcing=1完全enable,默认值是0

selinux #在启动时关闭或开启SELinux,selinux=0表示关闭,selinux=1表示开启selinux

另外,还是用max_loop来指定最多可使用的回路设备。

在Redhat的系统中,还有个经常看到的kernel启动参数——rhgb,rhgb表示redhat graphics boot,就是会看到图片来代替启动过程中显示的文本信息,这些信息在启动后用dmesg也可以看到
rhgb = redhat graphical boot – This is a GUI mode booting screen with most of the information hidden while the user sees a rotating activity icon spining and brief information as to what the computer is doing.

quiet = hides the majority of boot messages before rhgb starts. These are supposed to make the common user more comfortable. They get alarmed about seeing the kernel and initializing messages, so they hide them for their comfort.

参考资料:

linux kernel documents

《Linux kernel in a nutshell》

gdt / ldt

Global Descriptor Table / Local Descriptor Table

i386的存储管理采用两级虚拟的段页式管理,就是说它先分段,再分页。

它通过gdt(Global Descriptor Table)和ldt(Local Descriptor Table)进行分段,把虚拟地址转换为线性地址。

然后采用两级页表结构进行分页,把线性地址转换为物理地址。

PCB

进程控制块(Process Control Block)

进程是一个程序的一次执行的过程,是一个动态的概念。在i386体系结构中,任务和进程是等价的概念。

在Linux 2.2.x中,进程是通过系统调用fork创建的,新的进程是原来进程的子进程。

需要说明的是,在2.2.x中,不存在真正意义上的线程(Thread)。

Linux中常用的线程pthread实际上是通过进程来模拟的。

也就是说linux中的线程也是通过fork创建的,是“轻”进程。

进程表task实际上是一个PCB指针数组,其定义如下:

struct task_struct *task[NR_TASKS] = {&init_task,};

其中,init_task是系统的第0号进程,也是所有其它进程的父进程。

 

转载于:https://www.cnblogs.com/realplay/p/9867533.html

你可能感兴趣的:(linux kernel笔记)