• 这篇草稿有了 4 年了吧,已经不是拖延症的问题了…

    • 篇幅还不少,丢掉可惜,稍微整理后发.
    • 因为当时动笔时属于应付 KPI,东拼西凑,无法保证内容准确性.
    • 时间实在太长了,资料来源有很多缺失了,版权问题请联系我.
  • 资料来源:

    http://www.wowotech.net/timer_subsystem/time_concept.html

  • 更新

    1
    2022.03.27 初始
阅读全文 »

  • linux 文件权限详解,uid gid 等

  • 资料来源:

    linux 命令行与 shell 脚本大全

  • 更新

    1
    2
    20.11.07 初始化
    20.11.08 初版
阅读全文 »

共享内存

  • 即多进程之间直接对读写同一段内存.相比较管道及消息队列,显而易见的好处是速度快,所有的 IPC 方法中效率最高.但共享内存并未提供同步机制,需要自行实现.

原理

  • 顾名思义,共享内存就是说两个不同的进程 A、B 可以共同享有一块内存区域
    20150124140604078.png

  • 整个处理流程是

    • 进程 A 第一次访问该页中的数据时, 生成一个缺页中断. 内核读入此页到内存并更新页表使之指向此页.
    • 进程 B 访问同一页面而出现缺页中断,内核只将进程 B 的页表登记项指向次页即可.
    • 进程 A.B 即可访问同一段内存.

使用

  • shmget 函数
    shmget 被用来开辟/初始化一段共享内存.其他进程使用相同的 key 通过 shgat 获取同一段共享内存.,只有 shmget 函数才直接使用信号量键,所有其他的信号量函数使用由 semget 函数返回的信号量标识符.

    函数原型

    1
    int shmget(key_t key, size_t size, int shmflg);

    key : 与信号量的 semget 函数一样,使用共享内存 key(非 0 整数)shmget 函数成功时返回一个与 key 相关的共享内存标识符 (非负整数),用于多进程的共享.调用失败返回 -1.
    size : 开辟的共享内存大小 (字节).
    shmflg : 权限控制,与 IPC_CREAT 做或操作,控制其他进程对共享内存权限. 0644 即代表其他进程只有读的权限.

  • shmat 函数
    用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间

    函数原型

    1
    void *shmat(int shm_id, const void *shm_addr, int shmflg);

    shm_id: 由 shmget 函数返回的共享内存标识.
    shm_addr: 指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址.
    shm_flg: 标志位,通常为 0.

  • shmdt 函数
    将共享内存从当前进程中分离,使该共享内存对当前进程不再可用.

    函数原型:

    1
    int shmdt(const void *shmaddr);

    shmaddr: shmat 函数返回的地址指针,调用成功时返回 0,失败时返回 -1.

  • shmctl 函数
    控制共享内存.

    函数原型

    1
    int shmctl(int shmid, int cmd, struct shmid_ds *buf)

    shm_id: shmget 函数返回的共享内存标识符

    command: 要采取的操作,以下三个值 :

    • IPC_STAT:把 shmid_ds 结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖 shmid_ds 的值.
    • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为 shmid_ds 结构中给出的值
    • IPC_RMID:删除共享内存段

    buf: 结构指针,指向共享内存模式和访问权限的结构.

    1
    2
    3
    4
    5
    6
    7
    shmid_ds结构至少包括以下成员:
    struct shmid_ds
    {
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
    };

系统限制及规避

大小

  • linux 默认限制共享内存总大小由 SHMMAX 值确定.默认值未 32MB

  • 读取

    1
    cat /proc/sys/kernel/shmmax
  • 当超过系统限制时 提示 unable to attach to shared memory.

  • 规避

    • 直接修改/proc.无需重启

      1
      echo "2147483648" > /proc/sys/kernel/shmmax

      可以将命令写入启动脚本 /etc/rc.local 中.保证重启生效

    • 使用 sysctl 命令修改

      1
      sysctl -w kernel.shmmax=2147483648

      可以将此参数插入到 /etc/sysctl.conf 启动文件中

      1
      echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf

      永久生效.

数量

  • 与大小类似的,共享内存创建的总数量由 SHMMNI 参数确定.

  • 读取

    1
    cat /proc/sys/kernel/shmmni

    默认情况下是 4096.

  • 一般不需要修改.

同一进程多次 Shmat

  • shmat 即挂载共享内存到进程的进程空间.

  • 当同一进程多次调用 shmat 挂载同一共享内存时,shamat 每次返回的地址都不同,相当于在进程的线性空间中存在多个实际指向同一块共享内存.直到最后进程线性空间消耗殆尽.

  • 解决:
    需要在挂载共享内存前,判断申请的共享内存指针是否为空,为 NULL ,则第一次加载此共享内存.否则不再重复加载.

    1
    2
    3
    4
    5
    6
    void* p = NULL;
    /*其他操作*/
    if (NULL == p)
    {
    p = shmat(shmid,p,0666);
    }

多个进程相同 Key 多次创建共享内存

  • 共享内存创建有大小之分.key 相同情况下,容量小的共享内存会获得之前创建的大的共享内存的内容.有可能导致之前创建共享内存的进程崩溃.

  • 解决

    • 在 shmage 使用 IPC_EXCL 标记.现行判断共享内存是否已创建.如果已创建则挂载,没有创建返回失败后,再创建.
    1
    2
    3
    4
    5
    6
    Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL);
    if (-1 != shmid)
    {
    /*错误处理*/
    Shmid = Shmget(key, size,IPC_CREATE);
    }
    • 不通过 key 标记同一块共享内存.
      shmget 使用 kry = IPC_PRIVATE ,linux 会忽略 key 值,直接新建一块共享内存.返回标识,通过管道/文件方式共享给其他进程使用.

共享内存删除

  • 调用 shmctl 删除共享内存后,共享内存并不会立刻被系统清理.
  • 首先共享内存的 shmid_ds 结构中的 shm_nattch 减一.该共享内存从调用 shmctl 的进程剥离.但 shm_nattch 不为 0 的情况下,即仍然有别进程连接的情况下.共享内存并不会立刻清除. 只有在 shm_nattch 为 0 ,没有任何进程连接的情况下,系统才会清理 这段共享内存.
  • 该段共享内存被任何连接的进程执行 shmctl 删除操作后, 新的进程将无法连接到该段共享内存.
  • 与之对应的 Shmdt ,只是将共享内存由 调用的进程空间剥离.不会影响其他进程连接到该段共享内存.

应用

  • 多进程共享数据.以链表为例.
  • 有两种方式:
    • 开辟一段共享内存,之后存入整个链表.将共享内存连接到各个进程.
      • 优点:
        • 对原有代码改动少.
      • 缺点:
        • 封装链表操作时,各个进程头节点偏移量不同,不能简单的通过 *p->next 访问.
        • 有共享内存大小限制.
    • 每一个链表节点对应一个共享内存.开辟同等数量的共享内存.
      • 优点
        • 可以随用随创,相对节省内存.
        • 链表节点 next 存储相对链表头节点偏移即可,访问操作相对单一共享内存容易.
      • 缺点
        • 受限于 linux 默认 4096 的总数量限制.且总数不易更改.
        • 开辟新链表节点相对复杂.
  • 实际使用中第一种较为常见.

同步

  • 与共享内存类似的信号量,也是跨进程的.信号量是比较一种比较方便配合共享内存同步的方式.

  • 正式开始让人崩溃的 linux 系列,希望自己能写完。
    先拿 rtc 开刀。
  • 这里我尽可能记录下思维的细节,而不是仅局限于代码,希望自己能领会 Linux 内核开发者的想法。
  • 内核版本:linux4.1
  • 需要了解:简略了解字符驱动 和 Linux 设备驱动模型

RTC

  • rtc 即 real time clock,实时时钟。
  • rtc 一般负责系统关机后计时,面对繁多的 Linux RTC 设备,内核干脆提供了一个 rtc 子系统,来支持所有的 rtc 设备。
  • 参考资料

Rtc 子系统

  • rtc 设备本质上是一个字符设备,rtc 子系统在字符设备的基础上抽象与硬件无关的部分,并在这个基础上拓展 sysfs 和 proc 文件系统下的访问。
  • 分析时候始终记住两点:
    • rtc 子系统是为了让 rtc 设备驱动编写更为简单,与硬件无关部分已被抽离。
    • rtc 子系统是基于字符设备而来的。

文件框架

  • rtc 子系统的源码在 /drivers/rtc
    1.jpg
    删减了很多 rtc-xxx.c 的驱动,只留下了 ds1307 作为示例,这里看到实际上代码并不多。

  • 具体文件分析

    • rtc.h:定义与 RTC 有关的数据结构。
    • class.c:向内核注册 RTC 类,为底层驱动提供 rtc_device_register 与 rtc_device_unregister 接口用于 RTC 设备的注册/注销。初始化 RTC 设备结构、sysfs、proc。
    • Interface.c:提供用户程序与 RTC 的接口函数,其中包括 ioctl 命令。
    • rtc-dev.c:将 RTC 设备抽象为通用的字符设备,提供文件操作函数集。
    • rtc-sysfs.c:管理 RTC 设备的 sysfs 属性,获取 RTC 设备名、日期、时间等。
    • rtc-proc.c:管理 RTC 设备的 procfs 属性,提供中断状态和标志查询。
    • rtc-lib.c:提供 RTC、Data 和 Time 之间的转换函数。
    • rtc-xxx,c:RTC 设备的实际驱动,此处以 rtc-ds1307 为例。
    • hctosys.c:开机时获取 RTC 时间。
  • 整个文件系统框架

  • RTC 子系统具体可分为 3 层:

    • 用户层:RTC 子系统向上层提供了接口,用户通过虚拟文件系统,间接调用 RTC 设备,具体有 3 种方式。
      • /dev/rtc RTC 设备抽象而来的字符设备,常规文件操作集合。
      • /sys/class/rtc/rtcx 通过 sysfs 文件系统进行 RTC 操作,也是最常用的方式。
      • /proc/driver/rtc 通过 proc 文件系统获取 RTC 相关信息。
    • RTC 核心层:与硬件无关,用于管理 RTC 设备注册/注销、提供上层文件操作的接口等。
    • RTC 驱动:特定 RTC 设备的驱动程序,实现 RTC 核心层的回掉函数。编写 RTC 驱动需要按照 RTC 子系统的接口填写对应函数并建立映射即可。RTC 核心层函数实现的过程和数量与特定硬件紧密相关。
  • 初看 linux 系统的人来说,这个图够头晕的了,但是呢,实际上没那么麻烦。由浅入深,一点一点来分析。

Rtc 子系统分析

Rtc-dev

  • rtc 子系统基于字符设备,字符设备对应的肯定是 rtc-dev.c 了,我们的分析由 rtc-dev 起步。

  • rtc-dev.c
    rtc-dev.c

  • 典型的字符设备,模块的初始化/卸载自然是 rtc_dev_init(void) 和 rtc_dev_exit(void)。
    设备接入,添加/删除设备 rtc_dev_add_device(struct rtc_device _rtc) 和 e) void rtc_dev_del_device(struct rtc_device_rtc)
    还有 ioctl 和 open 等函数,熟悉字符设备驱动的不用多说。

  • 追踪一下这两组函数在哪里调用的。

    • rtc_dev_init(void) 对应在 class.c 的 rtc_init(void) 函数
    • rtc_dev_add_device 对应在 class.c 的 rtc_device_register() 函数。
  • rtc_dev_init(void) 对应实在系统初始化时使用,对应 rtc_init(void) 也是在系统初始化之后调用。

  • rtc_dev_add_device() 是在驱动匹配后调用,rtc_device_register() 也是在驱动匹配后调用。打开 rtc-ds1307.c>-prob 函数,能找到 rtc_device_register() 也就证实了这个思路。

  • 接下来转入 class.c

class.c

  • linux 驱动模型中 class.c 对应类的意思,是 rtc 类。
    每个 class 对应都有自己核心数据结果,对应 rtc 类就是 rtc-device

  • rtc_device
    rtc_device 代表 RTC 设备基础的数据结构

    • 数据结构

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
       struct rtc_device  {
      struct device dev;
      struct module *owner;
      int id; //RTC设备的次设备号
      char name[RTC_DEVICE_NAME_SIZE];
      const struct rtc_class_ops *ops;
      struct mutex ops_lock;
      struct cdev char_dev;
      unsigned long flags;
      unsigned long irq_data;
      spinlock_t irq_lock;
      wait_queue_head_t irq_queue;
      struct fasync_struct *async_queue;
      struct rtc_task *irq_task;
      spinlock_t irq_task_lock;
      int irq_freq;
      int max_user_freq;
      #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
      struct work_struct uie_task;
      struct timer_list uie_timer;
      /* Those fields are protected by rtc->irq_lock */
      unsigned int oldsecs;
      unsigned int uie_irq_active:1;
      unsigned int stop_uie_polling:1;
      unsigned int uie_task_active:1;
      unsigned int uie_timer_active:1;
      #endif
      };

    • 很长?很😵对不对?只要关注一点就行 _

      1
      2
      3
      4
      5
      6
      7
      8
      9

      int id; //代表是那个rtc设备
      char name[RTC_DEVICE_NAME_SIZE]; //代表rtc设备的名称
      const struct rtc_class_ops *ops; //rtc操作函数集,需要驱动实现
      struct mutex ops_lock; //操作函数集的互斥锁

      struct cdev char_dev; //代表rtc字符设备,因为rtc就是个字符设备
      unsigned long flags; //rtc的状态标志,例如RTC_DEV_BUSY

    • 上文书说到,驱动程序的 prob 函数里面调用了 rtc_device_register() 这货的类型就是 rtc_device。参加驱动程序怎样调用 rtc_device_register(),与其他核心的基本结构不同的是,驱动程序以不是以 rtc-device 为参数注册设备到子系统,而是注册函数会返回一个 rtc_deivce 的结构给驱动。

  • rtc_class_ops
    这个是 rtc_device 的一部分。_

    • 数据结构

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      struct rtc_class_ops {
      int (*open)(struct device *); //打开设备时的回调函数,这个函数应该初始化硬件并申请资源
      void (*release)(struct device *); //这个函数是设备关闭时被调用的,应该注销申请的资源
      int (*ioctl)(struct device *, unsigned int, unsigned long); //ioctl函数,对想让RTC自己实现的命令应返回ENOIOCTLCMD
      int (*read_time)(struct device *, struct rtc_time *); //读取时间
      int (*set_time)(struct device *, struct rtc_time *); //设置时间
      int (*read_alarm)(struct device *, struct rtc_wkalrm *); //读取下一次定时中断的时间
      int (*set_alarm)(struct device *, struct rtc_wkalrm *); //设置下一次定时中断的时间
      int (*proc)(struct device *, struct seq_file *); //procfs接口
      int (*set_mmss)(struct device *, unsigned long secs); //将传入的参数secs转换为struct rtc_time然后调用set_time函数。程序员可以不实现这个函数,但 前提是定义好了read_time/set_time,因为RTC框架需要用这两个函数来实现这个功能。
      int (*irq_set_state)(struct device *, int enabled); //周期采样中断的开关,根据enabled的值来设置
      int (*irq_set_freq)(struct device *, int freq); //设置周期中断的频率
      int (*read_callback)(struct device *, int data); ///用户空间获得数据后会传入读取的数据,并用这个函数返回的数据更新数据。
      int (*alarm_irq_enable)(struct device *, unsigned int enabled); //alarm中断使能开关,根据enabled的值来设置
      int (*update_irq_enable)(struct device*, unsigned int enabled); //更新中断使能开关,根据enabled的值来设置
      };
    • 该结构体中函数大多数都是和 rtc 芯片的操作有关,需要驱动程序实现。
      所有 RTC 驱动都必须实现 read_time、set_time 函数,其他函数可选。

  • 参考其他的资料,class 的分析如下
    class

  • static int __init rtc_init(void)

    • 调用 class_create 创建 RTC 类,创建/sys/class/rtc 目录,初始化 RTC 类相关成员,向用户空间提供设备信息。
    • 调用 rtc-dev.c 实现的 rtc_dev_init(); 动态分配 RTC 字符设备的设备号。
    • 调用 rtc_sysfs_init(rtc_class);创建/sys/class/rtc 下属性文件 _
  • static void _exit rtc_exit(void)

    • 调用 rtc-dev.c 实现的 rtc_dev_exit();注销设备号。
    • 调用 class_destroy(rtc_class);注销/sys/class 下的 rtc 目录
  • struct rtc_device *rtc_device_register(const char *name, struct device *dev,const struct rtc_class_ops *ops,struct module *owner)_

    • 申请一个 idr 整数 ID 管理机制结构体,并且初始化相关成员
    • 将设备 dev 关联 sysfs 下的 rtc 类
    • 初始化 rtc 结构体的信号量
    • 初始化 rtc 结构体中的中断
    • 设置 rtc 的名字
    • 初始化 rtc 字符设备的设备号,然后注册 rtc 设备,自动创建/dev/rtc(n) 设备节点文件
    • 注册字符设备
    • 在/sys/rtc/目录下创建一个闹钟属性文件
    • 创建/proc/driver/rtc 目录
  • void rtc_device_unregister(struct rtc_device *rtc)

    • 删除 sysfs 中的 rtc 设备,即删除/sys/class/rtc 目录
    • 删除 dev 下的/dev/rtc(n) 设备节点文件
    • 删除虚拟文件系统接口,即删除/proc/driver/rtc 目录
    • 卸载字符设备
    • 清空 rtc 的操作函数指针 rtc->ops
    • 释放 rtc 的 device 结构体 _
  • static void rtc_device_release(struct device dev)

    • 卸载 idr 数字管理机制结构体
    • 释放 rtc 结构体的内存

Rtc 子系统初始化

  • 上图
    初始化

  • 使用 rtc 子系统首先需要在内核编译选项中启用 RTC 子系统支持。

    • 必须启用 Real Time Clock
    • 使用/dev 下的设备文件对应开启 CONFIG_RTC_INTF_DEV
    • 使用/proc 下的接口对应开启 CONFIG_RTC_INTF_PROC
    • 使用/sysfs 下的接口对应开启 CONFIG_RTC_INTF_SYSFS
  • _ 系统启动后,如配置启用 rtc 子系统,则会首先执行 rtc_init_ 函数,创建 rtc 类、初始化相关成员、分配设备号等

  • 创建 rtc 类后,之后调用 rtc_dev_init() 动态分配 rtc 字符设备的设备号。之后调用 rtc_sysfs_init() 初始化/sys/class/rtc 目录中的属性文件

Rtc 设备注册

  • Rtc 设备本质上属于字符设备,依附于系统内总线。一般来说 cpu 内部 rtc 依附于 platform 总线,外置 rtc 芯片则依附于通信方式对应总线。其过程与通用字符设备相似,rtc 子系统在设备注册过程中附加了 prob 和 sysfs 相关的注册和初始化操作。

  • 上图
    Rtc设备注册

  • Rtc 设备挂载后,相应总线会搜索匹配的驱动程序,驱动程序成功 match 后,进入驱动实现的 probe 函数,执行设备注册等操作。

  • 完成总线设备注册后,probe 会跳转到 rtc_device_register() 函数,将设备注册到 rtc 子系统。

  • Rtc 设备本质属于字符设备,会调用 rtc_dev_prepare() 函数,初始化字符设备,设置 rtc 相关的 file operation 函数集合。

  • 之后依次调用 rtc_dev_add_device(rtc)、rtc_sysfs_add_device(rtc)、rtc_proc_add_device(rtc) ,进行注册字符设备、在/sys/rtc/目录下创建一个闹钟属性文件、创建/proc/driver/rtc 目录等操作。

  • rtc_device_register() 会将驱动实现的 rtc_class_ops 结构体与具体设备联系起来。

interface.c

  • 在 rtc-proc.c、rtc_sysfs 和 ioctl 命令中,所有的操作调用的都是 interface.c 提供的接口,这里以 ioctl 的一个例子说明整个调用的过程

  • 上图
    interface.c

  • 以 icotl 命令 RTC_RD_TIME 为例,说明命令调用的流程。

    • RTC_RD_TIME 对应的是/dev 下 ioctl 命令,调用被转发至/rtc-dev.c

    • rtc-dev.c->rtc_dev_ioctl(struct file *file,unsigned int cmd, unsigned long arg) 函数中。RTC_RD_TIME 对应的代码为 err = rtc_read_time(rtc, &tm); rtc_read_time 是 interface.c 文件提供的接口之一。

    • interface.c->rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm) 函数中对应 rtc_class_ops 转发代码为 err = rtc->ops->read_time(rtc->dev.parent, tm); 将操作转发至匹配的 rtc 设备。

    • 设备驱动这里以 rtc-ds1307 为例,但设备注册时通过 mcp794xx_rtc_ops 结构体将 rtc_class_ops 对应函数与驱动程序实现的函数绑定

      1
      2
      3
      4
      5
      6
      7
      static const struct rtc_class_ops mcp794xx_rtc_ops = {
      .read_time = ds1307_get_time,
      .set_time = ds1307_set_time,
      .read_alarm = mcp794xx_read_alarm,
      .set_alarm = mcp794xx_set_alarm,
      .alarm_irq_enable = mcp794xx_alarm_irq_enable,
      };
    • 最终执行转入 ds1307.c-> ds1307_get_time 函数,执行与硬件相关的操作。

rtc-sysfs.c

  • 由前半部分可知,/sys/class/rtc/是在 rtc-init 调用 rtc_sysfs_init 后生成。

    1
    2
    3
    4
    5

    void __init rtc_sysfs_init(struct class *rtc_class) {
    rtc_class->dev_groups = rtc_groups;
    }

  • 这里的 rtc_groups 是 rtc-sysfs.c 中定义了这样一个 attribute 函数指针数组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    static struct attribute *rtc_attrs[] = {
    &dev_attr_name.attr,
    &dev_attr_date.attr,
    &dev_attr_time.attr,
    &dev_attr_since_epoch.attr,
    &dev_attr_max_user_freq.attr,
    &dev_attr_hctosys.attr,
    NULL,
    };
    ATTRIBUTE_GROUPS(rtc);
  • _ 在 rtc_sysfs_init 函数调用后绑定了 sysfs 节点操作函数的集合,使得设备匹配驱动程序后而生成对应的 rtcn 文件夹。

  • dev_attr_name和dev_attr_data 由宏 DEVICE_ATTR_RO 和 DEVICE_ATTR_RW 生成,他们分别定义了只读的和可读可写的 attribute 节点。每个属性函数下都有 DEVICE_ATTR_XX() 宏声明,绑定到对应 attribute 节点。

rtc-proc.c

  • proc 文件系统是软件创建的文件系统,内核通过他向外界导出信息,下面的每一个文件都绑定一个函数,当用户读取这个文件的时候,这个函数会向文件写入信息。

  • rtc-proc.c 中初始化了 file_operations 结构体:

    1
    2
    3
    4
    5
    6
    static const struct file_operations rtc_proc_fops = {
    .open = rtc_proc_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = rtc_proc_release,
    };
  • _RTC 驱动在向 RTC 核心注册自己的时候,由注册函数 rtc_device_resgister 调用 rtc_proc_add_device 来实现 proc 接口的初始化,这个函数如下定义:

    1
    2
    3
    4
    5
    6

    void rtc_proc_add_device(struct rtc_device *rtc)
    {
    if (rtc->id == 0)
    proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);
    }

    主要是完成创建文件节点,并将文件的操作函数与节点联系起来。调用这个函数后,在/proc/driver 目录下就会有一个文件 rtc

Rtc 设备访问

  • rtc 子系统最多可以支持 16 个 rtc 设备,多个 rtc 设备会在/dev/和 /sys/class/rtc/下生成 rtc0、rtc1…等不同节点 (下文以 rtcn 代称)。而系统启动时会选择一个 rtc 设备读取计时,在/dev 下有 rtc 文件,rtc 文件指向系统选择的 rtc 设备对应的 rtcn(一般为 rtc0)。
  • 用户层访问 rtc 设备有 3 种途径:
    • /dev/rtcn open 等字符设备操作和 ioctl 命令。
    • /sys/class/rtc/rtcn sysfs 属性,一些属性是只读。
    • /proc/driver/rtc 第一个 rtc 会占用 procfs 接口,在 procfs 下暴露更多信息。

/dev

  • 在/dev 下用户可以通过两种方式访问 rtc 设备,第一个是通过字符设备定义的 open、read 等函数(需要驱动程序实现)、另一个是通过定义的 ioctl 命令。第一种方式是直接打开 rtc-dev.c 定义的 open 等函数,在 open 等中直接调用驱动程序实现的函数。通过 ioctl 命令访问则是将操作转发到了 interface.c 定义的接口,间接调用驱动程序实现的函数。

  • ioctl() 函数访问/dev 下的设备。以下是典型函数:_

    • ioctl(fd,RTC_ALM_SET, &rtc_tm);
      设置 alarm 中断的触发时刻,不超过 24 小时。第三个参数为 structrtc_time 结构体,读取时会忽略年月日信息。alarm 中断与 wakeupalarm 中断只能同时使用 1 个,以最后一次设定为准。

    • ioctl(fd,RTC_ALM_READ, &rtc_tm)
      读取 alarm 中断的触发时刻。

    • ioctl(fd,RTC_WKALM_SET, &alarm);
      设置 wakeupalarm 中断的触发时刻, wakeupalarm 中断的触发时刻可以在未来的任意时刻。alarm 中断与 wakeupalarm 中断只能同时使用 1 个,以最后一次设定为准。

    • ioctl(fd,RTC_WKALM_RD, &alarm);
      读取 wakeupalarm 中断的触发时刻。

    • ioctl(fd,RTC_IRQP_SET, tmp);
      设置周期中断的频率,tmp 的值必须是 2 的幂,非 Root 用户无法使用 64HZ 以上的周期中断。

    • ioctl(fd,RTC_IRQP_READ, &tmp);
      读取周期中断的频率。

    • ioctl(fd,RTC_SET_TIME, &rtc_tm)
      更新 RTC 芯片的当前时间。

    • ioctl(fd,RTC_RD_TIME, &rtc_tm);
      读取 RTC 硬件中的当前时间。

  • 以 open 操作为例,在用户层对/dev 下设备执行 open 会被转发至 rtc_dev_open(struct inode *inode, struct file *file) 函数,通过 err= ops->open ? ops->open(rtc->dev.parent) : 0; 判断驱动程序是否通过连接的 rtc_class_ops 结构体实现了 open 函数,驱动程序实现了 open 函数,则将 open 操作转发至驱动程序。

/sys

  • /sys/class/rtc/rtcn 下面的 sysfs 接口提供了操作 rtc 属性的方法,所有的日期时间都是墙上时间,而不是系统时间。
    • date: RTC 提供的日期
    • hctosys: 如果在内核配置选项中配置了 CONFIG_RTC_HCTOSYS,RTC 会在系统启动的时候提供系统时间,这种情况下这个位就是 1,否则为 0
    • max_user_freq: 非特权用户可以从 RTC 得到的最大中断频
    • name: RTC 的名字,与 sysfs 目录相关
    • since_epoch: 从纪元开始所经历的秒数
    • time: RTC 提供的时间
    • wakealarm: 唤醒时间的时间事件。 这是一种单次的唤醒事件,所以如果还需要唤醒,在唤醒发生后必须复位。这个域的数据结构或者是从纪元开始经历的妙数,或者是相对的秒数

/proc

  • /proc/driver/rtc 下只对应第一个 rtc 设备,与 sysfs 下相比,该设备暴露更多信息
  • 对应截图
    /proc

RTC 子系统测试

Hwclock 命令或使用测试文件。

  • Hwclock 命令可以执行最简单的 RTC 测试。常用命令示例如下
    • hwclock #查看RTC时间
    • hwclock -set -date=”07/07/17 10:10” #设置硬件RTC时间(月/日/年 时: 分: 秒)
    • hwclock -w #系统时间同步至RTC
    • hwclock -s #同步RTC到系统时间
  • Linux 内核提供了 RTC 子系统的测试示例文件,位于 tools/testing/selftests/timers/rtctest.c,包含了基于 ioctl 命令的完整测试。

Linux 下文件夹相关操作

  • 参考

    http://blog.csdn.net/u011118014/article/details/43232693

打开文件夹,遍历访问每个文件

切换到文件夹路径下

  • 调用 chdir 即可

    1
    chdir("/info");

打开文件夹

  • 源码,打开文件夹返回对应文件夹的 DIR 结构体

    1
    2
    3
    4
    5
    DIR *dir;
    dir = opendir(pcDirName);
    if (NULL == dir){
    continue ;
    }
  • DIR 结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct __dirstream
    {
    void *__fd;
    char *__data;
    int __entry_data;
    char *__ptr;
    int __entry_ptr;
    size_t __allocation;
    size_t __size;
    __libc_lock_define (, __lock)
    };
    typedef struct __dirstream DIR;

    保存文件夹相关内容,无需深究

  • 源码,遍历文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct direct    *ent;
    while (NULL != (ent = readdir(dir))){
    /*如果 指向 . ..*/
    if (0 == strcmp(ent->d_name, ".") \
    || 0 == strcmp(ent->d_name, "..") \
    || 4 == ent->d_type){
    continue;
    }
    /*遍历*/
    }
  • dirent 结构体

    1
    2
    3
    4
    5
    6
    7
    8
    struct dirent
    {
    long d_ino; /* inode number 索引节点号 */
    off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
    unsigned short d_reclen; /* length of this d_name 文件名长 */
    unsigned char d_type; /* the type of d_name 文件类型 */
    char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
    }

    dirent 指向目录和目录中某个具体文件,但还是桥梁作用,访问文件具体内容还需要通过 d_name 找到 stat 结构体支援.

  • 源码,获取 stat 结构体

    1
    2
    stat  f_stat;
    sdwRet = stat(ent->d_name, &f_stat);
  • stat 结构体是指向文件的结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    struct stat {
    mode_t st_mode; //文件访问权限
    ino_t st_ino; //索引节点号
    dev_t st_dev; //文件使用的设备号
    dev_t st_rdev; //设备文件的设备号
    nlink_t st_nlink; //文件的硬连接数
    uid_t st_uid; //所有者用户识别号
    gid_t st_gid; //组识别号
    off_t st_size; //以字节为单位的文件容量
    time_t st_atime; //最后一次访问该文件的时间
    time_t st_mtime; //最后一次修改该文件的时间
    time_t st_ctime; //最后一次改变该文件状态的时间
    blksize_t st_blksize; //包含该文件的磁盘块的大小
    blkcnt_t st_blocks; //该文件所占的磁盘块
    };
  • 之后愉快访问吧