linux中一些常见函数的含义

一.1.container_of : 根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针
例如:
有一个结构体变量,其定义如下:
struct demo_struct {
type1 member1;
type2 member2;
type3 member3;
type4 member4;
};
struct demo_struct demo;
同时,在另一个地方,获得了变量demo中的某一个域成员变量的指针,比如:
type3 *memp = get_member_pointer_from_somewhere();
此时,如果需要获取指向整个结构体变量的指针,而不仅仅只是其某一个域成员变量的指针,我们就可以这么做:
struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
这样,我们就通过一个结构体变量的一个域成员变量的指针获得了整个结构体变量的指针。

2.int down_interruptible(struct semaphore *sem)
这个函数的功能就是获得信号量,如果得不到信号量就睡眠,此时没有信号打断,那么进入睡眠。但是在睡眠过程中可能被信号打断,打断之后返回-EINTR,主要用来进程间的互斥同步。
下面是该函数的注释:
/**
* down_interruptible - acquire the semaphore unless interrupted
* @sem: the semaphore to be acquired
*
* Attempts to acquire the semaphore. If no more tasks are allowed to
* acquire the semaphore, calling this function will put the task to sleep.
* If the sleep is interrupted by a signal, this function will return -EINTR.
* If the semaphore is successfully acquired, this function returns 0.
*/
一个进程在调用down_interruptible()之后,如果sem<0,那么就进入到可中断的睡眠状态并调度其它进程运行, 但是一旦该进程收到信号,那么就会从down_interruptible函数中返回。并标记错误号为:-EINTR。一个形象的比喻:传入的信号量为1好比天亮,如果当前信号量为0,进程睡眠,直到(信号量为1)天亮才醒,但是可能中途有个闹铃(信号)把你闹醒。又如:小强下午放学回家,回家了就要开始吃饭嘛,这时就会有两种情况:情况一:饭做好了,可以开始吃;情况二:当他到厨房去的时候发现妈妈还在做,妈妈就对他说:“你先去睡会,待会做好了叫你。”小强就答应去睡会,不过又说了一句:“睡的这段时间要是小红来找我玩,你可以叫醒我。”小强就是down_interruptible,想吃饭就是获取信号量,睡觉对应这里的休眠,而小红来找我玩就是中断休眠。
使用可被中断的信号量版本的意思是,万一出现了semaphore的死锁,还有机会用ctrl+c发出软中断,让等待这个内核驱动返回的用户态进程退出。而不是把整个系统都锁住了。在休眠时,能被中断信号终止,这个进程是可以接受中断信号的!比如你在命令行中输入# sleep 10000,按下ctrl + c,就给上面的进程发送了进程终止信号。信号发送给用户空间,然后通过系统调用,会把这个信号传给递给驱动。信号只能发送给用户空间,无权直接发送给内核的,那1G的内核空间,我们是无法直接去操作的。

3.filp->private_data
struct file是字符设备驱动相关重要结构。struct file代表一个打开的文件描述符,它不是专门给驱动程序使用的,系统中每一个打开的文件在内核中都有一个关联的 struct file。 它由内核在 open时创建,并传递给在文件上操
作的任何函数,知道最后关闭。当文件的所有实例都关闭之后,内核释放这个数据结构。
在 struct filed有个成员void *private_data;文档上说明该成员是系统调用时保存状态信息非常有用的资源。起初一直不明白这个private_data在驱动 open函数中的作用,后来发现private_data 这个成员在open函数被调用的时候 linux 系统就已经将其幅值为NULL,之后可供用户使用,或者比较悲剧的被用户忽略改域。
在详细的阅读源代码后,发现 这个private_data 其实是用来保存自定义设备结构体的地址的。自定义结构体的地址被保存在private_data后,可以在read ,write 等驱动函数中被传递和调用自定义设备结构体中的成员。
例如 可以在open函数中这么做
struct scull_dev *dev;
dev = container_of(inode->i_cdev,struct scull_dev,cdev);
filp->private_data = dev; /for other methods/
(container_of这个宏返回的是地址,即结构体的地址)
也可以使用C语言中的一些技巧实现地址的赋值
struct s3c2440_camif *dev =&camif;
file->private_data = dev;

4.PAGE_ALIGN:将物理地址addr修整为页边界地址(页的上边界)
define PAGE_ALIGN(addr) (((addr)+PAGE_SIZE-1)&PAGE_MASK)
|————|<– PAGE_ALGN(addr)
| |
| |
| |
| |<– addr
| |
| |
| |
| |
|————|
one page(4K)
define PAGE_SHIFT 12
define PAGE_SIZE (1UL << PAGE_SHIFT)
define PAGE_MASK (~(PAGE_SIZE-1))

PAGE_MASK = ~(1 0000 0000 0000 - 1) = ~(1111 1111 1111) = 0000 0000 0000
PAGE_SIZE = 1 0000 0000 0000 = 2^12 = 4K

如addr为0x22000001 。。PAGE_ALIGN(addr)=(0x22000001+4096-1)&0xfffff000
=(0x22000001+0xfff)&0xfffff000=0x22001000&0xfffff000
=0x22001000;
同样,比如addr为0x22000003,PAGE_ALIGN(addr)后仍然是0x22001000;
就是和下一个页对齐,一个页为4k。起始地址为0x xxxxx 000;

5.int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
该函数的功能是创建页表。其中参数vma是内核根据用户的请求自己填写的,而参数addr表示内存映射开始处的虚拟地址,因此,该函数为addr~addr+size之间的虚拟地址构造页表。
另外,pfn(Page Fram Number)是虚拟地址应该映射到的物理地址的页面号,实际上就是物理地址右移PAGE_SHIFT位。如果PAGE_SHIFT为4kb,则PAGE_SHIFT为12,因为PAGE_SHIFT等于1<< PAGE_SHIFT。最后一个参数prot是新页所要求的保护属性。在驱动程序中,一般能使用remap_pfn_range()映射内存中的保留页(如X86系统中的640KB~1MB区域)和设备I/O内存。因此,如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。
下面是struct vm_area_struct结构体的定义:
QUOTE:
/*
* This struct defines a memory VMM memory area. There is color: black; background-color: #a0ffff;”>vm_area_struct {
struct mm_struct * vm_mm; /* VM area parameters */
unsigned long vm_start;
unsigned long vm_end;
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned long vm_flags;
/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;
/* For areas with an address space and backing store,
* font-size: 10px;”>vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /* offset in PAGE_SIZE units, not PAGE_CACHE_SIZE */
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; /* was vm_pte (shared mem) */
};
vm_area_struct结构所描述的虚存空间以vm_start、vm_end成员表示,它们分别保存了该虚存空间的首地址和末地址后第一个字节的地址,以字节为单位,所以虚存空间范围可以用[vm_start, vm_end)表示。
  通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。
  假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。
  一个程序可以选择MAP_SHARED或MAP_PRIVATE共享模式将一个文件的某部分数据映射到自己的虚存空间里面。这两种映射方式的区别在于:MAP_SHARED映射后在内存中对该虚存空间的数据进行修改会影响到其他以同样方式映射该部分数据的进程,并且该修改还会被写回文件里面去,也就是这些进程实际上是在共用这些数据。而MAP_PRIVATE映射后对该虚存空间的数据进行修改不会影响到其他进程,也不会被写入文件中。
  来自不同进程,所有映射同一个文件的vm_area_struct结构都会根据其共享模式分别组织成两个链表。链表的链头分别是:vm_file->f_dentry->d_inode->i_mapping->i_mmap_shared, vm_file->f_dentry->d_inode->i_mapping->i_mmap。而vm_area_struct结构中的vm_next_share指向链表中的下一个节点;vm_pprev_share是一个指针的指针,它的值是链表中上一个节点(头节点)结构的vm_next_share(i_mmap_shared或i_mmap)的地址。
  进程建立vm_area_struct结构后,只是说明进程可以访问这个虚存空间,但有可能还没有分配相应的物理页面并建立好页面映射。在这种情况下,若是进程执行中有指令需要访问该虚存空间中的内存,便会产生一次缺页异常。这时候,就需要通过vm_area_struct结构里面的vm_ops->nopage所指向的函数来将产生缺页异常的地址对应的文件数据读取出来。
  vm_flags主要保存了进程对该虚存空间的访问权限,然后还有一些其他的属性。vm_page_prot是新映射的物理页面的页表项pgprot的默认值。
例子:
QUOTE:
int memdev_mmap(struct file *filp, struct vm_area_struct *vma){
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
if(remap_pfn_range(vma, vma->vm->start, virt_to_phys(dev->data)>>PAGE_SHIFT, size, vma->vm_page_prot))
return -EAGAIN;
return 0;
}

6.writel() 往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节)。
原型:
void writel (unsigned char data , unsigned short addr )
readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )。
原型:
unsigned char readl (unsigned int addr )
变量 addr 是 I/O 地址。
返回值 : 从 I/O 空间读取的数值。
定义
**#define readb __raw_readb
**#define readw(addr) __le16_to_cpu(__raw_readw(addr))
**#define readl(addr) __le32_to_cpu(__raw_readl(addr))
**#ifndef __raw_readb
static inline u8 __raw_readb(const volatile void __iomem *addr)
{
return (const volatile u8 __force ) addr;
}
**#endif
**#ifndef __raw_readw
static inline u16 __raw_readw(const volatile void __iomem *addr)
{
return (const volatile u16 __force ) addr;
}
**#endif
**#ifndef __raw_readl
static inline u32 __raw_readl(const volatile void __iomem *addr)
{
return (const volatile u32 __force ) addr;
}
**#endif
**#define writeb __raw_writeb
**#define writew(b,addr) __raw_writew(__cpu_to_le16(b),addr)
**#define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)

8.virt_to_page
宏 virt_to_page 从一个内核虚地址得到该页的描述结构 struct page *。所有物理内存都
由一个 memmap 数组来描述。这个宏就是通过计算给定地址的物理页在这个数组中的位
置。另外这个文件也定义了一个简单的宏检查一个页是不是合法:VALID_PAGE(page)。
如果 page 离 memmap 数组的开始太远以至于超过了最大物理页面应有的距离则是不合
法的。
但页表项的定义也放在这里。有 pgd_t,pmd_t,pte_t 和存取它们值的宏 pte_val等

9.ClearPageReserved
ClearPageReserved清除了该页面flag中的reserved标志,表示该页面属于动态内存
10.copy_to_user和copy_from_user函数的使用说明

Linux操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,
因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,
对应的数据可能不在内存中。用户空间的内存映射采用段页式,而内核空间有自己的规则;本次主要的讨论
linux运行的内用空间与内核空间进行数据传递(主要是应用在linux的驱动程序中)常用函数copy_to_user
和copy_from_user。下面是对这两个函数进行的详解

1.函数copy_to_user的函数原型
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (likely(access_ok(VERIFY_WRITE, to, n)))
n = __copy_to_user(to, from, n);
return n;
}
参数详解:
参数1( void __user *to): 拷贝内核空间的地址指针
参数2(const void *from): 用户空间的地址指针
参数3(unsigned long n): 内核拷贝到用户空间的字节数
返回值: 成功返回 0,失败是返回还没有拷贝的字节数

2.copy_from_user 函数
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (likely(access_ok(VERIFY_READ, from, n)))
n = __copy_from_user(to, from, n);
else
memset(to, 0, n);
return n;
}
参数详解:
参数1( void __user *to): 拷贝内核空间的地址指针
参数2(const void *from): 用户空间的地址指针
参数3(unsigned long n): 用户空间到内核空间的字节数
返回值: 成功返回 0,失败是返回还没有拷贝的字节数
总结:在两个函数中,都有对地址空间的有效性进行了检测。

11.dma_alloc_coherent() – 给DMA池分配物理页
dma_alloc_coherent() – 获取物理页,并将该物理页的总线地址保存于dma_handle,返回该物理页的虚拟地址
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp){
void *ret;
if (!dev || *dev->dma_mask >= 0xffffffffUL)
gfp &= ~GFP_DMA;
ret = (void *)__get_free_pages(gfp, get_order(size)); //(1)
if (ret) {
memset(ret, 0, size);
*dma_handle = virt_to_bus(ret); //(2)
}
return ret;
}
(1) 将size转换成order, 即2^order
(2) 将虚拟地址ret转换成总线地址
mprotect: 设置内存访问权限
mmap 的第三个参数指定对内存区域的保护,由标记读、写、执行权限的 PROT_READ、PROT_WRITE 和 PROT_EXEC 按位与操作获得,或者是限制没有访问权限的 PROT_NONE。如果程序尝试在不允许这些权限的本地内存上操作,它将被 SIGSEGV 信号(Segmentation fault,段错误)终止。
在内存映射完成后,这些权限仍可以被 mprotect 系统调用所修改。mprotect 的参数分别为内存区间的地址,区间的大小,新的保护标志设置。所指定的内存区间必须包含整个页:区间地址必须和整个系统页大小对齐,而区间长度必须是页大小的整数倍。这些页的保护标记被这里指定的新保护模式替换。
获得页面对齐的内存
应注意的是, malloc 返回的内存区域通常并不与内存页面对齐,甚至在内存的大小是页大小整数倍的情况下也一样。如果您想保护从 malloc 获得的内存,您不得不分配一个更大的内存区域并在其中找到一个与页对齐的区间。
您可以选择使用 mmap 系统调用来绕过 malloc 并直接从 Linux 内核中分配页面对齐内存。
mmap通过映射 /dev/zero 来分配内存页。内存将被初始化为可读和可写模式。
int fd = open (“/dev/zero”, O_RDONLY);
char* memory = mmap (NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
close (fd);
然后,您的程序可以使用 mprotect 把它变成只读:
mprotect (memory, page_size, PROT_READ);
有一种监控内存访问的高级技巧,可以通过利用 mmap 和 mprotect 保护目标内存区间,然后当程序访问时候接收并处理 Linux 系统发送的 SIGSEGV 信号。代码 展示了这个技巧。
代码使用mprotect检测内存访问
include

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值