TL-WN823N,300M迷你型无线USB网卡
适用于TL-WN823N V1.0版本网卡的驱动程序.想在台式机电脑上使用TP-LINK TL-WN823N 300M 迷你型无线USB网卡,应该把它连到电脑的USB口上,然后电脑上装无线网卡的驱动。
tl wn823n无线驱动说明:
这个驱动是从TP-LINK TL-WN823N 300M迷你型无线USB网卡随卡光盘中复制出来的,如果你的原光盘驱动丢了,或者不方便从光盘安装驱动,可以使用这个。内附Utility配置工具安装数据包以及驱动程序。支持windowsXP/vista/win7_32及64位操作系统。附无线USB网卡配置指南。
编译时提示找不到config.h文件的解决办法
在安装Ginseng时,遇到了以下错误:
在包含自 regions.c:32 的文件中:
stats.c:34:26: 错误:linux/config.h:没有那个文件或目录
make[4]: *** [regions.o] 错误 1
make[4]: Leaving directory `/opt/ginseng1.2.2/banshee/libcompat'
make[3]: *** [libcompat] 错误 2
make[3]: Leaving directory `/opt/ginseng1.2.2/banshee'
make[2]: *** [banshee//dsu/dsu.o] 错误 2
make[2]: Leaving directory `/opt/ginseng1.2.2/cil'
make[1]: *** [setup] 错误 2
make[1]: Leaving directory `/opt/ginseng1.2.2/cil'
make: *** [setup] 错误 2
复制代码
Linux-2.6.20的网卡cs8900驱动分析
一、初始化阶段
网络初始化被调用的路径为:
init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1
真是不容易啊,终于进到cs89x0_probe1了,在这里开始探测和初始化cs8900了。下面就按照这个顺序来说明网络驱动第一阶段的工作。注意:这里的调用顺序是将cs8900驱动编入内核所产生的,如果将cs8900驱动选为模块,这个路径:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2也会执行。
1.1 init函数
我们知道当start_kernel函数完成后就会启动init进程执行,在真正的应用程序init进程(如busybox的/sbin/init)之前,Linux还需要执行一些初始化操作。init的代码可以在\init\main.c中找到,它的代码如下:
static int init(void * unused)
{
lock_kernel();
…… //省略多cpu的初始化代码先
do_basic_setup(); //我们所关注的初始化函数
……
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace(); //挂接根文件系统
}
……
free_initmem(); //释放初始化代码的空间
unlock_kernel();
…… //这几段没看懂
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //检查控制台console是否存在
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
……//这几段没看懂
if (ramdisk_execute_command) { //运行ramdisk_execute_command指定的init用户进程
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
……
if (execute_command) { //判断在启动时是否指定了init参数,如果指定,此值将赋给execute_command
run_init_process(execute_command); //开始执行用户init进程,如果成功将不会返回。
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
//如果没有指定init启动参数,则查找下面的目录init进程,如果找到则不会返回
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
//如果上面的程序都出错,则打印下面的信息,如果内核找到init进程,则程序不会指向到此处
panic("No init found. Try passing init= option to kernel.");
}
1.2 do_basic_setup函数
在这里我们最关心的是do_basic_setup函数,顾名思义该函数的功能就是“做基本设置”,它的实现代码也在\init\main.c中。do_basic_setup()完成外设及其驱动程序的加载和初始化。该函数代码如下所示:
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues(); //初始化工作队列
usermodehelper_init(); //初始化khelper内核线程,还没弄清楚
driver_init(); //初始化内核的设备管理架构需要的数据结构,很复杂,以后在谈这部分。
#ifdef CONFIG_SYSCTL
sysctl_init(); //没搞懂
#endif
do_initcalls(); //重点函数,初始化的主要工作就靠它了
}
1.3 do_ initcalls函数
do_initcalls函数将会调用内核中所有的初始化函数,它的代码同样在\init\main.c中。do_initcalls函数调用其他初始化函数相当简洁,它的关键代码如下所示:
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call ) {
……
result = (*call)();
……
}
简洁归简洁,但这段代码是什么意思呢?这说来就话长了,最重要的应该是先了解Linux处理初始化的大体思想,由于Linux有很多部分需要初始化,每个部分都有自己的初始化函数,如果按照常理一个一个的调用未免显得冗长,而且也不便于扩展。那么Linux是怎么处理的呢?首先,Linux将各个部分的初始化函数编译到一个块内存区中,当初始化完了以后释放这块内存区,这就是init函数中free_initmem所要做的事。然后,再在另外的内存区中存放一些函数指针,让每个指针指向一个初始化函数。然后在do_initcalls中依次根据这些指针调用初始化函数。
上面一段就是Linux实现初始化的大体思想,下面我们看看它最终是怎么实现的。首先要了解的是__define_initcall宏,该宏的定义在\ include\linux\init.h中,它的原型如下所示:
#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
__define_initcall宏有三个参数,level表示初始化函数的级别,level值的大小觉得了调用顺序,level越小越先被调用,fn就是具体的初始化函数,id简单标识初始化函数,现在还没找到有什么用^_^。__define_initcall的功能为,首先声明一个initcall_t类型的函数指针__initcall_##fn##id,initcall_t的原型为:
typedef int (*initcall_t)(void);
该类型可简单理解为函数指针类型^_^。然后,让该函数指针指向fn。最后,通过编译器的编译参数将此指针放到指定的空间".initcall" level ".init"中,__attribute_used向编译器说明这段代码有用,即使在没用到的时候,编译器也不会警告。__attribute__的__section__参数表示该段代码放入什么内存区域中,也即指定编译到什么地方,编译参数更详细的地方可以查阅GCC文档,在gcc官方网站http://gcc.gnu.org/onlinedocs/中能找到各个版本的手册。这样说来还是比较抽象,下面举个例子来说明:
假如有初始化函数init_foolish函数,现在使用__define_initcall宏向内核加入该函数。假如调用方式如下:
__define_initcall("0",init_foolish,1);
那么,__define_initcall宏首先申请一个initcall_t类型的函数指针__initcall_init_foolish1(注意替换关系),且使该指针指向了init_foolish,函数指针__initcall_init_foolish1被放到.initcall.0.init内存区域中,这个标志在连接时会用到。
有了上面的基础知识,现在回到do_initcalls函数中,首先注意到是__initcall_start和__initcall_end,它们的作用就是界定了存放初始化函数指针区域的起始地址,也即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。换句话说,只要某段程序代码从__initcall_start开始依次调用函数指针,那么就可以完成各个部分的初始化工作,这显得十分优雅而且便于扩充,再看看do_initcalls,它何尝不是如此呢。这里还有一个有用的技巧就是__initcall_start和__initcall_end的原型是initcall_t型的数组,以后可以使用这种技巧^_^。
现在我们知道了do_initcalls函数的实现原理,那么到底它调用了多少初始化函数呢?我们怎样才能知道呢?根据上面的分析,我们知道所有的初始化函数的指针都放在__initcall_start和__initcall_end区域期间,而函数指针与它指向的函数之间又有固定的关系,如上面的例子,初始化函数名为init_foolish,指向它的函数指针就是__initcall_init_foolish1,即在此函数加上前缀__initcall_和一个数字后缀,反之,从函数指针也可推出初始化函数名。有了这两个信息,我们就可以很方便的找个初始化函数。怎么找呢??首先打开Linux完后产生的System.map文件,然后找到__initcall_start和__initcall_end字符串,你会发现它们之间有很多类似于__initcall_xxx1这样的符号,这些符号就是我们需要的函数指针了,这样就可推出初始化函数的名字。比如,我们这里需要的函数指针__initcall_net_olddevs_init6,按照上面的名字规则,很容易推出它所指向的初始化函数名字是net_olddevs_init。
得到了初始化函数的名字又怎么样呢?又不知道它在哪个文件里,不要着急!请打开你的浏览器登陆http://lxr.linux.no/ident网站,然后选择Linux版本和架构,然后可以搜索我们想要的信息。比如我输入net_olddevs_init,然后我就会得到该函数所在文件的相关信息。
1.4 net_olddevs_init函数
我们知道net_olddevs_init函数在do_initcalls函数中被调用并执行,那么它到底要做什么呢?看看实现代码就知道了,它的实现代码可以在\drivers\net\Space.c中找到。对于网络驱动部分的主要实现代码如下:
static int __init net_olddevs_init(void){
……
int num;
for (num = 0; num < 8; num)
ethif_probe2(num);
……
}
这段代码就不用讲解了吧,嘿嘿!就是调用了8次ethif_probe2,赶快去看看ethif_probe2长什么样子。
1.5 ethif_probe2函数
先看看该函数的实现代码,该代码也在\drivers\net\Space.c文件中。
static void __init ethif_probe2(int unit)
{
unsigned long base_addr = netdev_boot_base("eth", unit); // 由于ethif_probe2被net_olddevs_init调用了8次,
// 所以unit的值为0~7,也即在这里可以注册eth0~eth7八个网络设备
if (base_addr == 1)
return;
设备与驱动的关系以及设备号、设备文件
Linux设备分类
Linux下的设备通常分为三类,字符设备,块设备和网络设备。
字符设备
一个字符设备是一种字节流设备,对设备的存取只能按顺序按字节的存取而不能随机访问,字符设备没有请求缓冲区,所有的访问请求都是按顺序执行的。Linux下的大多设备都是字符设备。应用程序是通过字符设备节点来访问字符设备的。设备节点一般都由mknod命令都创建在/dev目录下,下面的例子显示了串口设备的设备节点。字符设备文件的第一个标志是前面的“c”标志。
root#ls -l /dev/ttyS[0-3]
crw-rw---- 1 root root 4, 64 Feb 18 23:34 /dev/ttyS0
crw-r----- 1 root root 4, 65 Nov 17 10:26 /dev/ttyS1
crw-rw---- 1 root root 4, 66 Jul 5 2000 /dev/ttyS2
crw-rw---- 1 root root 4, 67 Jul 5 2000 /dev/ttyS3
字符设备是指那些只能按顺序一个字节一个字节读取的设备,但事实上现在一些高级字符设备也可以从指定位置一次读取一块数据。字符设备是面向数据流的设备,每个字符设备都有一个设备号,设备号由主设备号和次设备号组成。同时Linux使用管理文件相同的方法来管理字符设备,所以每个字符设备在/dev/目录下都有一个对应的设备文件,即设备节点,它们包含了设备的类型、主/次设备号以及设备的访问权限控制等,系统通过设备文件来对字符设备进行操作,每个字符设备文件都有自己的与普通文件相同的文件操作函数组结构(struct file_operations)。字符设备驱动通常至少需要实现文件操作函数组中的open、release、read和write四种操作方法。常见的字符设备有鼠标、键盘、串口、控制台等。
exec函数族
疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?而且,就我们的常识来说,当我们执行一个程序的时候,新产生的进程的内容应就是程序的内容才对。是我们理解错了吗?显然不是,要解决这些疑惑,就必须 提到我们下面要介绍的exec系统调用。
1 简介
说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件 。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程 ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回 一个-1,从原程序的调用点接着往下执行。
现在我们应该明白了,Linux下是如何执行新程序的,每当有进程认为自己不能为系统和拥护做出任何贡献了,他就可以发挥最后一点余热,调用任 何一个exec,让自己以新的面貌重生;或者,更普遍的情况是,如果一个进程想执行另一个程序,它就可以fork出一个新进程,然后调用任何一个 exec,这样看起来就好像通过执行应用程序而产生了一个新进程一样。
事实上第二种情况被应用得如此普遍,以至于Linux专门为其作了优化,我们已经知道,fork会将调用进程的所有内容原封不动的拷贝到新产生的子
进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,这看起来非常不 划算,于是人们设计了一种"写时拷贝(copy-on-write)"技术,使得fork结束后并不立刻复制父进程的内容,而是到了真正实用的时候才复 制,这样如果下一条语句是exec,它就不会白白作无用功了,也就提高了效率。
2 稍稍深入
上面6条函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。在学习它们之前,先来了解一下我们习以为常的main函数。
下面这个main函数的形式可能有些出乎我们的意料:
int&nbsnbsp;main(int argc, char *argv[], char *envp[])
它可能与绝大多数教科书上描述的都不一样,但实际上,这才是main函数真正完整的形式。
参数argc指出了运行该程序时命令行参数的个数,数组argv存放了所有的命令行参数,数组envp存放了所有的环境变量。环境变量指的是一组值,从用户登录后就一直存在,很多应用程序需要依靠它来确定系统的一些细节,我们最常见的环境变量是PATH,它指出了应到哪里去搜索应用程序,如
/bin;HOME也是比较常见的环境变量,它指出了我们在系统中的个人目录。环境变量一般以字符串"XXX=xxx"的形式存在,XXX表示变量名,xxx表示变量的值。
值得一提的是,argv数组和envp数组存放的都是指向字符串的指针,这两个数组都以一个NULL元素表示数组的结尾。
我们可以通过以下这个程序来观看传到argc、argv和envp里的都是什么东西:
/* main.c */
int main(int argc, char *argv[], char *envp[])
{
printf("\n### ARGC ###\n%d\n", argc);
printf("\n### ARGV ###\n");
while(*argv)
printf("%s\n", *(argv++));
printf("\n### ENVP ###\n");
while(*envp)
printf("%s\n", *(envp++));
return 0;
}
编译它:
$ cc main.c -o main
运行时,我们故意加几个没有任何作用的命令行参数:
./main -xx 000
结果如下:
### ARGC ###
3
linux内核源码目录结构
在阅读源码之前,还应知道Linux内核源码的整体分布情况。现代的操作系统一般由进程管理、内存管理、文件系统、驱动程序和网络等组成。Linux内核源码的各个目录大致与此相对应,其组成如下(假设相对于Linux-2.4.23目录):
arch目录包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel CPU及与之相兼容体系结构的子目录。PC机一般都基于此目录。