----------------- |boot sector| | |___________| | | data sector | |_______________|
__________________0x000 |jmp to this code| |________________|0x003 |disk para table | |________________|0x02c/0x03e | part of code | |_______________ |0x1f
一个硬盘在DOS的文件系统下可分为四个基本分区(primairy partition),如果数目达不到所需的数目,可以设置扩展分区(Extended partition ),整个硬盘一张分区表,它存放在硬盘的第一个分区里,而每个扩展分区也都有一张分区表,它存放在扩展分区的第一个分区表中,整个硬盘的主引导扇区(MBR)如下图所示:
------------------ | code | |________________| |partition table | |________________| | setup signal | |________________|
在启动的过程中,BIOS会把Boot Loader读入内存,并把控制权交给它,MBR(硬盘启动)或者启动扇区(软盘)的代码就是Boot Loader的一部分,这部分的实现其实是很复杂的,Boot Loader实际上会寻找各自的启动的扇区。
我的学习过程中有一个很明显的特点,先知道是什么,最后才知道为什么,不知道这样好不好?
http://faq.perl.org/
Programming Perl(Camel book)
Perl 5 Pocket Reference
其实,perl语言是这么一个流程:在内存中保存他的输出;更新内存;并且打印它
1.scalar : a number,a string,You can act on a scalar value with operators(like additioon or concatenation),You can read scalars from file and devices and write to them as well.
2.在整数常量中,如果数字特别巨大,可以使用 21_323_123_987_365表示
3.Others Nondecimal 0377 #377 octal 0xff #FF hex, 0b11111111 #also 255 decimal
4.如果在程序中使用utf-8,这样 use utf8;
5.在字符串的使用中,需要注意’ ‘的内容,里面除了“‘”和“",其余的都代表 他们自己本身,如果想得到一个“\”,那么就使用“\”,为了表示”’“,同样使用“\“转义; 与此对应的是,双引号的使用,你可以想象成c语言中的双引号
6.使用 “.”符号进行链接剪切等操作 For example:
"hello" . "world" #same as "helloworld"
"hello" ' ' . "world" # same as 'hello world'
'hello world' . "\n" #same as "hello world\n"
7.重复字符串”x”,对,你没看错,就是小写字母x,
"yubo" x 3
"yubo" x (3+1)
5 x 4.8 #same as “5555” 最后一个需要说明一下,在操作符 “x”的左边,我们称之为左值,你可以看成源操作数 ,符号右边是右值,你可以看成是目的操作数,4.8被自动转换为4.
8.在数字和字符串之间自由转换,这只是取决于你自己用在标量的的操作符, 比如,“+”系统默认为左值和右值为数字,“.”左右值默认为字符串 , 比如,”12” * “3”就是36,即便在“12yubo34” * 3的结果依旧是36
1.变量
用$表示,$name,…还可以用于数组
2.变量赋值
$bin = 12;
$asd = $bin *2;
另外需要注意的是在perl变量中,“ ”与shell中的“ ”一样, 在比较多的变量中,使用${varible}调用其值
3.打印特殊符号,先找到ascii码,然后使用 chr()函数,$alef = chr(0x05D0); 即可。
4.有关perl的运算符的优先级和结合性(结合性依次降低)
结合性 运算符
left ->
++ --
right **
right \ ! ~ + -(unary operators)
left =~ !~(?)
left */%x
left =-.(binary operations)
left <<>>
< <= > >=
== != <=>
left &
left | ^
left &&
.. ...(?)
right ?:(conditional operator)
right = += -= .=(and similar assignment operators )
left , =>
right not
left and
left or xor
没有必要记住上面的这些东西,如果你找不到你想要的运算顺序,尽管加上括 号即可(parenthses),the same time:
Numeric String
== eq
!= ne
< lt
> gt
<= le
>= ge
##perl中的bool类型 1.如果返回的值是0为false,其他为true;
2.如果返回的是一个字符串,‘ ’意味着false;其他意味着成功
3.如果还有其他情况,请转化为这两种情况。
1.1,Getting User Input
For example:
这样就是用户从屏幕标准输入了。
1.2
The chomp Operator
它的作用简单说就是作用于一个变量(输入的值),把结束符(newline)去掉,
这一步在以后的编程中很有用的,上面的例子,我们一般情况下这样做:
chomp($text =
如果你以这样的方式写出这样的程序,那么就没有换行输出了,因为它自动将换行符去掉了。
1.3 in short,chomp也是一个简单的函数,也有返回值,他的返回值就是清除字符的个数,但是这种用法和我们上面不太一样,是这样
$yubo = <STDIN>;
$NAME = chomp $yubo;
输出的结果是1.
1.4 The while control construction 同c语言的一样,很简单的。
1.5使用defined function
1.
$fred[0] = "yubo"; print $fred[0]; #perl的数组没有限制,随便用
2.列表
(1,2,3) ("yubo",4.5) (1..100)
2.2 The qw Shortcut
qw( fred yubo hechun wilma dino )# 等同于有双引号的列表
qw! yahoo\! google ask msn ! #include yahho! as an element
2.3 List Assignment
3.在给数组赋值的时候,可以在数组前面加上@,这样就全部赋值了。例如,
@rocks = qw/ bedrock slate lava /;
@tiny = ();# the empty list
@giant = 1..1e5; # a list with 100000 elements
The pop operator takes the last element off of an array and returns it @array = 5..9;
$yubo = pop(@array); #$yubo gets 9,@array now has (5,6,7,8)
$barney = pop @array;#barney gets 8,@array now has (5, 6, 7)
push(@array,0) # @array now has (5,6,0)
push @array,8 # @array now has (5,6,0,8)
push @array,1..10; # @array now has those 10 new elements
@yubo = qw# hechun wuhan wangyang #;
$m = shift (@yubo);
$n = shift @yubo;
unshift play an oppose play vs shift,and it used to be filled with elements.
unshift(@yubo, 5);
unshift @yubo, 5;
6.分离数组
1.splice就是分离数组的符号,有4个参数,其中
@yubo = qw(apple, dino, hechun, love);
@removed = splice @yubo, 2; #remove everything after hechun
#@removed is qw(hechun,love)
#@yubo is qw(apple, dino)
2.第三个参数就是要删除元素的个数
@removed = splice @yubo, 1, 2; #@removed is qw(dino, hechun)
#@yubo is qw(apple, love)
3.这第四个参数就是你放置要取代的元素,这个数不必和你取出去的相同。
@removed = splice @yubo, 1, 2, qw(wilma);
当然,你可以不用去掉任何元素,只要使第三个参数变0即可。
7.@和$ 当变量中含有@(list-var)时要注意使用”"转义,还有注意数组的使用。
8.foreach 一个loop的控制变量,一个普遍的用法是用$var读取list的内容,如下:
@yubo = qw(yu hechun hehe); foreach $num (@yubo){ print "$num\m"; }
注意这时的$num不是简单的@yubo的元素的复制,而是实实在在的东西, 你只要改变$num的内容,也能影响@yubo的内容。我们可以使用$_作为foreach的 默认控制变量,打印语句可以直接使用print;
以反序输出内容,如果是单词,按首字母排序。
如果是单词,按首字母排序;如果是数字,也是按照首位的大小排序。
以上几个内建函数的对象是list(我觉得),使用的时候注意, scalar和list的混用的情况,注意内容与类型的对应要正确。 作者说了,这部分的理解决定了你这一辈子是否使用perl的关键因素, 没有之一。
sub subroutine-name{ }
stores the parameter list in the special array named @_, you can access it to get number of argument and the value of those arguments. For example,$_[0],$_[1]…
在函数内部使用 @_
就是参数个数
sub max{ if (@_ != 2){ print "wrong\n"; } else { print "yes\n"; } } $n = &max(19,23);
下面的例子,把函数的规则总结的不错
$maxinum = &max(3, 5, 10, 4, 6); sub max { my($max_so_far) = shift @_; # private variable foreach (@_){ #look at the remaining arguments if ($_ > $max_so_far){ $max_so_far = $_; } } $max_so_far; } print "The maxinum is $maxinum\n";
my(var)也叫Lexical(my) Variables,这个变量位于最小的块中,可以是代码块 或者文件。利用关键词my去声明局部变量。
foreach (1..10){
my($square) = $_ * $_;
print "$_ square is $square\n";
}
在代码中你只要使用了这条语句,在使用变量的时候就必须先声明后使用, 不然编译器就报告失败.当然对于内建变量是不会报错的, please use it possiblely. 此时感觉perl真是非同一般,你只要使用了strict,那么程序中的变量就要使用 my去声明。
use strict;
my @name = qw(yubo hechun xixi qianqian);
my $result = &which_element_is("xixi",@name);
sub which_element_is {
my($what, @array) = @_;
foreach (0..$#array){
if ($what eq $array[$_]){
return $_;
}
}
-1;
}
What is state?Please see example follwing:
output:五个1。原因在这里很简单,就是my把变量局限于子函数内, 请不疑惑,尽管我们的$n 在sub marine内,但是,在foreach的调用中, 整体已经过去了,所以只会输出五个1(??)
典型例子
while (defined($line = <STDIN>)){
print "I saw $line";
}
同样的表达可以是
while (<STDIN>){
print "I saw $_";
}
foreach (<STDIN>){
print "I saw $_";
}
# Ctrl + D
Reading variables from input,
while (<>) {
chmop;
print "It was $_ that i saw";
}
名字最好使用大写字母,这样有好几个优势。
open CONFIG, 'dino'; # dino is filename
open config, '<dino';
open BEDROCK,'>fred'
you can use a “three-argument” open:
open CONFIG, '<', 'dino';
open BEDROCK, '>', '$file_name';
open CONFIG,'<:encoding(UTF-8)', 'dino';
open LOG, '>>:encoding(UTF-8)', &logfile_name();
if (! open LOG, '>>', 'logfile') {
die "Cannot create logfile: $!"
}
这个判断语句就是判断是否能创建logfile文件,不能的话返回一个数值, 这个值就是不能的含义,在这里,可以把die和pirnt联系起来,共同报错。
Recently i thought sth deeply,English is my shortcut to learn linux kernel and others, and lack of indpendent of thinking.
use strict;
use warnings;
open (my $yubo, '>', 'log');
print $yubo "My first report generated by perl\n";
close $yubo;
print "done\n";
这就是一个典型的文件句柄。open函数可以传入3个参数。
第一个, $yubo,是在 open()调用中定义的标量,第二个参数是读写文件的方式,第三个是文件路径。
文件的内容仍在磁盘上,并不在$yubo变量中,可能你往句柄中写入内容, 东西就会在文件中保存。
这个$yubo就可以类推c语言中的文件描述符(fd),有意思吧!
open(my $yubo, ‘>’, ‘no_exist/file’) or die;
下面这种用法结合前面几种的方法
my $filename = 'No_exist_file';
open(my $yubo, '>', $filename) or die "could not open $filename $!";
# return error information
当时偷懒了,只是简单的记录下来,并不知道什么意思。现在再重新整理整理。 顾名思义,@ARGV就是一个参数,这个参数接受外部变量,这里,这个变量就是某个路径上的文件,将该文件上的内容输出。
@ARGV = ("file1","file2");
while ($line = <>) {
print "$line\n";
}
上面的东西,一看就是菜鸟写的,应该简单一点是这样
@ARGV = ("file1");
while (<>) {
print "$_";
}
For example:
$family_name{'yubo'} = 'flintstone';
$family_name{'hechun'} = 'love';
foreach my $person (qw <yubo hechun>) {
print "I've heard of $person $family_name{$person}.\n";
}
my %new_hash = %old_hash;
my %last_name = (
'Yu' => 'bo',
'He' => 'chun',
);
foreach my $first (qw <Yu He>) {
print "$first in @last_name{$first}.\n";
}
上面的hash赋值完后还可以使用 $hash{“d”} = 1; 的方式解决。 还有一种方式可以赋值:
my %last_name;
$last_name{"fred"} = "hehe";
$last_name{"yubo"} = "bo";
$last_name{"he"} = "chun";
注意,赋值的时候使用%,使用的时候却是$
下面这句话改变了 key 和 value 的位置。
my %inverse_hash = reverse %any_hash;
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
my @k = keys %hash;
my @v = values %hash;
foreach (@k) {
print "$_\n";
}
这样就会打印hash的key,但是有一点请注意,perl不保证输出的顺序, 也就是你最好不要期望得到你想要的顺序。又但是,keys 和 values函数 可以保证此时的值相对应,例如,上面的例子@k会打印出c b a,那么打印 的@v一定是3 1 2
There is another function:
my $count = keys %hash;
$count的值就是hash函数中的对数。
while ( ($key, $value) = each %hash) {
print "$key => $value\n";
}
After sorted hash
foreach $key (sort keys %hash) {
$value = $hash{$key};
print "$key => $value\n";
}
The exists Function,to see whether a key exists in the hash, use the exists function,if true
以上面的代码为例:
if (exists $hash{"a"}) {
print "Hey, there exist\n";
}
if (!exists $hash{"d"}) {
print "NO exist!\n";
}
不知道我的思路是否有问题,$hash,@hash,%hash三者之间有着某种联系, 虽说hash是用来定义的,但它仅仅是定义吗?在其他的外部调用中,我们 反而使用的是$hash{“”},在当时学习@hash和$hash也有同样的问题。
print "PATH is $ENV{path}\n";
write a perl file, output the times of that have read from terminal. For example, input “yubo,hechun,yubo,hechun” output the numbers”yubo 2…hechun 2”
my(@words, %count, $word); # declare
chomp(@words = <STDIN>); # input
foreach $word (@words) {
$count{$word} += 1;
}
foreach $word (keys %count) {
print "$word was seen $count{$word} time . \n";
}
自己从这个程序中稍微理解了hash的原理,其中最后一个比较有意思, 在count hash中$word作为keys,so the value is $count{$word}.
下面的例子与上面的很类似
$longest = 0;
foreach my $key (keys %ENV) {
my $key_long = length($key);
$longest = $key_long if $key_long > $longest;
}
foreach my $key (sort keys %ENV) {
printf "%-s{longest} %s \n.$key, $ENV{$key}";
}
#Debugging Techniques 无论什么样的原因你都应该自己编译内核,在__kernel hacking__ menu 下,你最好开启以下选项. ###CONFIG_DEBUG_KERNEL the option just makes other debugging options available. ###CONFIG_DEBUG_SLAB 这对于内核内存分配函数非常关键, 它可以推断溢出的内存的数量以及未初始化的错误,每一个被分配的内存的字节在处理之前是0xa5,当被释放后是0x6b… ###CONFIG_DEBUG_PAGEALLOC Full pages are removed from the kernel address space,the option can slow it,also point out certain kinds of memory corruption errors ###CONFIG_DEBUG_SPINLOCK It catches uninitialized spinlock and other errors(such as unlocking a lock twice)
###CONFIG_DEBUG_SPINLOCK_SLEEP 开启这个选项,如果你呼叫一个潜在(可能不是)的sleep函数,它就会发出警告. ###CONFIG_INIT_DEBUG Items marked with init(__initdata)在系统初始化完成后或模块加载完毕后被丢弃. ###CONFIG_DEBUG_INFO The information debugging with __gdb,You also want to enable CONFIG_FRAME_POINTER. ###CONFIG_MAGIC_SYSRQ
###CONFIG_DEBUG_STACKOVERFLOW AND CONFIG_DEBUG_STACK_USAGE These options can help trace down kernel stack overflows. ###CONFIG_KALLSYMS This option (under “General setup/Stardsrd features”) causes kernel symbol information to be built into the kernel. ###CONFIG_IKCONFIG AND CONFIG_IKCONFIG_PROC These options (under “General setup”) cause the full kernel configuration state and to be made available via /proc. ###CONFIG_ACPI_DEBUG
###CONFIG_DEBUG_DRIVER It is useful to trace down the low-level support code. ###CONFIG_SCSI_CONSTANTS The item (under “Device drivers/SCSI device support”) ###CONFIG_INPUT_EVBUG The item (“Device drivers/Input device suuport”) including security implications and however: it logs everything you type. ###CONFIG_PROFILING
#Debugging by Printing The variable console_loglevel is initialized to DEFAULT_CONSOLE_LOGLEVEL AND can be modified through the sys_syslog system call,P95 这一段没看明白. ###Redirecting Console Messages 同样没有看懂 ###How Messages Get Logged The printk function writes messages into a circular buffer that is __LOG_BUF_LEN bytes long (from 4 KB to 1 MB) while configuring the kernel.
###系统日志那块很麻烦 ###Rate Limit 如果你的设备有错误,但是你只是想打印一次的错误,那可以考虑
int printk_ratelimit(void);
如果这个函数返回一个非0的数,那么继续打印消息,典型的应用是
if (printk_ratelimit())
printk(KERN_NOTICE "The printer is still on fire\n");
这个函数的行为可以通过 /proc/sys/kernel/printk_ratelimit改变. ###print device numbers Defined in <linux/kdev_t.h>
int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);
这一块学的真垃圾.
###Using the /proc/ Filesystem
这个目录下的文件是内核把硬件信息告诉外界./proc是很重要在linux system中,许多命令例如ps,top,uptime严重依赖这个目录.你的驱动也可以使用这个东西,并且是动态的. 我们最好使用 sysfs去调试信息.
<linux/proc_fs.h>是所有工作在/proc的模块的头文件,当有进程访问/proc下的文件,内核首先分配一页内存(4096 kb),此内存用来将数据发往用户空间,
int (*read_proc)(char *page, char **start, off_t offset, int count,
int *eof, void *data)
其中的offset和count同read方法的一致,其他的就可以按照字面上的理解.这个函数的参数需要注意.
下面是一个简单的例子:
##kdb内核调试器
1.在编译内核时,首先将支持kdb的选项打开,在 Linux hacking » KGDB
2.在控制台下,按下 Pause Break 键启动调试,当内核发生oops,或者到达一个断点时也会开启调试。
3.具体用到的时候再说 ##并发和竞争
1.当一个linux process reaches a point where it cannot make any further process it goes to sleep (or “block”)
2.使用锁机制容易造成“sleep”,所以使用信号量来避免,在linux中,有一对函数叫做P,V。在进入临界区前,首先呼叫p,并且将value-1,V 释放这个信号量。当这两个函数出现意外时,需要等待…
3.实现
为了使用semaphore,需要包括<asm/semaphore>
struct semaphore;
void sem_init(struct semaphore *sem, int val);
// val is the inital value to assign to a semaphore.
还可以使用MACROS
DECLARE_MUTEX(NAME); // ==>> 1
DECLARE_MUTEX_LOCKED(NAME); // ==>> 0
如果在运行时进行初始化,可以使用以下两种方法
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
使用:
void down (struct semaphore *sem);
void down_interruptible(struct semaphore *sem);
//产生不可杀的进程(D state in PS command)
int down_trylock(struct semaphore *sem);
//立即返回nonzero
void up(struct semaphore *sem);
使用信号量通过down获得,也就是说在你的目标代码中使用上述的的down xx(),可以获得这个信号量;当你离开这个临界区时,通过up()释放。
首先我们在设备文件中设置
struct semaphore sem;
init_MUTEX(&scull_devices[i].sem);
//一定要在设备分配之前使用
up(&dev->sem);
//must clean up
###Read/write semaphore 如果只是仅仅访问收保护的临界区,我们可以使用rwsem(“reader/writer semaphore”), it comes from <linux/rwsem.h>.
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore * sem);
void up_read(struct rw_semaphore *sem);
down_read只是使用可读的权限使用被保护的数据,还可以是并发的。它可以把呼叫程序处于一种不可中断的睡眠。down_read_trylock如果成功的话返回非0,不成功返回0,这一点与其它的内核函数一样。一个 rwsem使用down_read到的,必须使用up_read释放。
for writer:
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
综合rwsem的表现,我们拥有rwsem的时间不能过长,否则会出问题. ###completion 即便使用信号量我们也不能保障临界区的安全使用,in the 2.4.7 kernel,we can use completion that allow one thread to tell another that job is done. <linux/completion.h>
DECLARE_COMPLETION(my_completion);
如果动态的使用,则
struct completion my_completion;
/*...*/
init_completion(&my_completion); wait.
void wait_for_completion(strcut completion *c);
这个函数是不可中断的,如果使用后不及时释放,会产生不可杀的进程.
1.insmod 是系统调用 kernel/module.c,The function sys_init_module allocates kernel memory(how)=>( The memory is allocated with vmalloc function),系统调用函数把模块复制到内存区,通过内核符号表解决内核引用的问题,并且呼叫模块初始化函数使一切准备就绪。
2.在内核源代码中,使用前缀sys_ 就是系统调用。
3.modprobe 命令比insmod命令高级一点,它会自动解决模块引用不成功的问题。
4.如果模块正在被使用,清除模块的任务将会失败,或者这个模块已经被定义为不可清除
5.lsmod 将会列出目前正在内核被加载的模块还有例如其他模块正在使用一个特殊的模块,他是通过读 /proc/modules虚拟文件实现的。当前加载的模块也可以在虚拟系统文件 /sys/module读取。
6.在装载模块时,在链接的过程中会寻找一个目标文件 vermagic.o(不过,在debian中我没有找到这个文件)和当前的系统配置相对照,还有 vmlinux是做什么用的?
8.当你写的驱动需要在不同版本的linux上运行时,你这时就需要充分利用macros和#ifdef结构体了,类似大量的定义在这个文件linux/veersion.h中,这个头文件自动包括linux/module.h
UTS_RELEASE 这个宏定义了一个描述这个代码树的版本的字符串,例如”2.6.10”
LINUX_VERSION_CODE 定义了一个代表内核版本的二进制文件,一个字节代表版本的一部分,如,2.6.10是132618(0x020610)
KERNEL_VERSION(major,minor,release) KERNEL_VERSION(2.6.10)==>132618,
not clutter driver (填充)
9.解决 platform dependency
The best way is to release your driver under a GPL-compatible license and contribute it to the mainline kernel,distributing your driver in source form and a set of scripts to compile it on the user’s platform
msdos relies on symbols exported by the fat module, and each input USB device stacks on the usbcore and input modules. 有意识的去使用modprobe,这一句命令就可以代替很多insmod命令, 如果你写的模块希望被其他模块所使用,最好加入以下两句话 EXPORT_SYMBOL(name); EXPORT_SYMBOL_GPL(name); 具体的请参看<linux/module.h>吧,在模块中有一个“ELF”部分,在那里导入了有关符号表的东西。 MODULE_AUTHOR(); MODULE_VERSION(); MODULE_ALIAS(); MODULE_DEVICE_TABLE();
module__init 是强制性, 这个宏增加了一个的段对于模块的目标代码在初始化函数,没有这个定义,初始化函数是从来不会调用的。
模块可以注册不同的设备,对于不同的设备,这有一个特别的函数完成注册,传递给内核注册函数的参数一般是指向新设备的数据结构和这个注册设备的名字,这个数据结构经常包含指向模块函数的指针,这也是在模块体内部的函数得到调用的方法。
以下这些东西:串口,miscellaneous(杂项)设备,系统文件,/proc 文件,可执行的域名,line discipline(线性规划),这些文件不是直接与硬件打交道,但保持这软件抽象域,这些东西是可以被注册的。
这还有其他设备的插件也可以被注册,绝大多数用register_前缀。
11.清除函数 a function marked __exit can be called only at module unload ro system shutdown time.any other use is an error. and it (__exit)is vital for cleanup function.
In the linux kernel, err codes are negative numbers defined in the <linux/errno.h>
p52–p53的代码没有看明白。
1.你必须准备好只要你的代码完成它的第一步注册就有可能被调用,把你模块需要的设备准备好以后再去注册你的初始化函数。
2.避免把初始化函数失败。
###.模块参数
IDE can allow user control of DMA operations.
hardware need to know I/O ports and I/O memory address.
参数值可以被赋值在使用命令 insmod 或者 modprobe的时候,使用module_param macro,which is defined in
static char *whom = "world"; static int howmany = 1; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO);
使用命令 insmod hellop howmany=10 who=”Mom”;(有问题)
数组参数使用module_param_array(name,type,num,perm); the perm was defined in <linux/stat.h>,
第一个字符设备驱动
在dev_t中,12位的主设备号,20位的次设备号,你的代码应该充分利用在 <linux/kdev_t>的宏,为了去获得主设备号和次设备号dev_t,使用如下命令:
MAJOR(dev_t dev); MINOR(dev_t dev);
then,use MKDEV(int major, int minor);
为一个字符串设备获得设备号的函数是
register_chrdev_region(dev_t first,unsigned int count, char *name)
first 是你申请的开始设备号的范围,这经常从0开始,count是你申请的设备的总数 name必须和你的number相对应,而且这个名字就在/proc/devices中显示,另外,这个/proc/目录是个虚拟目录,它时刻在读取计算机的各个硬件cpu、io、模块等信息,要注意利用啊。 这个函数如果正确执行,会返回一个0,否则就返回一个负数,现在很多人努力使用这个函数,
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
记住,申请完设备后一定要记得释放,驱动程序应该始终使用alloc_chrdev_region.
void unsigned_chrdev_region(dev_t first,unsigned int count)
Major devices numbers 静态分配的多些,这些设备清单在内核的Documentayion/devices.txt,如果你的设备不工作,那么试试以下两种方法: 1.使用未使用的静态主设备号;2.使用动态分配的方法,in other word,我们应该更多的使用alloc_chrdev_region而不是register_chrdev_region.
是关于驱动程序的,例如 /dev/null,/dev/zero 的主设备号是1,虚拟控制台和串口终端是4,VCSL和VCSL设备是7.
由内核使用,用于正确确定设备文件所指的设备,可以通过设备号获得一个指向内核设备的直接指针。
file_operations structure :
这个scull设备驱动被初始化如下:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioptl,
.open = scull_open,
.release = scull_release,
};
hints:
对于结构体
struct a {
int a;
int b;
}
有以下几种初始化方式:
struct a a1 = {
.a = 1,
.b = 2,
};
或者
struct a a2 = {
a:1,
b:2,
};
or
struct a a3 = {
1,2
};
内核喜欢用第一种方式,使用第一种和第二种方式,成员初始化的顺序可以改变。使代码更具有可移植性。
struct file 是第二重要的数据结构,与用户空间的FILE完全没有关系
The inode 是被内核内部用来代表文件,单个文件可以有多个file结构,但是它们全都指向一个 inode,现在先有两个重要域关于驱动的
最近,有开发者希望能从inode中直接获取主设备号和次设备,
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
在linux中,内核使用 cdev 结构去表示字符设备,那么必须包含<linux/cdev.h>,可以使用以下方法在运行时获得 cdev 结构,
struct cdev *my_cdev = cdev_alloc();
my_dev->ops = &my_fops;
在初始化中,
void cdev_init(struct cdev *cdev, struct file_operations *ops);
在kenel呼叫cdev是
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
num是这个设备对应的第一设备号,count是设备号对应的设备的数量,多数情况下是1,也有例外的情况.这个函数失败的话会返回一个负数.
To remove a char device from the system ,call:
void cdev_del(struct cdev, *dev);
scull 代表被一个scull_dev设备
struct scull_dev {
strcut scull_qset *data; /*first quantum*/
int quantum; /* The current quantum size*/
int qset; /*the current array size*/
unsigned long size; /*amount of data stored here*/
unsigned int access_key;
struct semaphore sem; /* mutual exclusion semaphore*/
/*互斥信号量*/
struct cdev cdev;/* Char device structure*/
}
struct cdev是设备加载到内核的接口,这个结构必须初始化和加进系统使用如下的方法,
static void scull_setup_cdev(struct scull_cdev *dev, int index)
{
int err, devno = MKDEV(struct scull_major, scull_mirror +
index);
cdev->init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MOUDLE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno,1);
if(err);
printk(KERN_NOTICE "error %d adding scull%d",err,index);
}
__open__method is provided for a driver to do any initialization in preparation for later operations,must consider following tasks:检查设备特别的错误,如没有就绪 Initialize the device if it is being opened for the first time Update f_op pointer,Allocate and in filp->private_data
第一件事是确定哪个设备正在被打开,open的函数原型为
int (*open)(struct inode *inode, struct file *file)
我们希望scull_dev结构包含cdev结构,(它这是在说什么), container_of defined is in <linux/kernel.h>
container_of(pointer, container_type, container_field);
这个宏把一个指针指向container_field的域,返回一个指针指向containing结构,在scull_open
struct scull_dev *dev; /*device information*/
dev = contain_of(inode->i_cdev,struct scull_dev, cdev);
filp->private_data = dev;
只要一发现scull_dev结构,scull把一个指向前者的指针储存在private_data( in file structure)
另一种方法是储存在 inode 结构体中的次设备号,尤其你使用register_chrdev方法注册设备,scull_open的代码如下:
int scull_open(struct inode *inode, struct file *file)
{
struct scull_dev *dev; /* device information*/
dev = contain_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data =dev;/*for other methods */
/*now trim to 0 the length of the device if open was write
-only*/
if ( (filp->f_flags & 0_ACCMODE) == 0_WRONLY) {
scull_trim(dev);/*ignore errors*/
}
return 0; /* success*/
}
scull is a global structure.
这个函数的作用有两个:1.撤销任何已经分配给filp->private_data的数据,2. 最后一次关闭设备
int scull_release(struct inode *inode, struct file *file)
{
return 0;
}
因为没有硬件所以可以使用最简洁的方法去关闭scull.
如果一个设备文件关闭的次数比它打开的次数多会发生什么? (what happens when a device file is closed more times than it is opened),after all,dup和fork呼叫这open files不使用open,How to a driver know when an open device file has really been closed?
the answer is simple: not every close system call causes the release method to be invoked.the kernel keeps a counter of how many times a file structure is being used.Neither fork nor dup creates a new file structure (only open does that),They just increment the counter in the existing structure.The close system call executes the release method only when the file structure drops to 0,the guarantees that your driver sees only one release call for each open.
kernel automatically closes any file at process exit time by internally using the close system call.
##scull’s Memory Usage
two core functions used to manage memory in the Linux kernel.defined in <linux/slab.h>
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
其中,size 是申请的字节大小,flags 在这里是GFP_KERNEL(又是一个多重的宏定义),稍后再详细解释这个东西.只要不使用kmalloc,就别使用kfree,当然,NULL可以传递给kfree.kmalloc 不是最有效的方法去申请内存,但这里只是为了简单展现是read和write的方法,申请整页的方法以后讲.
这里有好好几种方法改变quantum和quantum set的大小:1,改变宏SCULL_QUANTUM和SCULL_QSET.2.在加载模块的时候指定scull_quantum和scull_qset的大小.3.或者改变ioctl的大小.
struct scull_qset {
void **data;
struct scull_qset *next;
}
下面的代码段展示了struct scull_dev和struct scull_qset如何持有data,__scull_trim__被scull_open激活.它遍历list和free quantum和quantum set.
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset;
int i;
for (dptr = dev->data;dptr; dptr = next){
/*all the list items*/
if(dptr->data){
for(i=0; i<qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
ssize_t read (struct file *flip, char __user *buff,
size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff,
size_t count, loff_t *offp);
这里的buff是用户空间的数据,不能直接被kernel 的代码使用,为了能够在用户空间和内核空间传递buff,我们引入了特别的函数<asm/uaccess.h>,
unsigned long copy_to_user(void __user *to,
const void *from,
unsigned long count);
unsigned long copy_from_user(void *to,
const void __user, *from,
unsigned long count);
需要注意的是,用户空间的地址可能不存在,这时你必须把该进程睡眠.你必须保证传递给这些函数的用户空间指针是合法的.:
A negative value is means an error,defined in <linux/errono.h>.typical values include -ETNTR(interrupted sytem call) -EFAULT(bad address).
These sysyem calls are versions of read and write taking an array of structures.each of which contains a pointer and a length value.
ssize_t (*read) (struct file *filp,const struct iovec *iov,
unsigned long count, loff_t *ppos);
ssize_t (*write) (struct file *filp, const struct iovec *iov,
unsigned long count, loff_t *ppos);
Here,the filp and ppos arugments are the same as for read and write.The __iovec__structure,defined in <linux/uio.h>
struct iovec
{
void __user *iov_base;
__kernel_size_t iov_len;
};
__kernel_size_t__是unsigned long ,Each iovec describes one chunk of data to be transferred; it starts at iov_base(in user space) and is iov_len bytes long.The count parameter tells the method how many iovec structure there are.
#函数
__task_pid_nr_ns(struct task_struct *task,enum pid_type type,struct pid_namespace *ns) #文件包含 #include<linux/sched.h> #函数定义 ##在内核源代码的位置 linux-3.2.64/kernel/pid.c ##函数定义格式:
#函数功能描述 此函数用于获取进程的进程号,但应该满足以下几个条件。 1.参数type如果不等于PIDTYPE_PID,则参数task用其所属任务组的第一个任务赋值,否则保持task不变。
2.此进程是参数task任务描述符的进程。
3.保证进程描述符和pid_namespaces和参数ns相同。
#参数补充(未写)
#举例
结合以前的知识,写出Makefile,执行
make
然后,键入
insmod __task_pid_nr_ns.ko
这时一般终端就会有消息产出,如果没有可以接着使用 __dmesg -c__命令 ##终端信息
Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244921] into __task_pid_nr_ns_init. Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244971] the pid of the find_get_pid is :2915 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244980] the result of __task_pid_nr_ns is :2915 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244988] the result of kernel_thread is :2915 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244995] the pid of current thread is :2914 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.245002] out __task_pid_nr_ns_init. Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.245964] in the kernel thread function dmesg -c dmesg: klogctl failed: 不允许的操作 yubo@debian:~/linux/process$
#find_get_pid() 定义:struct pid *find_get_pid(int nr) ##功能 此函数根据提供的进程号获取对应的进程描述符,并使进程描述符的count的值加1即此进程的用户数加1
##参数说明 nr即为进程号 ##返回参数 返回与参数提供的进程号对应的进程描述符,进程描述符定义如下:
##find_get_pid()应用举例
##输出结果
Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689851] into the find_get_pid_init. Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689863] the count of the pid is :2 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689864] the level of the pid is :0 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689866] the pid of the find_get_pid is :3554 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689867] the result of the kernel_thread is :3554 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689868] out find_get_pid_init. Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.690635] in the kerenl thread function!
看到一篇好的!文章,转过来。
如果你认为自己是互联网中的一员(业内人士或普通用户),想想自己是不是每天都徘沉溺于各种社区和同行、朋友、同学互动?是不是时时不忘阅读各种新鲜、热辣、搞笑的文章?是不是频繁参加业内各种各样的研讨会、聚会活动,有时候还会为没赶上而懊恼?真的需要这些吗?自己从这里面真正得到收获了吗?
本文是从 What I Wish Someone Had Told Me 4 Years Ago 翻译而来,作者是一个创业者,之前在微软工作。文章主要讲述了作者对创业、做事、时间管理、面对失败的感悟,以及一些优秀的建议。读罢此文,我觉得有必要和大家一起分享作者的这些经验。文章提到的这些,至少我是比较认同的。当你开始思考这些时,你会发现,原来自己一直都陷入了一个误区——看别人表演。
嗯,是时候开始行动了!
就在2007年,我毅然离开了微软,加入了创业者的队伍。像很多第一次创业的人一样,我对这种冒险非常的兴奋。同样也跟很多第一次创业的人一样,我茫然没有头绪。
我参加各种活动,聚会,研讨会,和西雅图本地的创业社区密切交往。当我到了硅谷后,我发现这里有更多的活动,聚会,更多的研讨会。在这个创业生态系统里,一切很忙、很活跃,我收获了大量的人们共享出来的知识和体验,我如饥似渴的享用着。
我还满腔热情的看了很多的博客,视频和书籍。活动、聚会、研讨会中人们分享和讨论的都是非常好的主题。
我甚至还加入了一个创业孵化机构!
直到决定开办自己的公司时,我才意识到,所有我的阅读、观看、参加的活动实际上没有给我带来任何的用处。我是认真的。完全没有任何用处。大部分我学到的东西都想不起来了,剩下一点能记住的也很少能应用到我现在的处境里。学习他人的经验和成功对我就像是在吃快餐,像是吃那些味道很好的垃圾食品,让我膨胀,让我自满。抱歉我在泼冷水,但这是事实。
起初,我努力把我学到的东西应用到我自己的问题里。不灵。这不可思议的时刻真的到来了,我平静的接受了这个事实:我浪费了大量的时间去学了一些根本不需要的东西,我相信有人说过的一句话正是在嘲笑我的这些行为:所有的这些活动,研讨会,博客文章都只是用来多拖延你一天立即行动的另一个借口。我默默的接受这个事实,以一个新手的心情往前走,沿着这个方向,我相信我将会理解出什么才是我需要的。
所有的变化由此而来。
我们每个人身上都有一种东西,想在这种存在着不确定性、具有风险的创业冒险活动中创造,成就,前进。但我们还有另外一种东西,每个人都希望能感到确定,可靠,让我们能做出正确的决策,让我们在这条道路上不至于失败、使自己受伤。矛盾冲突就是从此产生的。
可是这些博客,活动,访谈并不能真正的消解这些矛盾。只是暂时的,它让我知道,有很多人在做和我同样的事情,让我放松。去创业是让我走出我的安逸环境,而我所做的只是从一个安逸环境跳到另外一个安逸环境。你知道吗?我就职于一个不错的公司!
一天,我反省一下我目前的状况,发现自己忙于很多事情,唯独没有把时间用于自己的产品上。几个月后,我终于鼓足勇气对自己说:这唯一重要的事情是真正的坐下来去做事。
别误会。我觉得有些博客和研讨会还是有价值的。但除非你真正的去实施什么事情、给你所学所扩展的关系网创造一个用武之地,你就是在浪费你的宝贵时间。
下面就是一些帮助了我去克服这“创业困惑综合征”的东西:
1、我数周内不再阅读创业新闻和博客,我意识到对于我的产品我不缺任何东西。至于谁又获得了融资,谁被收购了,以及为什么在Google Chrome的竞争下IE丧失了它的市场份额,这跟我没有任何关系。我唯一要关心的人是客户,我唯一要关心的事情是他们的需求和愿望、如何提供他们最有价值的东西。
2、我数月不去参加那些创业相关的活动,而且开始通过咖啡和酒去交接朋友。每月我仍可能会去参加一两个活动,但那只是为了娱乐。我不再迷惑于为了成为一个企业家而且参加那些创业相关的活动。
3、我通过小项目来实践学习。我把一些想法细化成容易管理的细目,给自己设定期限去完成它们。项目和试验品是神奇的教学工具,因为你在学习你想要的,你在获得第一手资料。就像Keynotopia这个项目极大的帮助了我产生灵感,形成思路,我可以看见它,和它交互,展示给别人——这就是我首先要把它做出来的原因!有时一些小项目还可以让你获得不少收入。
4、每走一步,我都准备好一个问题清单,它能让我知道下一步该往哪里走。是该去获得更多的访问量?改进产品?还是在不提高访问量的情况下提高收入?我整理出最好的问题,然后研究它,咨询人们,然后把获得的答案立即付诸于行动。这些信息我都不会让它们在我的大脑里转的太久。
5、这是我的最爱:我更多的恐惧是产生于没有去行动,而不是相反的害怕去行动。我认识到,晚一天我没有让客户接受我的方案,竞争对手就多一天的机会比我先达到客户要求。我甚至设想到了如果我不能行动起来,最终将会看到最可怕的噩梦:从办公室又回到密尔顿,在Innotech的小隔间里饱食终日,拿着那个红色的订书机,等着下一次的发薪日。这正是我需要的充满魔力的紧迫感。
6、我第一是要把事情完成,然后才是把事情办对。我知道(深受教训),做事的劲头重要无比。如果你在得到一个想法后不能立即付诸实施,最终很有可能的结局是这个想法被束之高阁。如今不论何时我得到了一个想法,我会盯着它让它成为现实,让它变成实际。我立即行动,虽然方式不成熟,但之后想办法改进,这样就学会了自己真正需要的东西。
7、面对现实:如果你不走出你的安逸环境、动手去干,什么事情都不会发生。等待再等待,罕有行动。
我想留给你们一句改变了我的生活的名言:成功的人并不是一定比别人更有才智和更幸运。他们只是在不断的尝试、不断的失败,直到成功。
不要做想象中的企业家,用行动和成果成为企业家。
不知道各位是否体会到了作者的用心,他用他的经历告诉我们,只有真正坐下来做事,才是取得成功的唯一途径。说,没有用;行动,才是最重要的。
与君分享
目前我能想到的是做一个有关咨询的网站,目标是社会中产阶级,利用自己的优势去帮助 他们更好的理解国家政策法规、海外的投资机会,应该不是什么难的项目。
从邮件列表开始