一年又一年
转眼间,2011年又过去了,这一刻,回头看见自己这一路的风景,百感交集……
痛
这一年恐怕我这辈子都难以忘怀,因为对我来说实在太痛苦了,真的太痛苦了…… 在那段时间我甚至祈求上天早点结束我的生命,因为每活一分钟痛苦就会增加一分。直到现在我仍然不清楚我是否真的挺过来了,不过我真得想永远忘记它,永远!同时感谢在我最困难的时候帮助过我的人,真心的谢谢他们。
大学
大四了,要结束了,其实已经结束了,确切的说一开始就结束了。
我的大学生活大部分时间是在图书馆中度过的,每天面对着黑色的终端,对我而言仿佛整个世界都是黑色的,犹如在黑夜中一般。我始终努力在黑夜中找到属于自己的光明,但是我始终没有找到,现在仍然在黑夜中蹒跚前行 ……
呐喊、彷徨、沉沦、朝花夕拾,是大部分大学生四年的真实写照。但是对我来说只有呐喊和彷徨,因为始终处于一种茫然的状态,不知道付出这么多何时才能收获。
总之我的大学生活很不精彩,病了,一个人扛;烦了,一个人藏;痛了,一个人挡 …… 除了同学间的情谊其他的没什么值得回忆的。
Intern
这一年对我来说唯一的好事就是实习了。来到了一家还说得过去的公司实习,真的挺高兴的。记得那是一个艳阳高照的下午,在食堂吃饭的我突然接到电话,说要马上要进行电话面试,当时既激动又忐忑,惶恐中找到了一个人际稀少的楼梯口前坐了下来 ……
就这样揣着梦想只身一人来到了帝都 …… 作为一个还没毕业的菜鸟,工作中一直是保持着虚心学习态度。实习过程中遇到了很多牛人同时也学习到了很多,和一群热爱 Linux 的人工作在一起真是一件幸福的事!我不知道未来会是怎样,我也尽量不去想,因为往往计划的东西最终都没有按计划执行,一切随缘吧,我所能做的就是把当前的事做好。一万年太久,只争朝夕!
2012
世界末日?如果要真是世界末日我还蛮期待的,因为只有那个时候才能实现真正的众生平等!那个时候再也不会为就业和房价等问题发愁 …… 那个时候才叫一个洒脱!
不知道怎么回事以前能看书看一下午的我现在每当拿起书脑子中就思绪万千根本就看不进去,或许是因为寂寞吧。我的意志总被寂寞吞噬 …… 每当下班时我是又高兴又害怕,高兴的是忙了一天终于可以休息休息了,害怕的是回去能干什么呢?看墙?看窗?还是看风景?一个对下班没有期待的人下了班能干吗?
人生路漫漫,失意何其多?有时候确实挺无奈的,但无奈又有什么用呢?还不得昂起头继续前进!生活就像是被强奸,既然无力反抗倒不如默默享受。
新的一年即将来临,祝愿自己在新的一年中毕业顺利,工作顺利,活的快乐、充实、洒脱!BTW:如果可能的话,我非常渴望找到人生中的另一半,我知道这几乎没有可能,但是我真的很想,真的很想吗!
迁移完毕
终于迁移完毕,啊,好累啊!
接下来会把之前看过的内存(buddy + slab),PCI,USB(EHCI,UHCI,hub,usb-storage),等其他文章贴上来,因为之前看的时候已经总结好了,只需 (Ctrl + v) 就好,英明吧!!
在看这些代码时遇到了各种困难,各种 google,大部分问题得以解决就是因为参考了前辈们的博文,感谢他们的分享精神,真心的谢谢他们!所以我想把我的理解或者说愚见和大家分享一下,当然不一定对(大牛请绕过),总之互相探讨吧,希望对大家有帮助!
ioremap_nocache 函数分析(二)
非连续映射地址空间
`</p>
static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,
unsigned long start, unsigned long end,
int node, gfp_t gfp_mask)
{
struct vm_struct **p, *tmp, *area;
unsigned long align = 1;
unsigned long addr;
BUG_ON(in_interrupt());
if (flags & VM_IOREMAP) {
int bit = fls(size);
if (bit > IOREMAP_MAX_ORDER)
bit = IOREMAP_MAX_ORDER;
else if (bit < PAGE_SHIFT)
bit = PAGE_SHIFT;
align = 1ul << bit;
}
addr = ALIGN(start, align);
size = PAGE_ALIGN(size);
if (unlikely(!size))
return NULL;
area = kmalloc_node(sizeof(*area), gfp_mask & GFP_LEVEL_MASK, node);
分配虚拟页面结构
if (unlikely(!area))
return NULL;
/*
* We always allocate a guard page.
*/
size += PAGE_SIZE;
作为隔离带
write_lock(&vmlist_lock);
查找之前的 struct vmlist 链表(有序链表从小到大) 查找到一个合理的虚拟地址空间
for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {
if ((unsigned long)tmp->addr < addr) {
if((unsigned long)tmp->addr + tmp->size >= addr)
addr = ALIGN(tmp->size +
(unsigned long)tmp->addr, align);
continue;
}
if ((size + addr) < addr)
goto out;
回绕
if (size + addr <= (unsigned long)tmp->addr)
goto found;
addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);
if (addr > end - size)
goto out;
}
found:
area->next = *p;
*p 可能为 NULL
*p = area;
新申请的 struct vm_struct 加入到 vmlist 链表中
area->flags = flags;
area->addr = (void *)addr;
VMALLOC_START (线性地址)
area->size = size;
area->pages = NULL;
area->nr_pages = 0;
area->phys_addr = 0;
write_unlock(&vmlist_lock);
return area;
out:
write_unlock(&vmlist_lock);
kfree(area);
if (printk_ratelimit())
printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc= to increase size.\n");
return NULL;
}`</pre>
注意:此时我们假设没有打开PAE!
(页目录)
`</p>
int ioremap_page_range(unsigned long addr,
unsigned long end, unsigned long phys_addr, pgprot_t prot)
{
pgd_t *pgd;
unsigned long start;
unsigned long next;
int err;
BUG_ON(addr >= end);
start = addr;
线性地址
phys_addr -= addr;
pgd = pgd_offset_k(addr);
求出线性地址 addr 在页目录表中的地址
#define pgd_offset_k(address) pgd_offset(&init_mm, address)
#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address))
#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))
页目录表中的偏移
#define PGDIR_SHIFT 22
#define PTRS_PER_PGD 1024
do {
next = pgd_addr_end(addr, end);
一般情况下返回 end(线性地址末端)
err = ioremap_pud_range(pgd, addr, next, phys_addr+addr, prot);
if (err)
break;
} while (pgd++, addr = next, addr != end);
flush_cache_vmap(start, end);
return err;
返回 0
}`
#define pgd_addr_end(addr, end) \
({ \
unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK; \
\
(__boundary - 1 < (end) - 1)? __boundary: (end); \
})
PGDIR_SIZE = 1<<22
PGDIR_MASK = 3FFFFF
> addr 与 end 之间大小不能超过4Mb,因为一个页目录项最多表示4Mb 内存
(页上层目录)
`</p>
static inline int ioremap_pud_range(pgd_t *pgd, unsigned long addr,
unsigned long end, unsigned long phys_addr, pgprot_t prot)
{
参数:
pgd 线性地址 addr 所表示的页目录地址
addr 线性地址
end 线性地址末端
phys_addr = EHCI 总线地址
prot 标志
pud_t *pud;
unsigned long next;
phys_addr -= addr;
pud = pud_alloc(&init_mm, pgd, addr);
返回的是pgd 页目录地址
if (!pud)
return -ENOMEM;
do {
next = pud_addr_end(addr, end);
#define pud_addr_end(addr, end) (end)
if (ioremap_pmd_range(pud, addr, next, phys_addr + addr, prot))
return -ENOMEM;
} while (pud++, addr = next, addr != end);
return 0;
}`
static inline pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)
{
return (unlikely(pgd_none(*pgd)) && __pud_alloc(mm, pgd, address))?
NULL: pud_offset(pgd, address);
}
static inline pud_t * pud_offset(pgd_t * pgd, unsigned long address)
{
return (pud_t *)pgd;
}
(页中间目录)
`</p>
static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr,
unsigned long end, unsigned long phys_addr, pgprot_t prot)
{
pud 为pgd
pmd_t *pmd;
unsigned long next;
phys_addr -= addr;
pmd = pmd_alloc(&init_mm, pud, addr);
pmd 还是为 pgd
if (!pmd)
return -ENOMEM;
do {
next = pmd_addr_end(addr, end);
#define pmd_addr_end(addr, end) (end)
if (ioremap_pte_range(pmd, addr, next, phys_addr + addr, prot))
return -ENOMEM;
} while (pmd++, addr = next, addr != end);
return 0;
}
static inline pmd_t *pmd_alloc(struct mm_struct *mm, pud_t *pud, unsigned long address)
{
return (unlikely(pud_none(*pud)) && __pmd_alloc(mm, pud, address))?
NULL: pmd_offset(pud, address);
}
static inline pmd_t * pmd_offset(pud_t * pud, unsigned long address)
{
return (pmd_t *)pud;
}`
(页表) 开始干正经事了
`</p>
static int ioremap_pte_range(pmd_t *pmd, unsigned long addr,
unsigned long end, unsigned long phys_addr, pgprot_t prot)
{
pte_t *pte;
unsigned long pfn;
pfn = phys_addr >> PAGE_SHIFT;
EHCI 控制器总线地址的页框号
pte = pte_alloc_kernel(pmd, addr);
创建页表(如果不存在)
pte 为线性地址addr 在页表中的地址
if (!pte)
return -ENOMEM;
do {
BUG_ON(!pte_none(*pte));
set_pte_at(&init_mm, addr, pte, pfn_pte(pfn, prot));
#define pfn_pte(pfn, prot) __pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot))
很明显最后一个参数为 EHCI 的总线地址加上标志位(设置到页表中的地址为总线地址)
设置页表
pfn++;
} while (pte++, addr += PAGE_SIZE, addr != end);
此时假设 EHCI映射到内存的 I/O MEM大小为 1024Kb,此处会循环设置
return 0;
}`
`</p>
#define pte_alloc_kernel(pmd, address) \
((unlikely(!pmd_present(*(pmd))) && __pte_alloc_kernel(pmd, address))? \
NULL: pte_offset_kernel(pmd, address))`
`</p>
int __pte_alloc_kernel(pmd_t *pmd, unsigned long address)
{
pte_t *new = pte_alloc_one_kernel(&init_mm, address);
申请一页页框作为页表
if (!new)
return -ENOMEM;
spin_lock(&init_mm.page_table_lock);
if (pmd_present(*pmd)) /* Another has populated it */
pte_free_kernel(new);
else
pmd_populate_kernel(&init_mm, pmd, new);
设置到页目录表中去(将页表地址填入到页目录中)
spin_unlock(&init_mm.page_table_lock);
return 0;
}`
`</p>
pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address)
{
return (pte_t *)__get_free_page(GFP_KERNEL|__GFP_REPEAT|__GFP_ZERO);
}`
`</p>
#define pte_offset_kernel(dir, address) \
((pte_t *) pmd_page_vaddr(*(dir)) + pte_index(address))`
> 线性地址 addr 在页表中的地址
`</p>
#define pmd_page_vaddr(pmd) \
((unsigned long) __va(pmd_val(pmd) & PAGE_MASK))
#define pte_index(address) \
(((address) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))`
取线性地址的中间10位
`</p>
#define PTRS_PER_PTE 1024`
调用 ioremap_nocache() 函数之后,返回一个线性地址,此时 CPU 可以访问设备的内存(已经将其映射到了线性地址空间中了),此时 CPU 可以使用访问内存的指令访问设备的内存空间(host bridge 判断访问物理内存还是设备中的内存),此时我们就可以像访问内存一样来访问设备的内存(寄存器)!
现在我们就可以使用 readl() 或 writel() 函数读取或写入 EHCI 中 Capability Registers Operational Registers,此时我们就可以对EHCI 编程了!
注意:
PCI 设备的 I/O 寄存器 与 配置寄存器 的区别哦!
只提一点,后续的文章会有一部分专门介绍 PCI
对于任何一个PCI 设备 其内部
都有 I/O 寄存器 \----> 由cpu 直接访问 (writel or readl)
PCI 配置寄存器 \----> host bridge 直接访问 (pci\_read\_config_word)
真相在此:
ioremap_nocache 函数分析(一)
最近一直在研究 USB 看到了 EHCI 部分,在 usb_hcd_pci_probe () 函数中:
`</p>
hcd->regs = ioremap_nocache (hcd->rsrc_start, hcd->rsrc_len);`
ioremap_nocache() 函数我想大家都不陌生,现在我就把此函数分析一下,不当之处请大家谅解!
对于 EHCI 来说它把它本身的寄存器和内存映射到内存中区!但是站在 CPU 的角度来说,我们无法直接访问这块内存空间,需要将设备的总线地址映射成一个 cpu 可访问的线性地址!
调用 ioremap_nocache()函数之后,返回一个线性地址,此时 CPU 可以访问设备的内存(已经将其映射到了线性地址空间中了),此时CPU 可以使用访问内存的指令访问设备的内存空间(host bridge 判断访问物理内存还是设备中的内存),此时我们就可以像访问内存一样来访问设备的内存(寄存器)!
内核版本 2.6.22.1
cat /proc/iomem
此时我们就以此区间(0xd8426800 – 0xd8426bff)表示 EHCI 总线地址区间(机器内存3G)
`</p>
/**
* ioremap_nocache - map bus memory into CPU space
* @offset: bus address of the memory
* @size: size of the resource to map
*
* ioremap_nocache performs a platform specific sequence of operations to
* make bus memory CPU accessible via the readb/readw/readl/writeb/
* writew/writel functions and the other mmio helpers. The returned
* address is not guaranteed to be usable directly as a virtual
* address.
*
* This version of ioremap ensures that the memory is marked uncachable
* on the CPU as well as honouring existing caching rules from things like
* the PCI bus. Note that there are other caches and buffers on many
* busses. In particular driver authors should read up on PCI writes
*
* It's useful if some control registers are in such an area and
* write combining or read caching is not desirable:
*
* Must be freed with iounmap.
*/
void __iomem *ioremap_nocache (unsigned long phys_addr, unsigned long size)
{
phys_addr EHCI 总线地址
size 区间大小(1023)
unsigned long last_addr;
void __iomem *p = __ioremap(phys_addr, size, _PAGE_PCD);
if (!p)
return p;
/* Guaranteed to be > phys_addr, as per __ioremap() */
last_addr = phys_addr + size - 1;
if (last_addr < virt_to_phys(high_memory) - 1) {
struct page *ppage = virt_to_page(__va(phys_addr));
unsigned long npages;
phys_addr &= PAGE_MASK;
/* This might overflow and become zero.. */
last_addr = PAGE_ALIGN(last_addr);
/* .. but that's ok, because modulo-2**n arithmetic will make
* the page-aligned "last - first" come out right.
*/
npages = (last_addr - phys_addr) >> PAGE_SHIFT;
if (change_page_attr(ppage, npages, PAGE_KERNEL_NOCACHE) < 0) {
iounmap(p);
p = NULL;
}
global_flush_tlb();
}
return p;
}`
`</p>
#define _PAGE_PCD 0x010`
非连续映射地址空间
非连续映射地址空间用于将连续的线性地址映射到不连续的物理地址!同时它也提供了一种访问高物理内存的方法!
内核主要在一下三种情况使用非连续映射地址空间
映射设备的I/O空间
为内核模块分配空间
为交换分区分配空间
非连续映射地址空间的起始地址在常规映射地址空间的结束地址后8MB-16MB之间,而且保证8MB对齐(地址的低24位为0)
`</p>
/*
* Remap an arbitrary physical address space into the kernel virtual
* address space. Needed when the kernel wants to access high addresses
* directly.
*
* NOTE! We need to allow non-page-aligned mappings too: we will obviously
* have to convert them into an offset in a page-aligned mapping, but the
* caller shouldn't need to know that small detail.
*/
void __iomem * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
void __iomem * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
pgprot_t prot;
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
总线地址末端
if (!size || last_addr < phys_addr)
return NULL;
/*
* Don't remap the low PCI/ISA area, it's always mapped..
*/
if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS)
return (void __iomem *) phys_to_virt(phys_addr);
#define ISA_START_ADDRESS 0xa0000
#define ISA_END_ADDRESS 0x100000
640kb-1Mb 之间(此空洞用于连接到ISA总线上的设备)
/*
* Don't allow anybody to remap normal RAM that we're using..
*/
if (phys_addr <= virt_to_phys(high_memory - 1)) {
high_memory 为 896Mb 对应线性地址
phys_addr 在小于896Mb的常规内存空间中
char *t_addr, *t_end;
struct page *page;
t_addr = __va(phys_addr);
转化成线性地址
t_end = t_addr + (size - 1);
若小于896MB 则此页框应该被设置为保留
for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
if(!PageReserved(page))
return NULL;
}
prot = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY
| _PAGE_ACCESSED | flags);
#define __pgprot(x) ((pgprot_t) { (x) }
/*
* Mappings have to be page-aligned
*/
offset = phys_addr & ~PAGE_MASK;
取一页页框的偏移
phys_addr &= PAGE_MASK;
总线地址按4KB 对齐
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1))
size = PAGE_ALIGN(last_addr+1) - phys_addr;
#define PAGE_ALIGN(addr) (((addr)+PAGE_SIZE-1)&PAGE_MASK)
/*
* Ok, go for it..
*/
area = get_vm_area(size, VM_IOREMAP | (flags << 20));
#define VM_IOREMAP 0x00000001 /* ioremap() and friends */
申请一个非连续映射节点描述符
if (!area)
return NULL;
area->phys_addr = phys_addr;
总线地址
addr = (void __iomem *) area->addr;
起始线性地址
if (ioremap_page_range((unsigned long) addr,
(unsigned long) addr + size, phys_addr, prot)) {
vunmap((void __force *) addr);
return NULL;
}
return (void __iomem *) (offset + (char __iomem *)addr);
offset + addr
offset 为 0
addr 为线性地址,此地址被 CPU 用于读写 EHCI I/O mem 空间
这也验证那句话:在X86平台上总线地址就是物理地址
}`
`</p>
/**
* get_vm_area - reserve a contingous kernel virtual area
* @size: size of the area
* @flags: %VM_IOREMAP for I/O mappings or VM_ALLOC
*
* Search an area of @size in the kernel virtual mapping area,
* and reserved it for out purposes. Returns the area descriptor
* on success or %NULL on failure.
*/
struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
{
return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);
}
struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,
unsigned long start, unsigned long end)
{
return __get_vm_area_node(size, flags, start, end, -1, GFP_KERNEL);
}`
const
最近一直在阅读 Linux 内核源代码,发现 const 用处很多,不禁感叹 Linux 内核代码真是把 C 语言应用的淋漓尽致!今天就专门对 const 的用法进行一下分析!
const的作用就不说了,地球人都知道!
(一)
直接看代码:
int a=10; a=9;
以上两句史上最简单的代码肯定编译通过!再看……
const int a 10; a=9;
再次编译就通不过了吧!因为变量a已经限定为 const 常量,不能改变a的值!
注意:
在声明一个 const 变量时可以为它赋初值!
const int a; a=10;
此时编译再次报错,不能对一个只读变量赋值!此时若输出 a 的值为一个随机值,因为变量 a 在栈中!不信你看:
const.c
int main(void)
{
const int a=10;
}
gcc –S const.c
vim const.s
.file "const.c"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $16, %esp
movl $10, -8(%ebp)
addl $16, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-48)"
.section .note.GNU-stack,"",@progbits
注意 12-13 行,at&t 的汇编代码 不用解释了吧 。。。。。。你懂的
(二)
const 在内核代码中几乎全部用在了指针变量上!让我们继续分析!
1)
int a=10; const int *p=&a; *p=9;
编译报错!指针 p 指向的变量为只读变量!作用就是限制指针对变量进行修改。
注意:指针 p 的值是可以改变的!
例如:
int a=10; int b=9; const int *p=&a; p=&b;
编译运行成功!
2)
int a=10; int b=9; int * const p=&a; p=&b;
编译失败!const 限制 指针变量 p 为只读变量,所以同样不能改变它的值!
总结一下:
C 中 const 用法很巧妙,运用 const 可以很好的检查某些变量被人为的修改!