返回所有文章
学习 / TSENGCHEN BLOG

C语言基础

01_Learning/C · C语言基础

01_Learning · 第 1 / 2 篇

保留原笔记正文与技术内容,仅脱敏敏感信息。配图按作者要求保留原样。

位运算

进制转换与位编号

位编号从0开始

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0000 1000 //表示 bit3 = 1

对应:

1U << 3

十六进制:

0x08

十六进制与二进制的关系

每一个十六进制数字对应4个二进制位: 例如:

0x4c = 0100 1100

几种基础位运算

按位与:&

规则:

1 & 1 = 1
其他情况 = 0
  • 判断某个位
if(isr & TX_FINISH)

如果:

isr       = 0010 0010
TX_FINISH = 0000 0010
result    = 0000 0010

结果非零,条件成立

  • 保留指定字段
id & CAN_TX_ID_MASK

掩码中为1的位被保留,为0的位被清除。

按位或:|

规则:

有一个为1,结果就是1

主要用于:

  • 设置某个位
val |= WORK_MODE;
  • 同时设置多个位
 val |= WORK_MODE | MODE_AUTO_RETX;
  • 组合多个标志
 err_int = ERR_WARN_INT |
		   RX_BUF_OV|
		   PASSIVE_ERR|
		   TX_LOSTARB|
		   BUS_ERR_INT;

按位取反:~

将每一位取反:

0 -> 1
1 -> 0

主要用于生成清楚掩码

fi &= ~CAN_DLC_MASK;

假设:

CAN_DLC_MASK = 0000 1111;
~CAN_DLC_MASK = 1111 0000;

标准写法

val &= ~MASK;

按位异或: ^

规则:

相同为0 ,不同为1
  • 翻转某个位
val ^= BIT(3);

详细解释:

val ^= BIT(3);

等价于:

val = val ^ 0000 1000

bit3 原来为 0:

  0000 0000
^ 0000 1000
-----------
  0000 1000

bit3:0 → 1 bit3 原来为 1:

  0000 1000
^ 0000 1000
-----------
  0000 0000

bit3:1 → 0 结论:某一位与 1 异或,该位翻转;与 0 异或,该位不变。

左移: <<

将数据向高位移动:

1U << 3;

结果:

0000 0001
->
0000 1000

主要用于:

  • 生成单个位
  • 将字段值放入寄存器指定的位置
(bt->sjw - 1) << BT_SJW_SHIFT;

其中:

#define BT_SJW_SHIFT 14

将表示将 SJW 字段放到 bit15:14

右移: >>

将数据向低位移动。 驱动中主要用于:

  • 将寄存器字段移动到低位
  • 数值换算 例如:
(ecc & ERR_TYPE_MASK) >> ERR_TYPE_SHIFT

表示:

  1. 先保留 bit24:22
  2. 再右移 22 位
  3. 得到普通数值 0~7 CAN 驱动中还有:
(bt->brp >> 1) - 1

这里不是字段提取,而是硬件规定的 BRP 编码换算。

Linux操作宏

BIT(n) 生成单个位

相当于

#define BIT(nr) (1UL << (nr))

例如:

BIT(0)   // 0x00000001
BIT(2)   // 0x00000004
BIT(10)  // 0x00000400

驱动示例

#define WORK_MODE       BIT(0)
#define SELF_TEST_EN    BIT(2)
#define MODE_AUTO_RETX  BIT(10)

GENMASK(h, 1) 生成连续位掩码

例如:

GENMASK(15,14);

生成:

bit15:14 为 1
其他位置为0

即

0000 0000 0000 0000 1100 0000 0000 0000

驱动实例:

#define BT_SJW_MASK   GENMASK(15, 14)
#define BT_BRP_MASK   GENMASK(13, 8)
#define BT_TSEG2_MASK GENMASK(6, 4)
#define BT_TSEG1_MASK GENMASK(3, 0)

FILED_GET(mask, reg) 提取字段

传统写法:

field = (reg & MASK) >> SHIFT;

KERNEL内核:

field = FIELD_GET(MASK, reg);

驱动实例:

switch((ecc & ERR_TYPE_MASK) >> ERR_TYPE_SHIFT)

可以改写成:

 switch(FIELD_GET(GET_TYPE_MASK, ecc))

定义:

#define ERR_TYPE_NASK GETMASK(24,22)

FILED_GET()会自动根据mask计算右移位值

FILED_PREP(mask,value) 构造字段

传统写法:

field = (value << SHIFT) & MASK;

Kernel:

field = FIELD_PREP(mask, value);

例如CAN时序

cfg = FIELD_PREP(BT_SJW_MASK, bt->sjw - 1) |
      FIELD_PREP(BT_BRP_MASK, (bt->brp >> 1) - 1) |
      FIELD_PREP(BT_TSEG2_MASK, bt->phase_seg2 - 1) |
      FIELD_PREP(BT_TSEG1_MASK,
                 bt->prop_seg + bt->phase_seg1 - 1);

相比直接移位,它的优点是:

  • 不需要手动写 shift
  • 字段位置更加清晰
  • 自动限制到 mask 范围
  • 不容易把偏移写错

FIELD_MAX(mask) 字段最大值

例如:

FIELD_MAX(GENMASK(3, 1))

字段宽度为 3 位,最大值为:

0b111 = 7

适合参数范围检查。

FIELD_FIT(mask, value):判断数值能否放入字段

例如:

if (!FIELD_FIT(BT_TSEG2_MASK, value))  
    return -EINVAL;

若字段为 3 位,则合法范围为:

0~7

在成熟驱动里,参数通常已经由上层框架校验,所以不一定每次都显式使用。

驱动中常见位操作

CAN驱动举例 rockchip_can.c

设置单个位

val |= FLAG;

CAN:

val |= WORK_MODE;

同时设置多个值

val |= FLAG_A | FLAG_B

CAN驱动:

val = readl(rcan->base + CAN_MODE);
val |= WORK_MODE | MODE_AUTO_RETX;
writel(val, rcan->base + CAN_MODE);

示例:

FLAG_A = BIT(0)      // 0000 0001
FLAG_B = BIT(3)      // 0000 1000

先合并:

  FLAG_A  0000 0001
| FLAG_B  0000 1000
-------------------
          0000 1001

然后设置寄存器:

  val     0010 0000
		| 0000 1001
		-----------
          0010 1001

清除单个位

val &= ~FLAG;

例如关闭某功能

val &= ~WORK_MODE;

假设寄存器当前:

val = 1010 1011

执行:

  val        1010 1011
& ~WORK_MODE 1111 1110
----------------------
             1010 1010

结果:

bit0: 1 → 0

其他位:
保持不变

清除连续字段

val &= ~MASK;

CAN 驱动:

fi &= ~CAN_DLC_MASK;

定义:

#define CAN_DLC_MASK GENMASK(3, 0)

作用:清除 DLC 的低 4 位。

判断是否为1

if(reg & FLAG)

CAN驱动:

if (isr & TX_FINISH)
if (isr & RX_FINISH)
if (sta_reg & BUS_OFF)
if (fi & CAN_RTR)

判断结果是“是否非零”,不是必须等于 1。 错误写法:

if ((isr & TX_FINISH) == 1)

TX_FINISH 是 BIT(1),结果可能为 0x2。

判断位是否为 0

if (!(reg & FLAG))

CAN 驱动:

if ((ecc & ERR_DIR_RX) == 0)  
    cf->data[2] |= CAN_ERR_PROT_TX;

也可以写成:

if (!(ecc & ERR_DIR_RX))

含义:

ERR_DIR_RX = 0  

错误发生在发送方向

判断多个标志的任意一个

if(reg & mask)

CAN驱动:

u8 err_int = ERR_WARN_INT |
             RX_BUF_OV |
             PASSIVE_ERR |
             TX_LOSTARB |
             BUS_ERR_INT;

if (isr & err_int)
    rockchip_can_err(ndev, isr);

判断多个标志是否全部存在

if ((reg & mask) == mask)

例如:

u32 mask = STATUS_READY | STATUS_DONE;

if ((status & mask) == mask)  
    /* READY 和 DONE 都为 1 */

区别:

if (status & mask)

表示至少一个位存在。

if ((status & mask) == mask)

表示所有目标位都存在。

过滤有效位

val &= MASK;

CAN驱动:

writel(id & CAN_TX_ID_MASK, rcan->base + CAN_TX_ID);

定义:

#define CAN_TX_ID_MASK 0x1fffffff

0x1fffffff 表示低 29 位有效。 Linux 的 can_id 还可能包含:

CAN_EFF_FLAG  
CAN_RTR_FLAG  
CAN_ERR_FLAG

这些软件标志不能直接写入硬件 CAN ID 寄存器,因此需要:

id & CAN_TX_ID_MASK

只保留实际 CAN ID。

提取低位字段

字段位于最低位时,可以直接:

field = reg & MASK;

CAN 驱动:

cf->can_dlc = can_cc_dlc2len(fi & CAN_DLC_MASK);

因为:

#define CAN_DLC_MASK GENMASK(3, 0)

DLC 已经位于最低位,不需要右移。 也可以写成:

FIELD_GET(CAN_DLC_MASK, fi)

模式 11:提取非低位字段

传统写法:

field = (reg & MASK) >> SHIFT;

CAN 驱动:

switch ((ecc & ERR_TYPE_MASK) >> ERR_TYPE_SHIFT)

定义:

#define ERR_TYPE_MASK  GENMASK(24, 22)  
#define ERR_TYPE_SHIFT 22

过程:

ecc  
 ↓ 与 ERR_TYPE_MASK  
只保留 bit24:22  
 ↓ 右移 22 位  
得到 0~7

推荐:

switch (FIELD_GET(ERR_TYPE_MASK, ecc))

构造完整寄存器

CAN位时序:

cfg = ((bt->sjw - 1) << BT_SJW_SHIFT) |
      (((bt->brp >> 1) - 1) << BT_BRP_SHIFT) |
      ((bt->phase_seg2 - 1) << BT_TSEG2_SHIFT) |
      ((bt->prop_seg + bt->phase_seg1 - 1));

每个字段都遵循:

filed_value << filed_shift

最后使用:

|

组合成完整的32位寄存器 更明确的写法:

cfg = FIELD_PREP(BT_SJW_MASK, bt->sjw - 1) |  
      FIELD_PREP(BT_BRP_MASK, (bt->brp >> 1) - 1) |  
      FIELD_PREP(BT_TSEG2_MASK, bt->phase_seg2 - 1) |  
      FIELD_PREP(BT_TSEG1_MASK,  
                 bt->prop_seg + bt->phase_seg1 - 1);

修改已有字段

不能直接:

val |= FIELD_PREP(MASK, new_value);

因为旧字段可能残留。 正确写法:

val &= ~MASK;  
val |= FIELD_PREP(MASK, new_value);

或者:

val = (val & ~MASK) |  
      FIELD_PREP(MASK, new_value);

例如旧字段:

011

新字段:

100

错误地直接 OR:

011 | 100 = 111

正确操作应先将旧字段清零。

寄存器偏移与位掩码

CAN驱动中: 寄存器偏移地址

#define CAN_MODE 0x00

寄存器中的位:

#define WORK_MODE BIT(0)

使用方式:

val = readl(rcan->base + CAN_MODE);
val |= WORK_MODE;
writel(val, rcan->base + CAN_MODE);

结构:

rcan->base + CAN_MODE  
        ↓  
CAN_MODE 寄存器地址  
        ↓  
WORK_MODE  
CAN_MODE 寄存器内部的 bit0

bitmap&atomic bit opreation

区别

类型用途
普通位操作操作寄存器、变量中的 bit
bitmap管理大量 bit 状态
原子位操作多线程/中断环境安全修改 bit

Kernel bitops

set_bit(n, addr);
clear_bit(n, addr);
test_bit(n, addr);

例如:

unsigned long state;  
​  
set_bit(3, &state);  
​  
if (test_bit(3, &state))  
    clear_bit(3, &state);

常见接口:

set_bit()  
clear_bit()  
change_bit()  
test_bit()  
​  
test_and_set_bit()  
test_and_clear_bit()  
test_and_change_bit()

test_and_set_bit() 会:

  1. 返回原来的位值
  2. 同时将该位置 1 例如:
if (test_and_set_bit(DEVICE_BUSY, &flags))  
    return -EBUSY;

含义: 如果原来已经是 busy,则返回忙
否则原子地设置 busy

  • 不要对 MMIO 寄存器使用 set_bit() 错误理解:
set_bit(3, rcan->base + CAN_MODE);
MMIO 寄存器应该使用:
val = readl(rcan->base + CAN_MODE);  
val |= BIT(3);  
writel(val, rcan->base + CAN_MODE);

原因是:

set_bit()

用于普通内存位图,不是设备 MMIO 寄存器访问接口。

bitmap 接口

当位数很多时,Kernel 使用 bitmap:

DECLARE_BITMAP(bitmap, 128);

常见操作:

bitmap_zero()
bitmap_fill()
bitmap_set()
bitmap_clear()
bitmap_test_bit()
find_first_bit()
find_next_bit()

驱动初学阶段先认识即可,优先掌握寄存器位操作。

01_Learning · 文档目录
下一篇Register