文章目录
- 1.ioctl
-
- [1.1 实验](#1.1 实验)
- [1.2 dimm温度](#1.2 dimm温度)
- 2.休眠&唤醒
- 3.proc文件系统
- 4.内核函数调用堆栈
- 5.printk
- 6.动态打印
- 4.top:idle和wait是cpu不工作时间
- [5.dstat:监视系统资源使用,/proc/interruptes(硬中断),/proc/irq/[irq_num]/smp_affinity是一个十六进制数值(每个位对应一个 CPU 核心)](#5.dstat:监视系统资源使用,/proc/interruptes(硬中断),/proc/irq/[irq_num]/smp_affinity是一个十六进制数值(每个位对应一个 CPU 核心))
- 6.perf:函数调用占比
- 7.ftrace:系统层面,功能需要打开,image大小会变大
1.ioctl
1.1 实验
c
// hello_chr_locked.h
#ifndef _HELLO_CHR_LOCKED_H_
#define _HELLO_CHR_LOCKED_H_
#define HC_IOC_MAGIC 0x81 // type(0x81未被使用) // 内核源码里Documentation/userspace-api/ioctl/ioctl-number.rst
// 无参数命令
#define HC_IOC_RESET _IO(HC_IOC_MAGIC, 0)
// 功能:清空分配的空间
// _IO: 无数据传输
// 读命令(从内核读数据到用户空间)
#define HC_IOCP_GET_LENS _IOR(HC_IOC_MAGIC, 1, int)
// 功能:获取字符串长度(通过指针返回)
// _IOR: 读操作,用户提供指针接收数据
// int: 数据类型
// 无参数读命令
#define HC_IOCV_GET_LENS _IO(HC_IOC_MAGIC, 2)
// 功能:获取字符串长度(通过返回值返回)
// 用户调用:int len = ioctl(fd, HC_IOCV_GET_LENS);
// 写命令(从用户空间写数据到内核)
#define HC_IOCP_SET_LENS _IOW(HC_IOC_MAGIC, 3, int)
// 功能:设置字符串长度(通过指针传递)
// _IOW: 写操作,用户提供指针发送数据
// 无参数写命令
#define HC_IOCV_SET_LENS _IO(HC_IOC_MAGIC, 4)
// 功能:设置字符串长度(直接传值)
// 用户调用:ioctl(fd, HC_IOCV_SET_LENS, 100);
// 最大命令编号
#define HC_IOC_MAXNR 4
#endif
c
/*
hello_chr_locked.c:ioctl的第二个参数即cmd(32位整形)构成: direction(方向:指明读写,2bits) , size(数据大小,14bits) type(幻数,唯一标识ioctl命令,8bits) , number(序数nr,命令的编号,8bits)
宏:_IO(type,nr) _IOR(type,nr,size) _IOW(type,nr,size) _IOWR(type,nr,size) // 为了构造上面32位的cmd命令
_IOC_DIR(nr) _IOC_TYPE(nr) _IOC_NR(nr) _IOC_SIZE(nr) // 从cmd命令中提取对应字段
函数:access_ok() // 检查用户空间地址是否可用,数据传输之前要用这个检查下
put_user()和__put_user() // 向用户空间写数据,put_user安全即里面执行了access_ok()
get_user()和__get_user() // 从用户空间接收数据,__get_user需先手动调access_ok()
capable() // 检查进程是否有权限,因为ioctl要对硬件进行控制和更改,需要进程被授权操作权限
对于需要传入的数据量不大的ioctl函数,可以使用put_user()或get_user()【单个数据:可能1,2,4,8个字节】,如果传入数据量很大的话,可使用之前的copy_from_user(),copy_to_user()
*/
#include "hello_chr_locked.h" //上面自定义
long hc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct hello_char_dev *hc_dev = filp->private_data; //得到设备的地址
long retval = 0;
int tmp,err=0;
//如下两行判断传入的命令是否合法
if (_IOC_TYPE(cmd) != HC_IOC_MAGIC) return -ENOTTY; //检查幻数(返回值POSIX标准规定,也用-EINVAL)
if (_IOC_NR(cmd) > HC_IOC_MAXNR) return -ENOTTY; //检查命令编号
//如下只判断读的空间,读的时候使用带下划线函数,写使用安全函数
if (_IOC_DIR(cmd) & _IOC_READ) //涉及到用户空间与内核空间数据交互,判断读OK吗?
err = !access_ok((void __user *)arg, _IOC_SIZE(cmd)); //第一个参数:用户空间地址。第二个参数:传入参数大小。ok返回1,err就是0,下行if(0)不执行
if (err) return -EFAULT;
// 前面判断都正常,说明命令合法进行如下
switch(cmd){
case HC_IOC_RESET:
printk(KERN_INFO "ioctl reset\n");
kfree(hc_dev->c);
hc_dev->n=0;
break;
case HC_IOCP_GET_LENS:
printk(KERN_INFO "ioctl get lens through pointer\n"); //指针方式传递
retval = __put_user(hc_dev->n,(int __user *)arg); //第一个参数:你要传的数据 //第二个参数:用户空间地址
break;
case HC_IOCV_GET_LENS:
printk(KERN_INFO "ioctl get lens through value\n");
return hc_dev->n; //直接return这个值就行
break;
case HC_IOCP_SET_LENS:
printk(KERN_INFO "ioctl set lens through pointer");
if (! capable (CAP_SYS_ADMIN)) //具有管理员权限才可修改
return -EPERM;
retval = get_user(tmp,(int __user *)arg); //第一个参数:要保存数据的位置,第二个参数:用户空间传入的数据(这里是指针传入)
//hc_dev->n = min(hc_dev->n,tmp);
if(hc_dev->n>tmp) //判断这个值,和具体业务实现有关
hc_dev->n=tmp;
printk(KERN_INFO " %d\n",hc_dev->n);
break;
case HC_IOCV_SET_LENS:
printk(KERN_INFO "ioctl set lens through value");
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
hc_dev->n = min(hc_dev->n,(int)arg); //arg就是值,直接赋值
printk(KERN_INFO " %d\n",hc_dev->n);
break;
default: //前面做了cmd的检查,这里可以不需要
break;
}
return retval;
}
struct file_operations hc_fops = { //字符设备的操作函数
...
.unlocked_ioctl = hc_ioctl, //还有一个compat_ioctl(用于32位程序运行于64位系统上)
};
c
// ioctltest.c: 测试函数操作ioctl,先insmod hello_chr_locked.ko
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h> //包括了一些构造命令那些宏
#include<errno.h>
#include"hello_chr_locked.h" //这样才能在应用空间找到对应命令
int main(int argc ,char* argv[])
{
int n,retval=0;
int fd;
fd = open("/dev/hc_dev0",O_RDWR);
switch(argv[1][0])
{
case '0': //复位
ioctl(fd,HC_IOC_RESET);
printf("reset hc\n");
break;
case '1': //通过指针获取长度
ioctl(fd,HC_IOCP_GET_LENS,&n);
printf("get lens pointer, %d\n",n);
break;
case '2': //通过值获取长度
n = ioctl(fd,HC_IOCV_GET_LENS);
printf("get lens value, %d\n",n);
break;
case '3': //通过指针设置长度
n=argv[2][0]-'0';
retval = ioctl(fd,HC_IOCP_SET_LENS,&n);
printf("set lens value, %d %s\n",n,strerror(errno));
break;
case '4': //通过值设置长度
n=argv[2][0]-'0';
retval = ioctl(fd,HC_IOCV_SET_LENS,n);
printf("set lens value, %d %s\n",n,strerror(errno));
break;
}
close(fd);
return 0;
}

1.2 dimm温度

c
// 如下用命令获取memory温度,前提将DDR SPD访问权切给BMC,CPU0_A0上0x49设备地址0x0寄存器读回2个字节数据
// i3ctransfer -d /dev/i3c-0-3c000000000 -w 49,0 -r 2
Success on message 0
Success on message 1
received data:
0xd0
0x01
// HS_I3C.h
#ifndef _HS_I3C_H_
#define _HS_I3C_H_
#include <linux/ioctl.h>
struct HS_i3c_ioc_priv_xfer
{
__u64 data;
__u16 len;
__u8 rnw; // 读/写标志(8位),0表示写,1表示读
__u8 pad[5];
};
#define HS_I3C_DEV_IOC_MAGIC 0x07
#define HS_I3C_PRIV_XFER_SIZE(N) \
((((sizeof(struct HS_i3c_ioc_priv_xfer)) * (N)) < (1 << _IOC_SIZEBITS)) \
? ((sizeof(struct HS_i3c_ioc_priv_xfer)) * (N)) : 0)
#define HS_I3C_IOC_PRIV_XFER(N) \ // _IOC_READ|_IOC_WRITE: 双向数据传输
_IOC(_IOC_READ|_IOC_WRITE, HS_I3C_DEV_IOC_MAGIC, 30, HS_I3C_PRIV_XFER_SIZE(N)) // N个xfer结构的大小
extern char gI3C_DIMM_Device[32][32];
extern int HS_I3CWriteReadDevice( char *device, INT8U WriteLen, INT8U ReadLen, INT8U *pwriteBuf, INT8U *preadbuf );
#endif
// HS_I3C.c
#include "HS_I3C.h"
char gI3C_DIMM_Device[32][32] =
{
"/dev/i3c-0-3c000000000", //CPU0_A0
"/dev/i3c-0-3c000000001", //CPU0_A1
"/dev/i3c-0-3c000000002", //CPU0_B0
...
};
int HS_I3CWriteReadDevice( char *device, INT8U WriteLen, INT8U ReadLen, INT8U *pwriteBuf, INT8U *preadbuf )
{
int ret = -1;
//(void)snprintf( BusName, 32, "/dev/i3c-%d-3c000000000", BusNum );
struct HS_i3c_ioc_priv_xfer *xfers;
int file;
int nxfers = 0;
if( !device )
{
printf( "I3C device pointer is NULL.\n" );
return ret;
}
if( ( NULL == pwriteBuf ) || ( NULL == preadbuf ) )
{
printf( "Function parameter pointer is NULL.\n" );
return ret;
}
if( 0 != WriteLen )
{
nxfers++; // 计算传输次数
}
if( 0 != ReadLen )
{
nxfers++;
}
file = open( device, O_RDWR );
if( file < 0 )
{
printf( "Open I3C device %s failed.\n", device );
return ret;
}
xfers = ( struct HS_i3c_ioc_priv_xfer * )calloc( nxfers, sizeof( *xfers ) );
if( !xfers )
{
printf( "Calloc memory failed.\n" );
close( file );
return ret;
}
nxfers = 0;
if( 0 != WriteLen ) // 设置传输参数
{
xfers[nxfers].len = WriteLen;
xfers[nxfers].data = ( unsigned long )pwriteBuf;
nxfers++; // rnw默认为0(写)
}
if( 0 != ReadLen )
{
xfers[nxfers].rnw = 1;
xfers[nxfers].len = ReadLen;
xfers[nxfers].data = ( unsigned long )preadbuf; //////////////////////////////重点
nxfers++;
}
if( ioctl( file, HS_I3C_IOC_PRIV_XFER( nxfers ), xfers ) < 0 )
{
printf( "Error: I3C read or write failed.\n" );
free( xfers );
close( file );
return ret;
}
free( xfers );
close( file );
return 0;
}
wbuf[0] = 49;
wbuf[1] = 0; // 参考上面i3ctransfer命令
DimmGroupID = pSensorInfo->SensorNumber - HS_SENSOR_DIMMG0_TEMP; // 0,1,2,3
for( DimmIndex = 0; DimmIndex < 8; DimmIndex++ )
{
if( access( gI3C_DIMM_Device[DimmGroupID*8 + DimmIndex], F_OK ) == 0 )
{
ret = HS_I3CWriteReadDevice(gI3C_DIMM_Device[DimmGroupID*8 + DimmIndex], wlen, rlen, wbuf, rbuf);
DimmTemp = (( rbuf[0] + ( rbuf[1] << 8 ) ) >> 4) & 0xFF;
// 根据JESD302-1-01规范,温度数据在MR49和MR50寄存器中
// MR49(低8位) + MR50(高8位) 组成16位温度值
// >>4 是因为温度数据在寄存器的高12位
if( rbuf[1] & 0x10 ) // 检查MR50的第4位
{
DimmTemp = DimmTemp | 0x80; // 设置符号位(最高位为1表示负数)
}
printf("Dimmdev: %s, Temp %d\n", gI3C_DIMM_Device[DimmGroupID*8 + DimmIndex],DimmTemp);
}
memset( rbuf, 0, rlen );
if( DimmTemp & 0x80 ) // 如果是负数
{
raw_reading = ( DimmTemp & 0x7F ) > raw_reading ? raw_reading:DimmTemp; // 取较小的温度值(负数越小,实际温度越低)
}
else // 如果是正数
{
raw_reading = ( DimmTemp & 0x7F ) > raw_reading ? DimmTemp:raw_reading; // 取较大的温度值
}
}
2.休眠&唤醒
c
// cat /sys/power/state : 支持哪些挂起模式,不是当前状态
freeze: 可快速恢复
mem: 长时间 省电
// echo freeze/mem > /sys/power/state : 触发休眠,命令敲完后,串口卡住没反应了 (在/sys/power/autosleep【CONFIG_PM_AUTOSLEEP:自动休眠唤醒】是off状态才行)
// kernel/power/suspend.c:
int pm_suspend(suspend_state_t state) // autosleep也是调用这行函数
{
error = enter_state(state); // 调用enter_state进入suspend状态, 具体的状态根据state值而定, 如果执行成功的话, 一直到系统退出suspend的状态后此函数才退出
}
EXPORT_SYMBOL(pm_suspend); // 自动休眠唤醒suspend控制器发现如果/sys/kernel/debug/wakeup_sources中active_since一列【可设置/sys/power/wake_lock】都为0即没有持锁【即没有唤醒】就休眠
// 如果外设没有实现dev_pm_ops,那么外设对于休眠唤醒没有任何动作
struct dev_pm_ops {
int (*suspend)(struct device *dev);//休眠,所有设备
int (*resume)(struct device *dev);//唤醒
...
int (*runtime_suspend)(struct device *dev); // 单设备:并不是要求设备一定要进入低功耗状态, 而是要求设备在suspend后,不再处理数据,不再和CPUs、RAM进行任何的交互,直到设备的.runtime_resume被调用
int (*runtime_resume)(struct device *dev);
};



c
/*
有时候进程在读设备时,发现设备数据还没准备好,没办法正常读取设备。或在写设备时,发现设备缓冲区满,没办法正常写设备。进程在操作设备时,如果条件不满足,就让它进入休眠等待,直到条件满足,就可唤醒进程进行后面操作。
初始化:
DECLARE_WAIT_QUEUE_HEAD() //初始化等待队列头,宏的静态方式
或
wait_queue_head_t wq; //动态方式
init_waitqueue_head(&wq);
休眠:
wait_event() //不可被打断,死等,直到满足条件为止
wait_event_interruptible() //可使用信号打断它,常用
唤醒:
wake_up() //对应wait_event()
wake_up_interruptible() //对应上面可中断方式休眠的进程wait_event_interruptible()
*/
# include <linux/wait.h>
DECLARE_WAIT_QUEUE_HEAD(wq);
{ // hc_read函数中
printk(KERN_INFO "read hc_dev %p\n",hc_dev);
// wait_event(wq,hc_dev->c!=NULL); //第一个参数:等待队列头。第二个参数:等待条件: 如果设备字符串区(即echo值进设备文件里)为空就进入等待(唤醒方法在write函数中)。
wait_event_interruptible(wq,hc_dev->c!=NULL);
}
{ // hc_write函数中
printk(KERN_INFO"%s write done",current->comm);
// wake_up(&wq); // 当写入字符设备成功后调用wakeup函数唤醒等待队列上的进程
wake_up_interruptible(&wq);
return count;
}

如下等待条件满足,如上卡住的即休眠的进程会正常退出(两个进程cat卡住休眠,执行如下一行,两个进程都不会卡住)。

如下__pm_relax用来释放先前通过__pm_stay_awake增加的唤醒请求即active_since一列。


3.proc文件系统


c
/*
新调试方法:利用proc文件系统在pro文件夹下创建接口,读写这个接口就可实现对内核的调试
struct proc_ops //pro文件夹下创建接口第一种方式
proc_create()
struct seq_operations //第二种方式
proc_create_seq()
remove_proc_entry //移除接口
*/
#include<linux/module.h>
#include<linux/uaccess.h>
#include<linux/string.h>
#define PROC_DEBUG
#ifdef PROC_DEBUG
#include<linux/proc_fs.h> //传统第一种方式
#include<linux/seq_file.h> //seq第二种方式
#endif
char * str = "hello proc\n";
#ifdef PROC_DEBUG
int hp_open(struct inode * inode, struct file * filp)
{
printk(KERN_INFO"open %ld\n",strlen(str));
return 0;
}
ssize_t hp_read(struct file * filp, char __user * buff, size_t count, loff_t * f_pos)
{
ssize_t retval=0;
int n = strlen(str); // 内容总长度(11字节,含\n)
if(*f_pos >= n) // 已经读完
goto out;
if(*f_pos + count > n) // 防止越界
count = n - *f_pos; // 调整读取长度
if(copy_to_user(buff,str,count)) //将字符串str("hello proc\n")赋值到buff用户空间
{
retval = -EFAULT;
goto out;
}
*f_pos += count; // 更新偏移量
return count;
out:
return retval;
}
struct proc_ops hp_ops = { //下面 __init函数中proc_create调用hp_ops创建接口文件
.proc_open = hp_open,
.proc_read = hp_read,
};
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
void * hp_seq_start (struct seq_file *m, loff_t *pos) //pos表示当前读到哪个位置或写到哪个位置了
{
printk(KERN_INFO"seq start\n");
if(*pos >= strlen(str)) //pos指当前读到或写到哪个位置,索引
return NULL;
return &str[*pos]; //拿出字符串中字符,将地址返回,这返回值将来作为下面其他函数的v传入
}
void hp_seq_stop(struct seq_file *m, void *v)
{
printk(KERN_INFO"seq stop\n"); //清除start函数一些工作,start里开辟一些空间或申请一些锁,这里清除
}
void * hp_seq_next (struct seq_file *m, void *v, loff_t *pos) //改变索引值
{
printk(KERN_INFO"seq next\n");
(*pos)++;
if(*pos >= strlen(str))
return NULL;
return &str[*pos];
}
int hp_seq_show (struct seq_file *m, void *v)
{
printk(KERN_INFO"seq show\n");
seq_putc(m,*(char*)v); //将获得到的字符一个一个打印出
return 0;
}
const struct seq_operations seq_ops={ //构建这结构体
.start = hp_seq_start,
.stop = hp_seq_stop,
.next = hp_seq_next,
.show = hp_seq_show,
};
#endif
//11111111111111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)
{
printk(KERN_INFO "HELLO LINUX MODULE\n");
#ifdef PROC_DEBUG
proc_create("hello_proc",0,NULL,&hp_ops); //第一个参数即显示在pro目录下文件名称,
//第二个参数默认0只读权限。第三个参数父节点,null默认pro目录。最后一个参数是操作的结构体地址
proc_create_seq("hello_seq_proc",0,NULL,&seq_ops); //就可在pro目录下创建对应节点
#endif
return 0;
}
static void __exit hello_exit(void)
{
#ifdef PROC_DEBUG
remove_proc_entry("hello_proc",NULL); //第二个参数是父节点
remove_proc_entry("hello_seq_proc",NULL);
#endif
printk(KERN_INFO "GOODBYE LINUX\n");
}
module_init(hello_init);
module_exit(hello_exit); ...
show next调用了11次,最后一次返回null会调用stop。cat又会再调用一次,调start后直接返回null到stop。

4.内核函数调用堆栈

WARN_ON(condition)函数实际也是调用dump_stack,如WARN_ON(1)条件判断成功,类似BUGON(1)。

5.printk
cat /proc/cmdline查看console=ttyS0,如下图左边分支是远程登录,缺点是log_buf是环形buff(会循环覆盖,日志不全)。左右分支都有dmesg,左分支dmesg不受下面x控制,所以dmesg是最全的。


c
// Linux 内核为printk定义了8个打印等级,KERN_EMERG等级最高,KERN_DEBUG等级最低。在内核配置时,有一个宏来设定系统默认的打印等级 CONFIG_MESSAGE_LOGLEVEL_DEFAULT,通常该值设置为4,那么只有打印等级高于4时才会打印到终端或者串口。
// kern_levels.h
#define KERN_EMERG KERN_SOH "0" /* system is unusable 紧急事件,一般是系统崩溃之前的提示消息 */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* critical conditions 临界状态,通常涉及严重的硬件或者软件操作失败 */
#define KERN_ERR KERN_SOH "3" /* error conditions 报告错误状态,经常用来报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* warning conditions 对可能出现问题的情况进行警告,通常不会对系统造成严重问题 */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition 有必要的提示,通常用于安全相关的状况汇报 */
#define KERN_INFO KERN_SOH "6" /* informational 提示信息,驱动程序常用来打印硬件信息 */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages 用于调试信息 */
c
printk("[me]%s[%d].\n",__func__,__LINE__);
printk调用printk_safe_enter_irqsave --> local_irq_save,local_irq_save的调用把当前的中断状态(开或关)保存到flags中,然后禁用当前处理器上的中断,所以:
printk----不是随意就可以添加 (会影响性能) ------ 延申出动态打印
板卡 ----> 没有办法跟外界通信(I2C、UART、SPI都用到中断)

6.动态打印
printk是全局的且只能设打印等级,动态打印可控制选择模块的打印,在内核配置打开CONFIG_DYNAMIC_DEBUG
printk会关中断影响性能,如果在usb的read/write里printk,那么usb就没法直接用了。我想加很多调试信息,但是不想影响linux性能,所以用动态打印,调试时才打开,control节点默认不输出,如下操作才输出,+p是转为printk,相当于下面的define dev_dbg ...。


4.top:idle和wait是cpu不工作时间
如下是top命令:idle是cpu无事可做,而wait是cpu想做事却做不了,所以wait是特殊的idle,是cpu上至少一个线程阻塞在i/o时的idle。
用户层us:这个比例越高,说明cpu利用率越好,因为我们目的就是为了让cpu将更多的时间用在执行用户代码上。
内核sy:比例越低越好,因为cpu不应该把时间浪费在执行内核函数上。
ni:运行一个进程后,默认nice值为0,通过renice增加该进程nice值(设为正数),该进程的CPU利用率会在ni中显示,减少进程nice值(设为负数)则不会在这里显示。
空闲id:整体cpu利用率=100减去id,为了提高资源利用率,目标是降低id。
wa:cpu阻塞在i/o上时间,如cpu从磁盘读取文件内容时,由于磁盘i/o太慢,导致cpu不得不等待数据就绪,才能继续执行下一步操作,这个值越高,说明i/o处理能力出问题。
硬中断hi:cpu消耗在硬中断里时间,正常情况这个值很低,因为中断处理很快,即使有大量硬件中断也不会消耗很多cpu时间。
软中断si:cpu消耗在软中断里时间,如进行网络数据大量收发,si会升高,因为有tx软中断和rx软中断处理才能发数据。

5.dstat:监视系统资源使用,/proc/interruptes(硬中断),/proc/irq/[irq_num]/smp_affinity是一个十六进制数值(每个位对应一个 CPU 核心)

如下软中断(如syscall函数,产生软中断的进程一定是当前正在运行的进程,不会中断CPU,但会中断调用代码的流程)。



6.perf:函数调用占比


如下记录过去60秒。






7.ftrace:系统层面,功能需要打开,image大小会变大
如下查看debugfs和tracefs挂载点。

如下文件包含可追踪的所有场景。







如下案例:mpstat,perf(看函数执行次数),ftrace看函数执行时间。





如下sys_sync是系统调用。

如下可以打印出时间。

