打印
[嵌入式linux]

开始学linux驱动,三个月熟悉,立帖为证!每日汇报进展

[复制链接]
楼主: tiger84
手机看帖
扫描二维码
随时随地手机跟帖
161
tiger84|  楼主 | 2010-1-28 20:42 | 只看该作者 回帖奖励 |倒序浏览
linux设备驱动中的阻塞与非阻塞I/O

阻塞操作是指在执行设备操作时若不能获得资源则刮起进程,直到满足可操作的条件后再进行操作。
非阻塞操作的进程进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。

等待队列
在linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。
linux2.6提供如下关于等待队列的操作。
1,定义“等待队列头”
wait_queue_head_t my_queue;
2,初始化“等待队列头”
init_waitqueue_head(&my_queue);
而下面的DECLARE_WAIT_QUEUE_HEAD()宏可以作为定义并初始化等待队列头的快捷方式。
DECLARE_WAIT_QUEUE_HEAD(name)
3,定义等待队列
DECLARE_WAITQUEUE(name,tsk)
该宏用于定义并初始化一个名为name的等待队列。
4,添加/移除等待队列
void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);用于将等待队列wait添加到等待队列头q指向的等待

队列链表中。
void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);用于将等待队列wait从附属的等待队列头q指向的

等待队列链表中移除。
5,等待事件
wait_event(queue,contion)
wait_event_interruptible(queue,contion)
wait_event_timeout(queue,contion,timeout)
wait_event_interruptible_timeout(queue,contion,timeout)
这4个从函数名字上就可以理解了。
6,唤醒队列
void wake_up(wait_queue_head_t *queue);与wait_event(queue,contion),wait_event_timeout(queue,contion,timeout)配合使

用。可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程;
void wake_up_interruptible(wait_queue_head_t *queue);与wait_event_interruptible(queue,contion),

wait_event_interruptible_timeout(queue,contion,timeout)配合使用。只能唤醒处于TASK_INTERRUPTIBLE的进程。
7,在等待队列上睡眠
sleep_on(wait_queue_head_t *queue);将目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队

列头q,知道资源可获得,q引导的等待队列被唤醒。
interruptible_sleep_on(wait_queue_head_t *queue);将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它

附属到等待队列头q,知道资源可获得,q引导的等待队列被唤醒或者进程收到信号。

它们的流程如下所示:
(1)定义并初始化一个等待队列,将进程状态改变为TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,并将等待队列添加到等待队列头


(2)通过schedule()放弃CPU,调度其他进程执行。
(3)进程被其他地方唤醒,将等待队列移除等待队列头。

总结
在设备驱动中阻塞IO一般基于等待队列来实现,等待队列可用于同步驱动中事件发生的先后顺序。使用非阻塞IO的应用程序也可借助

轮询函数来查询设备是否能立即被访问,用户空间调用select()和poll()接口,设备驱动提供poll()函数。设备驱动中的poll()本身

不会阻塞,但是poll()和select()系统调用则会阻塞地等待文件描述符集合中的至少一个可访问或超时。

使用特权

评论回复
162
tiger84|  楼主 | 2010-1-28 21:59 | 只看该作者
Linux设备驱动中的异步通知与异步I/O

异步通知的概念与作用
    阻塞与非阻塞访问、poll()函数提供了较好的解决设备访问的机制,若有了异步通知机制就更加完整了。
    异步通知的意思是:一旦设备就绪,则主动通知应用程序,貌似硬件中“中断”的概念,比较完整的称谓是“信号驱动的异步

I/O”。信号是软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。

信号是异步的,一个进程不必通过任何操作来等待信号的到达。
    阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可以访问,而异步通知则意味着设备

通知自身可访问,实现了异步I/O。由此可见,这几种方式I/O可以互为补充。
   
Linux信号
   在linux系统中,异步通知使用信号来实现。除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。一个信号

被捕获的意思是当一个信号到达时有相应的代码处理它。如果一个信号没有被这个进程锁捕获,内核将采用默认行为处理。

信号的接收
   在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数,如下所示:
   void(*signal(int signum,void (*handler))(int))(int);
   可以分解如下:
   typedef void(*sighandler_t)(int);
   sighandler_t signal(int signum,sighandler_t handler);
   第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获后,该函数将被执行。
   
   除了signal()函数外,sigaction函数可用于改变进程接收到特定信号后的行为,它的原型如下
            sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
    该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号。第二个参数是指向结构体sigaction的一个实例指针,在结构体sigaction的实例中,指定了对特定信号的处理函数,若为空,则进程会以默认方式对信号处理。第三个参数oldact指向的对象用来保存原来对相应信号的处理函数,可指定oldact为NULL。如果把第二个和第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

   为了在用户空间中能处理一个设备释放的信号,必须完成以下3项工作:
(1)通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到。
(2)通过F_SETFL IO控制命令设置设备文件支持FASYNC,即异步通知模式。
(3)通过signal函数连接信号和信号处理函数。

信号的释放

为了使设备支持异步通知机制,驱动程序中涉及以下3项工作。
(1)支持F_SETOWN命令;
(2)支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此驱动中应实现fasync()函数;
(3)在设备资源可获得时,调用kill_fasync()函数获得激发相应的信号;

很明显,驱动程序的这3项与应用程序中的3项工作是一一对应的。

   
linux2.6异步IO
在某些情况下,IO请求可能需要与其他进程产生重叠。异步I/O(AIO)应用程序接口就提供了这种功能。
linux异步IO是2.6内核版本的一个标准特性。AIO的基本思想是允许进程发起很多IO操作,而不用阻塞或等待任何操作完成。稍后或在接收到IO操作完成的通知时,进程就可以检索IO操作的结果。

关于AIO我暂时还没有用到,就先不研究了。

使用特权

评论回复
163
tiger84|  楼主 | 2010-1-28 22:11 | 只看该作者
刚刚才发现我使用cramfs时,仍然是把cramfs拷贝到sdram,然后从SDRAM启动的,这样比较花时间。
于是重新配置
setenv bootargs noinitrd root=1f03 init=/linuxrc rootfstype=cramfs console=ttyS0,115200 mem=64M
注释:root=1f03也就是root=/dev/mtdblock3,1f主设备号31,次设备号03
启动出现如下错误:
VFS: Cannot open root device "mtdblock3"

可以确认mtd分区无误,且根文件系统放在mtdblock3。

google

据说这是cramfs的一个BUG,把nandflash的ECC去掉就行。

照做,可以启动了。

最近几天碰到2个把ECC去掉就行的例子了,晕。

使用特权

评论回复
164
tiger84|  楼主 | 2010-1-28 22:26 | 只看该作者
今天白天上午一个培训,下午一个培训,白白浪费1天时间,还不得不参加,因为要记入什么KPI考核。好在我在培训的时候补上了一觉,哈哈。
明天晚上要项目聚餐,写不了周末计划了,今天先写了。
1,把中断与时钟,内存与I/O访问研究透;周六白天;
2,研究u-boot1.3.4; 周日
   a,把与产品CPU无关的文件及目录去掉;
   b,把与产品CPU无关的代码去掉;
   c,开始读u-boot程序,研究初始化相关代码。

使用特权

评论回复
165
tiger84|  楼主 | 2010-1-30 10:10 | 只看该作者
哥们加我吧,我也学Linux驱动,共同进步哦,QQ389582183
lantian5435 发表于 2010-1-29 10:47

好的,一起学习,呵呵

使用特权

评论回复
166
tiger84|  楼主 | 2010-1-30 10:11 | 只看该作者
中断与时钟
    在linux中断中引入了顶半部和底半部分离的机制。顶半部完成尽可能少的比较紧急的功能。它往往只是简单读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列去。这样,底半部执行的速度就会很快,可以服务更多的中断请求。以前在写单片机和arm程序时,也经常用到这种思想。
    现在,中断工作的处理重心就落到了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部最大的不同。

Linux中断编程
申请和释放中断
1,申请IRQ
int request_irq(unsigned int irq,
                void (*handler)(int irq,void *dev_id,struct pt_regs *regs),
                unsigned long irqflags,
                const char *devname,
                void *dev_id);
    irq是要申请的中断号。
    handler是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
    irqflags是中断处理属性,若设置了SA_INTERRUPT,则表示中断处理程序是快速处理程序;若设置了SA_SHIRQ,则表示多个设备共享中断,dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
    request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

2,释放IRQ
与request_irq()对应的函数为free_irq()
        void free_irq(unsigned int irq,void *dev_id);

3,使能和屏蔽中断
下列3个函数用于操作一个中断源,对系统内所有CPU生效。
void disable_irq(int irq);// 等待目前的中断处理完成后再使用
void disable_irq_nosynv(int irq);//立即返回
void enable_irq(int irq);

下列2个函数将屏蔽本CPU内的所有中断。
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
前者是将目前的中断状态保留在flags中,后者直接禁止中断。

与上述2个禁止中断对应的恢复中断的方法:
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);

底半部机制
linux系统实现底半部的机制主要有tasklet、工作队列和软中断。
1,tasklet
   使用比较简单,只需要定义tasklet及其处理函数,并将2者关联即可,例如:
void my_tasklet_fun(unsigned long);  // 定义一个处理函数
DECLARE_TASKLET(my_tasklet,my_tasklet_fun,data);// 实现了定义名称为my_tasklet的tasklet并将其与my_tasklet_fun()这个函数绑定,而传入这个函数的参数为data。

   在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行,如下所示:
        tasklet_schedule(&my_tasklet);

                tasklet使用模板
// 定义tasklet和底半部函数相关联
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,data);

// 中断处理底半部
void xxx_do_tasklet(unsigned long)
{
        ----
}

// 中断处理顶半部
irqreturn_t xxx_interrupt(int ira,void *dev_id,struct pt_regs *regs)
{
        ----
        tasklet_schedule(&xxx_tasklet);
        ----
}

//设备驱动模块加载函数
int __init xxx_init(void)
{
        ---
        //申请中断
        result = request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,"xxx",NULL);
        ---
}

//设备驱动模块卸载函数
void __exit xxx_exit(void)
{
        ---
        //释放中断
        free_irq(xxx_irq,NULL);
        ---
}

2,工作队列
工作队列的使用方法和tasklet非常相似。

3,软中断
软中断使用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果,tasklet也是基于软中断实现的。
    软中断和tasklet仍然运行于中断上下文,而工作队列则运行于进程上下文,因此软中断和tasklet处理函数不能睡眠,而工作队列处理函数允许睡眠。

前面说的异步通知所基于的信号也类似于中断。
硬中断,软中断和信号的区别:
硬中断是外部设备对CPU的中断;
软中断通常是硬中断服务程序对内核的中断;
信号是由内核(或其他进程)对某个进程的中断。

使用特权

评论回复
167
icecut| | 2010-1-30 10:26 | 只看该作者
ls的知识会让个思想更近一步.

使用特权

评论回复
168
tiger84|  楼主 | 2010-1-30 10:32 | 只看该作者
我现在的linux启动起来要花15S左右,仔细观察并记录启动过程后,比较花时间的地方如下:
1,u-boot初始化nandflash时;
2,u-boot初始化网络时;
3,bootdelay延时;
4,把内核从nand flash复制到sdram;
5,把内核解压缩;
6,网络的相关操作

简单的看下u-boot源代码。
1,u-boot初始化nandflash时;
   把ECC去掉?只检测u-boot用到的部分flash?
    ECC去掉,我试过,出现了一些警告,速度也没变快,放弃。
   只初始化部分flash,还没仔细看。
2,u-boot初始化网络时;
   把网络初始化中的重启一块去掉了,能节约2秒;而且启动时基本不会出现什么错误;如下
   board.c里面
  #if defined(CONFIG_RESET_PHY_R)
        debug ("Reset Ethernet PHY\n");
        reset_phy();
#endif
  去掉。
3,bootdelay延时;
   把bootdelay设置成0,发现再启动时,进不了u-boot了。于是源码如下:
   main.c里面
while ((bootdelay > 0) && (!abort)) {
                int i;

                --bootdelay;
                /* delay 100 * 10ms */
                for (i=0; !abort && i<100; ++i) {
                        if (tstc()) {        /* we got a key press        */
                                abort  = 1;        /* don't auto boot        */
                                bootdelay = 0;        /* no more delay        */
# ifdef CONFIG_MENUKEY
                                menukey = getc();
# else
                                (void) getc();  /* consume input        */
# endif
                                break;
                        }
                        udelay(10000);
                }

                printf("\b\b\b%2d ", bootdelay);
        }
修改如下,红色部分:
while ((bootdelay >= 0) && (!abort)) {
                int i;

                --bootdelay;
                /* delay 100 * 10ms */
                for (i=0; !abort && i<1; ++i) {
                        if (tstc()) {        /* we got a key press        */
                                abort  = 1;        /* don't auto boot        */
                                bootdelay = 0;        /* no more delay        */
# ifdef CONFIG_MENUKEY
                                menukey = getc();
# else
                                (void) getc();  /* consume input        */
# endif
                                break;
                        }
                        //udelay(10000);
                }

                printf("\b\b\b%2d ", bootdelay);
        }

有个坏处,启动时要进入u-boot,就得提前按着键了。

4,把内核从nand flash复制到sdram;
   把内核变小点?nand flash速度加快点?
   暂时还没做。
5,把内核解压缩;
   不了解怎么改。
6,网络的相关操作
   由于公司的IP不能随便用,我使用的是动态分配IP,改成静态IP,可以节约1到2秒。
   为了调试方便,板子启动时mount到主机上了,去掉,可节约1秒左右。
   
做完上述工作后,启动用了9秒,很大进步,呵呵。
不过,在网上,有人可以只用7秒,这个我相信后续我也可以优化到7S.
也有哥们说只用了4秒,方法是不经过u-boot,直接通过一个小bootload来启动,打算以后有时间试试这个。
各位兄弟是怎么改善的,提提意见吧。

使用特权

评论回复
169
tiger84|  楼主 | 2010-1-30 11:30 | 只看该作者
内核定时器
内核定时器编程软件意义上的定时器最终依赖硬件定时器来实现,内核在时钟中断发生后检测各定时器是否到期,到期后的定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序执行update_process_timers()函数,该函数调用run_local_timers()函数,这个函数处理TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。
在Linux设备驱动编程中,可以利用linux内核中提供的一组函数和数据结构来完成定时触发工作或者完成某周期性的事务。
linux内核所提供的用于操作定时器的数据结构和函数如下。
1,time_list
  struct time_list{
                struct list_head entry;  // 定时器列表
                unsigned long expires;  // 定时器到期时间
                void (*function)(unsigned long); // 定时器处理函数
                unsigned long data;        // 作为参数被传入定时器处理函数
                struct timer_base_s *base;
                };
2,初始化定时器
void init_timer(struct time_list *timer);
该函数初始化timer_list的entry的next为NULL,并给base指针赋值。

TIMER_INITIALIZER(_function,_expires,_data)宏用于赋值定时器结构体的function、expires、data、base成员。
DEFINE_TIMER(_name,_function,_expires,_data)宏是定义并初始化定时器成员的“快捷方式”。

此外,setup_timer()也可用于初始化定时器并赋值其成员。

3,增加定时器
void add_timer(struct time_list *timer);
4,删除定时器
void del_timer(struct time_list *timer);
5,修改定时器的expire
void mod_timer(struct time_list *timer,unsigned long expires);

                             内核定时器使用模板
// xxx设备结构体
struct xxx_dev
{
        struct cdev cdev;
        ---
        timer_list xxx_timer;// 设备要使用的定时器
}

//xxx驱动中的某函数
xxx_func1(---)
{
        struct xxx_dev *dev = filep->private_data;

        ---
        //初始化定时器
        init_timer(&dev->xxx_timer);
        dev->xxx_timer.function = &xxx_do_timer;
        dev->xxx_timer.data = (unsigned long)dev; //设备结构体指针作为定时器处理函数参数
        dev->xxx_timer.expires = jiffies + delay;

        // 添加注册定时器
        add_timer(&dev->xxx_timer);

        ---
}

//xxx驱动中的某函数
xxx_func2(---)
{
        struct xxx_dev *dev = filep->private_data;

        ---

        // 删除定时器
        del_timer(&dev->xxx_timer);

        ---
}

// 定时器处理函数
static void xxx_do_timer(unsigned long arg)
{
        struct xxx_devices *dev = (struct xxx_devices *)(arg);
        ---

        // 调度定时器再执行
        dev->xxx_timer.expires = jiffies + delay;
        add_timer(&dev->xxx_timer);
        ---
}

内核延时
linux内核提供如下3个函数分别进行纳秒、微妙和毫秒延迟。实际上都是忙等待!
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

在内核中最好不要直接使用mdelay()函数,对于毫秒级以上的时延,内核提供了如下函数
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);  // 可以被打断
void ssleep(unsigned int secdonds);

长延迟
比较当前的jiffies和目标jiffies

睡着延迟
    schedule_timeout()可以使当前任务睡眠指定的jiffies之后重新被调度执行。msleep()和msleep_interruptible在本质上都是依靠包含了schedule_timeout()的schedule_timeout_uninterruptible()和schedule_timeout_interruptible()实现的。

使用特权

评论回复
170
tiger84|  楼主 | 2010-1-30 17:13 | 只看该作者
本帖最后由 tiger84 于 2010-1-30 17:15 编辑

内存与IO访问

内存空间和IO空间
目前大多数嵌入式微处理器如ARM、POWERPC等中并不提供IO空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序中使用的变量和其他数据都存在于内存空间中。

内存管理单元MMU
为了理解基本的MMU操作原理,需先明晰几个概念。
TLB:Translation Lookaside Buffer,即转换旁路缓存,TLB是MMU的核心旗舰,它缓存少量的虚拟地址与物理地址的转换关系,是转换表的Cache,因此也经常被称为“快表”。
TTW:Translation Table walk,即转换表漫游,当TLB中没有缓存对应的地址转换关系时,需要通过对内存中转换表的访问来获得虚拟地址和物理地址的对应关系。TTW成功后,结果应写入TLB.

    当ARM要访问存储器时,MMU先查找TLB中的虚拟地址表。如果ARM的结构支持分开的数据TLB(DTLB)和指令TLB(ITLB),则除取指令使用ITLB外,其他的都使用DTLB。
     若TLB中没有虚拟地址的入口,则转换表遍历硬件从存放于主存储器中的转换表中获取地址转换信息和访问权限,同时将这些信息放入TLB,它或者被放在一个没有使用的入口或者替换一个已经存在的入口。之后,在TLB条目中控制信息的控制下,当访问权限允许时,对真实物理地址的访问就爱那个在Cache或者在内存中发生。
    ARM中的TLB条目中的控制信息用于控制对对应地址的高速缓存和写缓冲,并决定是否高速缓存。
   (1)C(高速缓存)和B(缓冲)位被用来控制对应地址的高速缓存和写缓冲,并决定是否采用高速缓存。
   (2)访问权限和域位用来控制读写访问是否被允许。

Linux内存管理

    对于包含MMU的处理器而言,Linux系统提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。
    在linux系统中,进程的4GB内存空间被分为2个部分,用户空间和内核空间。用户空间一般分布在0-3GB,剩下的3-4GB为内核空间。用户进程通常情况下只能访问用户空间的虚拟地址。
    每个进程的用户空间都是完全独立、互不相干的,用户进程各有不同的页表。而内核空间是由内核负责映射,它不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,内核的虚拟空间独立于其他程序。
    linux中1GB的内核空间又被分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区。如下:


                        4GB-----------------
                                保留区
                        --------------------
                        专用页面映射区
                        --------------------
                        高端内存映射区
                        --------------------
                        --------------------
                        vmalloc分配器区
                        --------------------
                        --------------------
                        物理内存映射区
                        3GB-----------------

    一般情况下,物理内存映射区最大长度为896M,系统的物理内存被顺序映射到内核空间的这个区域中。当系统物理内存大于896MB时,超过物理内存映射区的那部分内存称为高端内存(未超过的称为常规内存),内核在存取高端内存时必须将他们映射到高端页面映射区。
    linux保留内核空间最顶部FIXADDR_TOP---4GB的区域作为保留区。
    紧接着最顶端的保留区以下的一段区域为专用页面映射区(FIXADDR_START---FIXADDR_TOP)。它的总尺寸和每一页的用途由fixed_address枚举结构在编译的时候预定义,用__fix_to_virt(index)可获取专用区内预定义页面的逻辑地址。其开始地址和结束地址宏定义如下:
#define         FIXADDR_START                (FIXADDR_TOP - __FIXADDR_SIZE)
#define         FIXADDR_TOP                ((unsigned long)__FIXADDR_TOP)
#define                __FIXADDR_TOP                0xfffff000

接下来,如果系统配置了高端内存,则位于专用页面映射区之下的就是一段高端内存映射区,其起始地址为PKMAP_BASE,定义如下:
#defined        PKMAP_BASE                ((FIXADDR_BOOT_START - PAGE_SIZE * (LAST_PKMAP + 1)) & PMD_MASK)
其中涉及到的宏定义如下:
#define                LAST_PKMAP                PTRS_PER_PTE
#define                PTRS_PER_PTE                512
#define         PMD_MASK                (~(PMD_SIZE - 1))
#define                PAGE_SIZE                 (1UL << PMD_SHIFT)
#define                PMD_SHIFT                21

在物理区和高端映射区之间为虚存内存分配区(VMALLOC_START---VMALLOC_END),用于malloc()函数,它的前部与物理内存映射区

有一个隔离带,后部与高端映射区也有一个隔离带。vmalloc区域定义如下:
#define                VMALLOC_OFFSET                (8*1024*1024)
#define                VMALLOC_START                (((unsigned long)high_memory      \
                                        + vmalloc_earlyreserve            \
                                        + 2*VMALLOC_OFFSET - 1)           \
                                        & ~(VMALLOC_OFFSET - 1))
                                       
#ifdef        CONFIG_HIGHMEM
#define                VMALLOC_END                (PKMAP_BASE - 2*PAGE_SIZE)
#else
#define                VMALLOC_END                (FIXADDR - 2*PAGE_SIZE)

使用特权

评论回复
171
tiger84|  楼主 | 2010-1-30 17:31 | 只看该作者
本帖最后由 tiger84 于 2010-1-30 17:43 编辑

内存存取
用户空间内存动态申请和释放。理想情况下,malloc()和free()应成对出现,即谁申请,就由谁释放。
内核空间内存动态申请
在linux内核空间申请内存涉及到的函数主要包括kmalloc(),__get_free_pages()和vmalloc()等。kmalloc()和__get_free_pages()(及其类似函数)申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而vmallox()则是在虚拟内存空间给出的一块连续的内存区,实质上,这片连续的虚拟内存在物理内存上并不一定连续,也没有简单的换算关系。

虚拟地址与物理地址关系
对于内核物理内存映射区的虚拟内存,使用virt_to_phys()可以实现内核虚拟地址转化为物理地址,定义如下:
#define                __pa(x)                        ((unsigned long)x - PAGE_OFFSET)
extern  inline unsigned long virt_to_phys(volatile void *address)
{
        return        __pa(address);
}
上面转换过程中调用的__pa()会将虚拟地址减去PAGE_OFFSET,通常为3GB。

与之对应的函数为phys_to_virt()
#define                __va(x)                        ((unsigned long)x + PAGE_OFFSET)
extern  inline unsigned long phys_to_virt(volatile void *address)
{
        return        __va(address);
}
上述方法仅适用于常规内存。

IO内存静态映射
在将linux移植到目标电路板的过程中,通常会建立外设IO内存物理地址到虚拟地址的静态映射,这个映射通过在电路板对应的
map_desc结构体添加新成员来完成。
struct map_desc
{
unsigned long virtual;   // 虚拟地址
unsigned long pfn;       // __phys_to_pfn(phy_addr)
unsigned long length;    // 大小
unsigned long type;      // 类型
};

使用特权

评论回复
172
tiger84|  楼主 | 2010-1-30 17:36 | 只看该作者
关于设备IO端口和IO内存的访问暂时不涉及,先跳过

使用特权

评论回复
173
tiger84|  楼主 | 2010-2-1 21:55 | 只看该作者
工作上的事情开始忙了,刚完成今天的任务。只能看看书了

使用特权

评论回复
174
Quentin| | 2010-2-1 22:33 | 只看该作者
亡羊补牢,为时不晚!

使用特权

评论回复
175
tiger84|  楼主 | 2010-2-2 20:42 | 只看该作者
今天被串口程序折磨的死去活来,千万不要小看基础啊。

0x0d变成0x0a;
0x11,0x13收不到。
解决方法
Opt.c_iflag &=~(INLCR|IGNCR|ICRNL);
    Opt.c_oflag &=~(ONLCR|OCRNL);

    Opt.c_iflag &= ~(IXON|IXOFF);   /* not to set software control */

使用特权

评论回复
176
牛牛特工| | 2010-2-2 21:54 | 只看该作者
N久没来了 不知道为何
很久也做不了一件事情

使用特权

评论回复
177
icecut| | 2010-2-3 12:56 | 只看该作者
210# 牛牛特工
找几个枪战片.刺激一下你

使用特权

评论回复
178
反质子| | 2010-2-3 14:47 | 只看该作者
建议楼主以后好好整理一下学习过程,弄成个PDF 哈哈

使用特权

评论回复
179
tiger84|  楼主 | 2010-2-3 23:25 | 只看该作者
最近开始忙着产品的原理图及应用编程了,学习linux设备驱动时间被压缩了很多,效率必须想办法提高才行。
今天刚把回家的票搞定,比较兴奋。

使用特权

评论回复
180
tiger84|  楼主 | 2010-2-3 23:29 | 只看该作者
感觉研究linux驱动遇到点瓶颈,一些简单的驱动吧,都弄过了,复杂点的吧,花的时间比较多,又有现成的,就想放在后面研究。感觉不知道怎么继续下去了?顺其自然?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则