【notes13】ioctl,休眠&唤醒,proc文件系统,调用堆栈函数,printk,动态打印,top,dstat,perf,ftrace

文章目录


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是系统调用。

如下可以打印出时间。

相关推荐
红豆子不相思1 小时前
virual serve
linux·运维·服务器
zl_dfq2 小时前
Linux 之 【网络套接字编程】(网络字节序、字节序转换函数、套接字编程类型、标准套接字编程的头文件、sockaddr结构、整数IP与字符串IP的转换)
linux·网络
不知名。。。。。。。。2 小时前
Linux---序列化
linux
江畔何人初2 小时前
MySQL 服务器进程的三层结构
linux·运维·服务器·云原生·mysal
陈桴浮海2 小时前
MySQL 主从复制与 GTID 环形复制
linux·mysql·云原生
白太岁2 小时前
C++:(6) 常用 linux 命令:进程管理、日志查看、网络端口与文件权限
linux·运维·服务器
MMME~2 小时前
HAProxy:高性能负载均衡实战指南
linux·运维·数据库
野指针YZZ2 小时前
Gstreamer插入第三方plugins流程:rgaconvert
linux·音视频·rk3588
快快起来写代码2 小时前
Arrays.asList方法踩坑
linux