热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

驱动调试(二)-环形缓冲区到文件

目录驱动调试(二)-环形缓冲区到文件目标框架分析虚拟文件系统procdmesgproc_misc_init

目录

  • 驱动调试(二)-环形缓冲区到文件
    • 目标
    • 框架分析
      • 虚拟文件系统proc
      • dmesg
      • proc_misc_init
      • kmsg_read
      • do_syslog
    • 程序1创建文件
    • 程序2提供读函数
    • 程序3读全局数组
    • 程序4 环形缓冲区+休眠唤醒
      • 打印驱动
      • 测试驱动
      • 应用程序
      • 测试
    • 程序5 cat后保留数据
      • 完整的程序
      • 测试驱动
      • 应用程序
      • 测试
    • 环形缓冲区
      • 读后清除
      • 读后不清除

title: 驱动调试(二)-环形缓冲区到文件
date: 2019/1/10 22:57:04
toc: true
---

驱动调试(二)-环形缓冲区到文件

目标

  • printk是将信息先保存到log_buf,然后通过打印级别来选择是否输出.
  • log_buf存储在/proc/kmsg中,该文件是包含了打印级别的
  • 使用cat去获取这个文件是读后清的,使用dmsg是允许反复读的

参考上述的描述,尝试达成如下目标

  1. 构造一个my_log_bug[],存储到文件/proc/mymsg
  2. 提供read的接口供cat使用,使用环形缓冲区保存,提供读后清和读后不清的版本
  3. 驱动程序调用my_printk输出到my_log_bug写入

框架分析

虚拟文件系统proc

我们的/proc实际上是一个虚拟的文件系统,我们使用mount或者cat /proc/mount来查看挂接了哪些

# mount
rootfs on / type rootfs (rw)
/dev/root on / type yaffs (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
tmpfs on /dev type tmpfs (rw)
devpts on /dev/pts type devpts (rw)
# cat /proc/mounts
rootfs / rootfs rw 0 0
/dev/root / yaffs rw 0 0
proc /proc proc rw 0 0
sysfs /sys sysfs rw 0 0
tmpfs /dev tmpfs rw 0 0
devpts /dev/pts devpts rw 0 0

这个文件系统是我们在脚本文件中指挂载的,mount -a表示挂载所有/etc/fstab的文件系统

# cat /etc/init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

# cat /etc/fstab
#device mount-ponit type options dump fsck
proc    /proc   proc    defaults    0   0
sysfs   /sys    sysfs   defaults    0   0
tmpfs   /dev    tmpfs   defaults    0   0

dmesg

我们在printk中可以指定级别来输出打印,可以使用dmesg来查看所有的信息log_buf,这个命令实际是去读取文件/proc/kmsg,可以直接使用cat来读取这个信息

注意 这个文件只能cat一次,然后就清空了,使用dmesg可以多次查看的,使用cat命令是能够看到打印级别的

# cat /proc/kmsg
]=PATH=/sbin:/bin:/usr/sbin:/usr/bin
<4>envp[2]=ACTION=add
<4>envp[3]=DEVPATH=/class/tty/ttyw9
<4>envp[4]=SUBSYSTEM=tty

proc_misc_init

搜索kmsg,找到文件fs\proc\proc_misc.c,接下来开始分析了,我们从入口函数开始分析proc_misc_init

创建一个文件kmsg ,父目录是proc_root,创建成功则同时提供相应的读写操作

#ifdef CONFIG_PRINTK
    {
        struct proc_dir_entry *entry;
        // 创建一个文件 kmsg ,父目录是 proc_root
        entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
        //创建成功则同时提供相应的读写操作
        if (entry)
            entry->proc_fops = &proc_kmsg_operations;
    }
#endif


const struct file_operations proc_kmsg_operatiOns= {
    .read       = kmsg_read,
    .poll       = kmsg_poll,
    .open       = kmsg_open,
    .release    = kmsg_release,
};

参见程序1,创建mymsg目录

kmsg_read

  1. 判断如果是非阻塞方式打开,且没有数据,直接返回
  2. 如果是阻塞方式打开,等待读取
static ssize_t kmsg_read(struct file *file, char __user *buf,
             size_t count, loff_t *ppos)
{
    if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))
        return -EAGAIN;
    return do_syslog(2, buf, count);
}

// 非阻塞方式判断是否是空
do_syslog(9, NULL, 0))
    case 9:     /* Number of chars in the log buffer */
        error = log_end - log_start;
        break;

//阻塞方式,进入休眠唤醒了
    case 2:     /* Read from log */
            error = -EINVAL;
            if (!buf || len <0)
                goto out;
            error = 0;
            if (!len)
                goto out;
            if (!access_ok(VERIFY_WRITE, buf, len)) {
                error = -EFAULT;
                goto out;
            }
            //这里判断数据是否为空,wait_event_interruptible 中第二个参数为0是睡眠
            error = wait_event_interruptible(log_wait,
                                (log_start - log_end));
            if (error)
                goto out;
            i = 0;
            spin_lock_irq(&logbuf_lock);
            while (!error && (log_start != log_end) && i 

do_syslog

  • 非阻塞方式,直接看看属否有数据
  • 阻塞方式,数据为空则睡眠等待

程序1创建文件

仿照着写一个驱动,产生一个 my_msg 的文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

struct proc_dir_entry *my_entry;
const  struct  file_operations proc_mymsg_operations;

static int hello_init(void)
{

    my_entry = create_proc_entry("mymsg", S_IRUSR, &proc_root);
    if (my_entry)
        my_entry->proc_fops = &proc_mymsg_operations;
    return 0;
}
static void hello_exit(void)
{
    remove_proc_entry("mymsg",&proc_root);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

测试下,确实生成了文件,无法cat是因为没有提供读写函数

# insmod mymsg.ko
# ls /proc/mymsg -l
-r--------    1 0        0               0 Jan  5 04:38 /proc/mymsg
# cat /proc/mymsg
cat: read error: Invalid argument

程序2提供读函数

我们提供下读函数,避免cat报错

ssize_t *mymsg_read (struct file *  myfile , char __user *  myuser , size_t   len , loff_t * myloff )
{
    printk("print by mymsg\n");
    return 0;  //这里如果不return0 ,就一直打印了
}
const  struct  file_operations proc_mymsg_operatiOns=
{
    .read=mymsg_read,
};

测试如下

# insmod mymsg.ko
# cat /proc/mymsg
print by mymsg

程序3读全局数组

这里提供一个全局数组,复制到用户态

struct proc_dir_entry *my_entry;
static char mylog_buf[1024];

ssize_t *mymsg_read (struct file *  myfile , char __user *  myuser , size_t   len , loff_t * myloff )
{
    //printk("print by mymsg\n");
    copy_to_user(myuser,mylog_buf,10);
    return 10;
}
static int hello_init(void)
{
    sprintf(mylog_buf,"this is a log buf\n");
    ...
}

测试后发现一直打印,这是引文read函数一直有返回,应该是cat后不断去read的原因

# cat /proc/mymsg
this is a this is a this is a this is a 
this is a this is a this is a this is a 

程序4 环形缓冲区+休眠唤醒

环形缓冲区就是有头尾指针的一个数组,这里有一个巧妙的判断是否为满的方法

写的位置+1====读的位置,则是满
  • 空 读指针=写指针
  • 满 写指针=读指针+1

具体的函数如下

static int is_mylog_empty(void)
{
    return (mylog_r == mylog_w);
}

static int is_mylog_full(void)
{
    return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}

static void mylog_putc(char c)
{
    if (is_mylog_full())
    {
        /* 丢弃一个数据 */
        mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
    }
    mylog_buf[mylog_w] = c;
    mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;
}

static int mylog_getc(char *p)
{
    if (is_mylog_empty())
    {
        return 0;
    }
    *p = mylog_buf[mylog_r];
    mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
    return 1;
}

接下来使用唤醒队列来处理,也就是当读取的时候如果没有数据,则睡眠,写数据的时候触发休眠的队列

static void mylog_putc(char c)
{
    写操作
    ...
    /* 唤醒等待数据的进程 */ 
    wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */
}

接着根据原有的.read=kmsg_read函数模仿写一个

static ssize_t mymsg_read(struct file *file, char __user *buf,
             size_t count, loff_t *ppos)
{
    int error = 0;
    int i = 0;
    char c;

    // 非阻塞方式读取,没有数据的时候直接返回
    if ((file->f_flags & O_NONBLOCK) && is_mylog_empty())
        return -EAGAIN;
    
    //阻塞方式 如果为空则睡眠
    error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty());
    // 唤醒后,也就是有数据,读取数据复制到用户态
    while (!error && (mylog_getc(&c)) && i 

创建一个printf函数,参考printk中将缓存赋值中使用了

printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);

int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
    int i;

    i=vsnprintf(buf,size,fmt,args);
    return (i >= size) ? (size - 1) : i;
}

或者看下
int sprintf(char * buf, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i=vsprintf(buf,fmt,args);
    va_end(args);
    return i;
}
int myprintk(const char *fmt, ...)
{
    va_list args;
    int i;
    int j;

    va_start(args, fmt);
    i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
    va_end(args);
    
    for (j = 0; j 

打印驱动

提供myprintk供其他驱动程序调用写入缓冲

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
extern int myprintk(const char *fmt, ...);
EXPORT_SYMBOL(myprintk);

static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);
struct proc_dir_entry *my_entry;
#define LEN_LOG 1024
static char mylog_buf[LEN_LOG];
static char tmp_buf[LEN_LOG];
static int pt_read=0,pt_write=0;


#define pt_add(pt)    ((pt+1)%LEN_LOG)

// ret =1 means empty
int  isEmpty(void)
{
    return (pt_read == pt_write);
}

// ret =1 means full
int  isFull(void)
{
    return (pt_read == pt_add(pt_write));
}
//putchar
void myputc(char c)
{
    if (isFull()) {
        pt_read = pt_add(pt_read);
    }

    mylog_buf[pt_write]=c;
    pt_write=pt_add(pt_write);
    /* 唤醒等待数据的进程 */ 
    wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */    
}

//getchar
int  mygetchar(char * p)
{
    if (isEmpty()) {
        return 0;
    }
    *p = mylog_buf[pt_read];
    pt_read=pt_add(pt_read);
    return 1;
}

//printf for user
int myprintk(const char *fmt, ...)
{
    va_list args;
    int i;
    int j;

    va_start(args, fmt);
    i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
    va_end(args);
    
    for (j = 0; j f_flags & O_NONBLOCK) && isEmpty())
        return -EAGAIN;

    error = wait_event_interruptible(mymsg_waitq, !isEmpty());

    /* copy_to_user */
    while (!error && (mygetchar(&c)) && i proc_fops = &proc_mymsg_operations;
    return 0;
}

static void hello_exit(void)
{
    remove_proc_entry("mymsg",&proc_root);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

测试驱动

调用myprintkwrite时写入缓冲

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev;
extern int myprintk(const char *fmt, ...);
static int first_drv_open(struct inode *inode, struct file *file)
{
    static int cnt = 0;
    myprintk("first_drv_open : %d\n", ++cnt);
    return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    int val;
    static int cnt = 0;
    myprintk("first_drv_write : %d\n", ++cnt);
    return 0;
}

static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,  
    .open   =   first_drv_open,     
    .write  =   first_drv_write,       
};


int major;
static int first_drv_init(void)
{
    myprintk("first_drv_init\n");

    major = register_chrdev(0, "first_drv", &first_drv_fops); 
    firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); 
    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv"); 
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

应用程序

调用open打开测试驱动,使用write以调用myprintk写入缓冲


#include 
#include 
#include 
#include 

/* firstdrvtest on
  * firstdrvtest off
  */
int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    fd = open("/dev/xyz", O_RDWR);
    if (fd <0)
    {
        printf("can't open!\n");
    }
    if (argc != 2)
    {
        printf("Usage :\n");
        printf("%s \n", argv[0]);
        return 0;
    }

    if (strcmp(argv[1], "on") == 0)
    {
        val  = 1;
    }
    else
    {
        val = 0;
    }
    
    write(fd, &val, 4);
    return 0;
}

测试

  1. 加载两个驱动

    # insmod ../mymsg.ko
    # insmod first_drv.ko
  2. 加载驱动程序

    # ./test on
    # ./test off
  3. 获取打印信息

    # cat /proc/mymsg &
    first_drv_init
    first_drv_open : 1
    first_drv_write : 1
    first_drv_open : 2
    first_drv_write : 2

程序5 cat后保留数据

在这里其实更应该理解成三个指针

  • 头指针,指向数据有效区域头

  • 尾指针,指向数据有效区的尾巴

  • 读指针,当前读取的区域

修改的部分

  1. 判断空的函数,应该判断读指针是否到达尾指针

    int  isEmpty(void)
    {
        return (pt_now_read == pt_write);
    }
  2. 读取函数,其中的读指针更改为这个新增的指针

    //getchar
    int  mygetchar(char * p)
    {
        if (isEmpty()) {
            return 0;
        }
        *p = mylog_buf[pt_now_read];
        pt_now_read=pt_add(pt_now_read);
        return 1;
    }
  3. 写数据的时候,如果写入的数据一次性超过缓冲区的大小,比如 缓冲区比较小,一次写入大于缓冲

    也就是比如当前是 start=3,end=2,now=2,存入数据后依然是start=3,end=2,now=2,这个时候需要手动调整now=start

    mark

    //putchar
    void myputc(char c)
    {
        if (isFull()) {
            pt_read = pt_add(pt_read);
    
            // 这里其实就是判断 当前读的指针在逻辑上必须大于有数据的 读的指针,也就是数据起始指针
            if (pt_add(pt_now_read) == pt_read) {
    #if(1)
                    pt_now_read=pt_read;
    #endif
                    printk("<<<>>> \n");
            }
    
        }
        mylog_buf[pt_write]=c;
        pt_write=pt_add(pt_write);
         printk("put in %d  \n",pt_write);
        /* 唤醒等待数据的进程 */ 
        wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */    
    }

完整的程序

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
extern int myprintk(const char *fmt, ...);
EXPORT_SYMBOL(myprintk);
extern void  get_pt(void);
EXPORT_SYMBOL(get_pt);
static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);
struct proc_dir_entry *my_entry;
#define LEN_LOG 23
static char mylog_buf[LEN_LOG];
static char tmp_buf[LEN_LOG];
static int pt_read=0,pt_write=0;
static int pt_now_read=0;
//printf for user
void  get_pt(void )
{
    printk("<<>>  \n", pt_read, pt_write, pt_now_read);
}
#define pt_add(pt)    ((pt+1)%LEN_LOG)
// ret =1 means empty
int  isEmpty(void)
{
    return (pt_now_read == pt_write);
}
// ret =1 means full
int  isFull(void)
{
    return (pt_read == pt_add(pt_write));
}
//putchar
void myputc(char c)
{
    if (isFull()) {
        pt_read = pt_add(pt_read);

        // 这里其实就是判断 当前读的指针在逻辑上必须大于有数据的 读的指针,也就是数据起始指针
        if (pt_add(pt_now_read) == pt_read) {
#if(1)
                pt_now_read=pt_read;
#endif
                printk("<<<>>> \n");
        }

    }
    mylog_buf[pt_write]=c;
    pt_write=pt_add(pt_write);
     printk("put in %d  \n",pt_write);
    /* 唤醒等待数据的进程 */ 
    wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */    
}

//getchar
int  mygetchar(char * p)
{
    if (isEmpty()) {
        return 0;
    }
    *p = mylog_buf[pt_now_read];
    pt_now_read=pt_add(pt_now_read);
    return 1;
}

//printf for user
int myprintk(const char *fmt, ...)
{
    va_list args;
    int i;
    int j;

    va_start(args, fmt);
    i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
    va_end(args);
    
    for (j = 0; j f_flags & O_NONBLOCK) && isEmpty())
        return -EAGAIN;

    error = wait_event_interruptible(mymsg_waitq, !isEmpty());

    /* copy_to_user */
    while (!error && (mygetchar(&c)) && i proc_fops = &proc_mymsg_operations;
    return 0;
}
static void hello_exit(void)
{
    remove_proc_entry("mymsg",&proc_root);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

测试驱动

更改下测试驱动,使得有方法显示当前的指针 调用get_pt显示当前指针

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev;
extern int myprintk(const char *fmt, ...);
static int first_drv_open(struct inode *inode, struct file *file)
{
    //static int cnt = 0;
    //myprintk(">>Open>>%d\n", ++cnt);
    return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    int val;
    static int cnt = 0;
    copy_from_user(&val,buf,count);
    if (val==0) {
        get_pt();
    }
    else
    {
        myprintk(">>1234567890123456Read>>%d\n", ++cnt);
    }
    return 0;
}

static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,  
    .open   =   first_drv_open,     
    .write  =   first_drv_write,       
};


int major;
static int first_drv_init(void)
{
    //myprintk("first_drv_init\n");

    major = register_chrdev(0, "first_drv", &first_drv_fops); 
    firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); 
    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv"); 
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

应用程序

更改下应用程序使得有方法显示当前的指针./test show

#include 
#include 
#include 
#include 

int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    fd = open("/dev/xyz", O_RDWR);
    if (fd <0)
    {
        printf("can't open!\n");
    }
    if (argc != 2)
    {
        printf("Usage :\n");
        printf("%s \n", argv[0]);
        return 0;
    }

    if (strcmp(argv[1], "on") == 0)
    {
        val  = 1;
    }
    else
    {
        val  = 0;

    }
    write(fd, &val, 4);
    return 0;
}

测试

  1. 加载驱动
    shell mount -t nfs -o nolock,vers=2 192.168.95.222:/home/book/stu /mnt insmod ../mymsg.ko && insmod first_drv.ko && cat /proc/mymsg & rmmod first_drv && rmmod mymsg echo "7 1 4 7 "> /proc/sys/kernel/printk
  2. 运行测试程序
    shell ./test on #写入缓冲区 ./test on1 # 显示当前的三个 头指针,尾指针,以及当前的读指针

  3. 测试错误的驱动,这里驱动(mymsg)程序,我测试了两个版本,一个是写数据的时候不判断是否一次就写满缓冲,另外一个是判断写缓冲的,可以发现不判断写缓冲的,打印输出不对

    # ./test show
    <<>>
    # ./test on
    put in 1
    put in 2
    put in 3
    put in 4
    put in 5
    put in 6
    put in 7
    put in 8
    put in 9
    put in 10
    put in 11
    put in 12
    put in 13
    put in 14
    put in 15
    put in 16
    put in 17
    put in 18
    put in 19
    put in 20
    put in 21
    put in 22
    <<<>>>
    put in 0
    put in 1
    put in 2
    put in 3
    >1                      ########这里打印明显出错了,缓冲区已经改变了起始位置
    # ./test show
    <<>>
  4. 测试正确的驱动程序

    # mount -t nfs -o nolock,vers=2 192.168.95.222:/home/book/stu /mnt
    #
    # cd /mnt/code/first_drv_myprintk/
    # insmod ../mymsg.ko && insmod first_drv.ko && cat /proc/mymsg &
    #
    # echo "7 1 4 7 "> /proc/sys/kernel/printk
    # ./test show
    <<>>
    # ./test on
    put in 1
    put in 2
    put in 3
    put in 4
    put in 5
    put in 6
    put in 7
    put in 8
    put in 9
    put in 10
    put in 11
    put in 12
    put in 13
    put in 14
    put in 15
    put in 16
    put in 17
    put in 18
    put in 19
    put in 20
    put in 21
    put in 22
    <<<>>>
    put in 0
    <<<>>>
    put in 1
    <<<>>>
    put in 2
    <<<>>>
    put in 3
    # 34567890123456Read>>1         #############打印正确
    
    # ./test show
    <<>>
    

环形缓冲区

读后清除

  1. 空 读指针=写指针
  2. 满 写指针=读指针+1

读后不清除

  1. 定义为头,尾指针,读指针
  2. 空 头指针=尾指针
  3. 满 头指针=尾指针+1
  4. 一般来说,缓冲区一直处于满的状态工作
  5. 第一次读取的时候直接从头指针开始获取
  6. 持续后台读取的时候,需要判断是否有一次性塞满一个缓存循环,也就是写入的时候,判断当前的读指针+1如果等于头指针,说明一个缓存满,需要移动读指针,具体见图片分析

mark


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
愤然尔立_980
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有