常见内核函数

fls 函数

fls 函数可以查找到一个数中最低的1位,具体参考注释中的note说明。

/**
 * fls - find last (most-significant) bit set
 * @x: the word to search
 *
 * This is defined the same way as ffs.
 * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
 */

static __always_inline int fls(unsigned int x)

local_irq_disable 和local_irq_enable

local_irq_disable() 禁用当前cpu 中断
local_irq_enable() 使能当前cpu 中断

preempt_disable 和preempt_enable

void preempt_disable(void) 禁用内核抢占(禁用抢占后cpu 将不会调度到其他线程)
void preempt_enable(void) 使能内核抢占

try_module_get和module_put

bool try_module_get(struct module *module) 增加模块的引用计数(module->refcnt++),返回1 表示成功。
void module_put(struct module *module) 减少模块引用计数(module->refcnt–)

内核态文件读写

内核高精度定时器使用 hrtimer

高精度定时器hrtimer 实现机制

kthread_run

内核线程创建宏,该宏调用kthread_create 实现线程创建。

/**
 * kthread_run - create and wake a thread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#define kthread_run(threadfn, data, namefmt, ...)                          \
({                                                                         \
        struct task_struct *__k                                            \
                = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
        if (!IS_ERR(__k))                                                  \
                wake_up_process(__k);                                      \
        __k;                                                               \
})

threadfn: 创建线程后需要执行的函数。
data: 传给threadfn 的参数,可以为NULL。
namefmt: 线程名字。
返回: struct task_struct 用来描述一个线程。
使用例子:pThread = kthread_run(LED_Thread, NULL, “ledThread”);
创建完成后ps 命令可以查看到该线程。
在这里插入图片描述

proc_create

很多时候我们需要查看内核中的一些 属性/数据,就可以在 “/proc” 创建虚拟文件,例如cmdline。
在这里插入图片描述
proc_create 函数可以在"/proc" 下创建一个虚拟文件。

struct proc_dir_entry *proc_create(
	const char *name, umode_t mode, struct proc_dir_entry *parent,
	const struct file_operations *proc_fops);
//proc_dir_entry 可以描述一个文件条目
//当返回值为 NULL 时创建失败

//删除一个/proc 虚拟文件,proc_dir_entry 创建时返回的指针
void proc_remove(struct proc_dir_entry *);

cmdline 参考代码(fs/proc/cmdline.c)
最重要的参数就是proc_fops (struct file_operations),open、read、write 等等函数可以根据需要自由发挥(甚至可以不写open)
在这里插入图片描述

原子位操作

原子操作中的位操作部分函数如下:
void set_bit(int nr, void *addr) 原子设置addr所指的第nr位
void clear_bit(int nr, void *addr) 原子的清空所指对象的第nr位
void change_bit(nr, void *addr) 原子的翻转addr所指的第nr位
int test_bit(nr, void *addr) 原子的返回addr位所指对象nr位
int test_and_set_bit(nr, void *addr) 原子设置addr所指对象的第nr位,并返回原先的值
int test_and_clear_bit(nr, void *addr) 原子清空addr所指对象的第nr位,并返回原先的值
int test_and_change_bit(nr, void *addr) 原子翻转addr所指对象的第nr位,并返回原先的值

unsigned long word = 0;

set_bit(0, &word); /*第0位被设置*/

set_bit(1, &word); /*第1位被设置*/

clear_bit(1, &word); /*第1位被清空*/

change_bit(0, &word); /*翻转第0位*/

Linux内核内存申请的三种方式(kmalloc,kzalloc,vmalloc)

Linux 大小端读写函数

内核 ARM64 大小端读写函数定义在:linux-5.4.47\linux-5.4.47\arch\arm64\include\asm\io.h
小端读写:

#define readb_relaxed(c)	({ u8  __r = __raw_readb(c); __r; })
#define readw_relaxed(c)	({ u16 __r = le16_to_cpu((__force __le16)__raw_readw(c)); __r; })
#define readl_relaxed(c)	({ u32 __r = le32_to_cpu((__force __le32)__raw_readl(c)); __r; })
#define readq_relaxed(c)	({ u64 __r = le64_to_cpu((__force __le64)__raw_readq(c)); __r; })

#define writeb_relaxed(v,c)	((void)__raw_writeb((v),(c)))
#define writew_relaxed(v,c)	((void)__raw_writew((__force u16)cpu_to_le16(v),(c)))
#define writel_relaxed(v,c)	((void)__raw_writel((__force u32)cpu_to_le32(v),(c)))
#define writeq_relaxed(v,c)	((void)__raw_writeq((__force u64)cpu_to_le64(v),(c)))

#define readb(c)		({ u8  __v = readb_relaxed(c); __iormb(__v); __v; })
#define readw(c)		({ u16 __v = readw_relaxed(c); __iormb(__v); __v; })
#define readl(c)		({ u32 __v = readl_relaxed(c); __iormb(__v); __v; })
#define readq(c)		({ u64 __v = readq_relaxed(c); __iormb(__v); __v; })

#define writeb(v,c)		({ __iowmb(); writeb_relaxed((v),(c)); })
#define writew(v,c)		({ __iowmb(); writew_relaxed((v),(c)); })
#define writel(v,c)		({ __iowmb(); writel_relaxed((v),(c)); })
#define writeq(v,c)		({ __iowmb(); writeq_relaxed((v),(c)); })

大端读写:

#define ioread16be(p)		({ __u16 __v = be16_to_cpu((__force __be16)__raw_readw(p)); __iormb(__v); __v; })
#define ioread32be(p)		({ __u32 __v = be32_to_cpu((__force __be32)__raw_readl(p)); __iormb(__v); __v; })
#define ioread64be(p)		({ __u64 __v = be64_to_cpu((__force __be64)__raw_readq(p)); __iormb(__v); __v; })

#define iowrite16be(v,p)	({ __iowmb(); __raw_writew((__force __u16)cpu_to_be16(v), p); })
#define iowrite32be(v,p)	({ __iowmb(); __raw_writel((__force __u32)cpu_to_be32(v), p); })
#define iowrite64be(v,p)	({ __iowmb(); __raw_writeq((__force __u64)cpu_to_be64(v), p); })

__read_mostly

kmemdup

list_splice_init

将list 链表插入head 链表中

static inline void list_splice_init(struct list_head *list,
				    struct list_head *head)
{
	if (!list_empty(list)) {
		__list_splice(list, head, head->next);
		INIT_LIST_HEAD(list);
	}
}

of_count_phandle_with_args

获取handle 中元素的个数。

init_completion

初始化完成量 completion,用于一个执行单元等待另一个执行单元执行完某事。例如spi_sync 同步传输函数中,需要等待spi 传输完成才会返回。(completion 的实现是依赖休眠、唤醒机制)

spin_lock_irqsave

中断中使用自旋锁。

PTR_TO_UINT

PTR_TO_UINT:指针类型转化为 uint类型

for_each_child_of_node

遍历设备树节点parent 下所有子节点。

#define for_each_child_of_node(parent, child) \
	for (child = of_get_next_child(parent, NULL); child != NULL; \
	     child = of_get_next_child(parent, child))

for_each_child_of_node(fm_node, dev_node) {		//遍历fman 下所有子节点
	......
}

of_clk_get

获取时钟。

clocks = <&clockgen 3 0>;

struct clk *of_clk_get(struct device_node *np, int index)
unsigned long clk_get_rate(struct clk *clk)	//获取时钟速率

of_irq_to_resource

of_irq_to_resource 从设备树获取irq。
dev:设备树节点。
index:下标,指定要获取第几个irq。
resource :指向存放获取到资源地址的指针,此处为NULL。
返回值:失败返回0,否则返回irq。

//设备树
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>,
             <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
//原型
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
//实例
err_irq = of_irq_to_resource(fm_node, 1, NULL);

of_address_to_resource

of_address_to_resource 获取外设寄存器的物理地址。
dev:设备树节点。
index:下标,指定要获取第几个irq。
resource :指向存放获取到资源地址的指针,这里是reg的物理地址范围。
返回值:返回<0 时为失败。

//设备树
reg = <0x0 0x1a00000 0x0 0xfe000>;
//原型
int of_address_to_resource(struct device_node *dev, int index,
			   struct resource *r)
//实例
_errno = of_address_to_resource(fm_node, 0, &res);	//获取fman 物理地址
if (unlikely(_errno < 0)) {
    ......
}

大小端转换函数 be32_to_cpu

在kernel里面经常能看见把十六进制数用下面几个函数转换一下,比如be32_to_cpu, cpu_to_be32, cpu_to_le16,cpu_to_le32等.其实很简单.

名词解释
le叫做little endian, be叫做big endian,这是两种字节序,分别称为小段和大端.
le表示地址低为存储值的低位,地址高位存储值的高位.
be表示地址低位存储值的高位,地址高位存储值的低位.

不用cpu使用了不同的字节序,比如PowerPC系列cpu就用了大端模式,而ARM和x86就用的小端模式.
因此,对于不用的cpu,上面几个函数的执行结果也是不一样的.
但是,凡是xx_to_cpu就说明结果是给cpu使用的.反之,cpu_to_xx就说明从cpu的字节序转换成目标字节序.
如果cpu本身就是小端模式,那么cput_to_le32这类函数就会do nothing.

per_cpu

在Linux操作系统中,特别是针对SMP或者NUMA架构的多CPU系统的时候,描述每个CPU的私有数据的时候,Linux操作系统提供了per_cpu机制。per_cpu机制就是让每个CPU都有自己的私有数据段,便于保护与访问。

per_cpu:获取指定cpu私有数据变量。通过per_cpu(var, cpu)宏来获取,cpu是int型,代表CPU index;var代表将要访问的CPU的那一份数据(变量类型)。

struct softnet_data *sd = &per_cpu(softnet_data, i);

例如上面代码中使用per_cpu 获取指定cpu 的网络软中断数据softnet_data。

INIT_LIST_HEAD

offsetof

offsetof 是一个宏,它可以计算出member 成员在type 结构体类型的偏移地址。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

(TYPE *)0 把0强转成指针,它的值是0x0, 0x0->MEMBER 就是 0x0 + member 偏移地址; 这点在汇编中也是这么计算的可以使用gdb来查看内核的反汇编代码和结构体的偏移。
例如 offsetof(typeof(netdev) ,irq) 就可以得到irq 的偏移地址64。

(gdb) ptype /o struct net_device
/* offset | size / type = struct net_device {
/
0 | 16 / char name[16];
/
16 | 16 / struct hlist_node {
/
16 | 8 */ struct hlist_node next;
/
24 | 8 */ struct hlist_node **pprev;

                           /* total size (bytes):   16 */
                       } name_hlist;

/* 32 | 8 */ struct dev_ifalias ifalias;
/
40 | 8 / unsigned long mem_end;
/
48 | 8 / unsigned long mem_start;
/
56 | 8 / unsigned long base_addr;
/
64 | 4 */ int irq;

**typeof : ** 使用typeof() 可以返回该结构体的结构体类型,传入的参数是结构体,而不是结构体指针。

container_of简介

#define container_of(ptr, type, member) \
    (type *)((char *)(ptr) - (char *) &((type *)0)->member)

container_of(to_mdio_device(d), struct phy_device, mdio) 可以得到phy_device 结构体的地址。
该函数的意义是得到type 类型的结构体首地址(即得到该结构体)。

原理很简单: 已知结构体type的成员member的地址ptr,求解结构体type的起始地址。
type的起始地址 = ptr(member地址) - offset (member偏移量)。 size 不用计较它如何得知,我们只要知道该函数会返回member成员所在结构体的首地址就好了。

linux内核中likely与unlikely

likely和unlikely
参考/include/linux/compiler.h */

# define likely(x)  __builtin_expect(!!(x), 1)
# define unlikely(x)    __builtin_expect(!!(x), 0)

上述源码中采用了内建函数__builtin_expect来进行定义,即 built in function。
  __builtin_expect的函数原型为long __builtin_expect (long exp, long c),返回值为完整表达式exp的值,它的作用是期望表达式exp的值等于c(如果exp == c条件成立的机会占绝大多数,那么性能将会得到提升,否则性能反而会下降)。注意, __builtin_expect (lexp, c)的返回值仍是exp值本身,并不会改变exp的值。
  __builtin_expect函数用来引导gcc进行条件分支预测。在一条指令执行时,由于流水线的作用,CPU可以同时完成下一条指令的取指,这样可以提高CPU的利用率。在执行条件分支指令时,CPU也会预取下一条执行,但是如果条件分支的结果为跳转到了其他指令,那CPU预取的下一条指令就没用了,这样就降低了流水线的效率。
  另外,跳转指令相对于顺序执行的指令会多消耗CPU时间,如果可以尽可能不执行跳转,也可以提高CPU性能。
  简单从表面上看if(likely(value)) == if(value),if(unlikely(value)) == if(value)。
也就是likely和unlikely是一样的,但是实际上执行是不同的,加likely的意思是value的值为真的可能性更大一些,那么执行if的机会大,而unlikely表示value的值为假的可能性大一些,执行else机会大一些。
  加上这种修饰,编译成二进制代码时likely使得if后面的执行语句紧跟着前面的程序,unlikely使得else后面的语句紧跟着前面的程序,这样就会被cache预读取,增加程序的执行速度。
  那么上述定义中为什么要使用!!符号呢?
  两次 !! 翻转非逻辑值(比如1、2、3等数值,或某个指针等等)转化位逻辑值0或1,方便比较。
  计算机中bool逻辑只有0和1,非0即是1,当likely(x)中参数不是逻辑值时,就可以使用!!符号转化为逻辑值1或0 。比如:!!(3)=!(!(3))=!0=1,这样就把参数3转化为逻辑1了。
  那么简单理解就是:
  likely(x)代表x是逻辑真(1)的可能性比较大;
  unlikely(x)代表x是逻辑假(0)的可能性比较大。

preempt_disable 和preempt_enable

preempt_disable 在内核进程上锁前,为了防止抢占发生死锁,需要关闭内核抢占。
preempt_enable 解锁后使能抢占
上锁前需要关闭内核抢占的原因

ARRAY_SIZE

ARRAY_SIZE :求一个数组中元素的个数。sizeof(arr)为整个数组的大小,sizeof((arr)[0])) 为其中一个元素的大小,相除及得到数组元素的个数。

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

ALIGN

先说ALIGN的用法,ALIGN(x,a) 是为了使x以a为边界对齐,实现原理是给x加上一个最小的数,使x以a为边界对齐。举个例子,a = 8, x=0, ALIGN(x,a) 运算结果为0; a = 8, x = 3, 运算结果为8; a = 8, x = 11, 运算结果为16。

#define ALIGN(x, a)	(((x) + (a) - 1) & ~((a) - 1))

PTR_ALIGN

地址p 以a 为字节对齐,并返回p 类型的地址。
typeof§ 获取p的数据类型。

#define PTR_ALIGN(p, a)		((typeof(p))ALIGN((unsigned long)(p), (a)))

__setup

uboot bootargs 向内核传递参数,内核通过__setup 设置。

#define __setup_param(str, unique_id, fn, early)			\
	static const char __setup_str_##unique_id[] __initconst		\
		__aligned(1) = str; 					\
	static struct obs_kernel_param __setup_##unique_id		\
		__used __section(.init.setup)				\
		__attribute__((aligned((sizeof(long)))))		\
		= { __setup_str_##unique_id, fn, early }

#define __setup(str, fn)						\
	__setup_param(str, fn, fn, 0)

下面是再bootargs 中关闭pcie 电源管理
bootargs=console=ttyS0,115200 earlycon=uart8250,mmio,0x21c0500 root=/dev/mmcblk0p2 cma=64M rw rootwait pcie_port_pm=off

static int __init pcie_port_pm_setup(char *str)
{
	if (!strcmp(str, "off"))
		pci_bridge_d3_disable = true;
	else if (!strcmp(str, "force"))
		pci_bridge_d3_force = true;
	return 1;
}
__setup("pcie_port_pm=", pcie_port_pm_setup);

net_ratelimit

net_ratelimit 限制内核打印时间,当返回ture时 表示可以打印。
net_ratelimit()用于保护内核网络调试信息的打印, 当它返回(TRUE)时则可以打印调试信息,返回零则禁止信息打印. 它的特性为当"极快地"调用net_ratelimit()时,它最多只允许连续打印前10条信息, 后继信息每隔5秒允许打印一次.
net/core/utils.c:

int net_msg_cost = 5HZ; /在拥塞时, 每条网络消息记录所间隔的时间/
  int net_msg_burst = 10
5*HZ; /连续记录网络突发消息的间隔(最多连续记录10条消息)/

of_phy_find_device

内核的rcu 机制

RCU锁机制原理解析
深入理解 Linux 的 RCU 机制

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
\初级驱动\drivers_实验资料\drivers_实验资料\kernel课程\内核课程.chm 内核源码下载和结构; 目录结构: Arch: 架构:arm x86 ppc mips :Arch/arm/ lib:存放体系结构特有的对诸如strlen和memcpy之类的通用函数的实现。与arch/*/lib下的代码不同,这里的库代码都是使用C编写的,在内核新的移植版本中可以直接使用。arch/*/lib就是跟具体架构相关的函数,大部分都是汇编代码 mm:存放体系结构特有的内存管理程序的实现。 Drivers:这个目录是内核中最庞大的一个目录,显卡、网卡、SCSI适配器、PCI总线、USB总线和其他任何Linux支持的外围设备或总线的驱动程序都可以在这里找到,驱动一般分成字符设备和块设备,但是现在内核把驱动做成了很多子系统,比如input子系统,mtd子系统,mmc子系统,media子系统等等 Init:内核的启动初始化代码。包括main.c、创建早期用户空间的代码以及其他初始化代码。 Kernel:内核中最核心的部分,包括进程的调度(kernel/sched.c),以及进程的创建和撤销(kernel/fork.c和kernel/exit.c)等,和平台相关的另外一部分核心的代码在arch/*/kernel目录。 Crypto:内核本身所用的加密API,实现了常用的加密和散列算法,还有一些压缩和CRC校验算法。 Usr:实现了用于打包和压缩的的cpio等 Virt:允许使用其他操作系统,然后在宿主 Linux 内核的用户空间中运行内核中的 KVM ,通过/dev/kvm字符设备来公开虚拟化后的硬件 Documentation:非常有用的目录,内核文档,可以帮助我们理解内核中的一些技术 fs: 文件系统的代码,包含Linux常见的文件系统格式,比如ext3,ext4,cramfs,jffs2,如果想让内核支持更多的文件系统格式,那么可以专注到这个目录 ipc:进程间通信的实现代码,比如共享内存,消息队列,信号量,主要是system V的,其他的就没有了 net: 大部分的网络协议代码就在这里了,比如IPv4,IPv6,tcp,udp,ieee80211,域套接字

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值