#什麼是愛情
原来ctags的功能这么强大,之前自己是蒙着眼使用的啊。
有人说默认有这个插件,确实有,再下一次吧。
在你的工作目录中,输入以上命令,$是shell的命令提示符,不是命令的一部分,这一点需要注意,这样就会在当前工作目录下产生ctags的标签文件。
xx就是你要查找的变量和函数名
It maybe acted on some impact using “SHIFT + K”
在vim的命令行下输入__:ts__就会列出列表供用户选择满足的变量或者函数(tagslist)。
display preview tags-file and display next tags-file.
jump to the function or variable where whose were defined.
Return where jump from where
Waa…
command:
cscope -Rbqk
-R:表示把所有子目录里的文件也建立索引
-b:仅仅建立符号数据库
-q:生成cscope.in.out和cscope.po.out文件,加快cscope的索引
-k: 在生成索引文件时,不搜索/usr/include目录
vim的normal模式下输入:
:cs add cscope.out
就把索引文件加入到vim中.将以下命令放在.vimrc中,即可省去该命令:
if filereadable("cscope.out")
cs add cscope.out
endif
再试试把下面的内容复制到~/.vimrc里,这样就可以利用相应的快捷键进行不同的查找了. 原文1 原文2 cscope add cscope.out ##4. Commmand & Options After adding the file that is called cscope.out ,we can call cscope find x 简写 cscope f x
c:Find functions calling this function d:Find functions called by this function e:Find this egrep pattern f:Find this file g:Find this definition i:Find files #including this file s:Find this C symbol t:Find assignments to
#taglist 将这三个工具用起来了,然后在命令窗口中键入:
:TlistToggle
在关闭与打开之间转换.
我的vimrc中定义了下面的映射,使用“,tl”键就可以打开/关闭taglist窗口:
map
----------------- |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.