Android 块设备驱动之二

2023-11-08

将块设备添到系统

调用void blk_register_region()来将块设备注册到系统中,函数如下所示:

/*
 * Register device numbers dev..(dev+range-1)
 * range must be nonzero
 * The hash chain is sorted on range, so that subranges can override.
 */
void blk_register_region(dev_t devt, unsigned long range, struct module *module,
             struct kobject *(*probe)(dev_t, int *, void *),
             int (*lock)(dev_t, void *), void *data)
{
    kobj_map(bdev_map, devt, range, module, probe, lock, data);
}

该函数会将块设备添加到bdev_map中,这是一个有内核维护的数据库,包含了所有的块设备。在打开块设备是,必然会调用blkdev_get,而blkdev_get会对bdev_map进行查询

添加磁盘和分区到系统中

为了将磁盘添加到系统中,使其可用,必须初始化磁盘数据结构并调用void add_disk(struct gendisk *disk)方法。其中将磁盘和分区添加到系统中的整个调用流程如下图所示:

Created with Raphaël 2.1.0 alloc_dev alloc_dev add_disk add_disk genhd.c genhd.c block_dev.c block_dev.c partition_generic.c partition_generic.c blk_alloc_devt disk_alloc_events blk_register_region register_disk bdev->bd->invalidated=1 bkdev_get blkdev_get(bdev,mode,NULL) __blkdev_get(bdev,mode,0) rescan_partation(gendisk,block_dev) blk_register_queue disk_add_events

接下来我们分别研究研究在该流程中的一些重要函数都做了些什么重要的事情。

add_disk(gendisk*)函数

/**
 * add_disk - add partitioning information to kernel list
 * @disk: per-device partitioning information
 * This function registers the partitioning information in @disk
 * with the kernel.
 */
 //添加分区磁盘到分区系统
void add_disk(struct gendisk *disk)
{
    struct backing_dev_info *bdi;
    dev_t devt;
    int retval;

    /* minors == 0 indicates to use ext devt from part0 and should
     * be accompanied with EXT_DEVT flag.  Make sure all
     * parameters make sense.
     */
    WARN_ON(disk->minors && !(disk->major || disk->first_minor));
    WARN_ON(!disk->minors && !(disk->flags & GENHD_FL_EXT_DEVT));

    disk->flags |= GENHD_FL_UP;
    /**
     * blk_alloc_devt - allocate a dev_t for a partition
     * 根据磁盘的主次设备号信息为磁盘分配设备号
     * 这里要特别注意的是:blk_alloc_devt是阻塞的。。。
     */
    retval = blk_alloc_devt(&disk->part0, &devt);
    if (retval) {
        WARN_ON(1);
        return;
    }
    disk_to_dev(disk)->devt = devt;

    /* ->major and ->first_minor aren't supposed to be
     * dereferenced from here on, but set them just in case.
     */
    disk->major = MAJOR(devt);
    disk->first_minor = MINOR(devt);
    //调用disk_alloc_events初始化磁盘的事件(alloc|add|del|release)处理机制。
    //在最开始磁盘事件会被设置为被阻塞的。
    disk_alloc_events(disk);

    //调用bdi_register_dev将磁盘注册到bdi
    bdi = &disk->queue->backing_dev_info;
    bdi_register_dev(bdi, disk_devt(disk));
    //调用blk_register_region将磁盘添加到bdev_map中
    blk_register_region(disk_devt(disk), disk->minors, NULL,
                exact_match, exact_lock, disk);
    /* 调用register_disk将磁盘添加到系统中。主要完成:
     * 将主设备的分区(第0个分区)信息标记设置为分区无效
     * 调用device_add将设备添加到系统中
     * 在sys文件系统中为设备及其属性创建目录及文件
     * 发出设备添加到系统的uevent事件(如果能获取分区的信息,则也为分区发送uevent事件)。
     */
    register_disk(disk);
    /*
    调用blk_register_queue注册磁盘的请求队列。主要是为队列和队列的调度器在设备的
    sys文件系统目录中创建相应的sys目录/文件,并且发出uevent事件。
    */
    blk_register_queue(disk);
    /*
     * Take an extra ref on queue which will be put on disk_release()
     * so that it sticks around as long as @disk is there.
     */
    WARN_ON_ONCE(!blk_get_queue(disk->queue));

    retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
                   "bdi");
    WARN_ON(retval);
    /*
    调用__disk_unblock_events完成
    在/sys文件系统的设备目录下创建磁盘的事件属性文件
    将磁盘事件添加到全局链表disk_events中
    解除对磁盘事件的阻塞。
    */
    disk_add_events(disk);
}

register_disk

static void register_disk(struct gendisk *disk){
    //......
    //该设备上分区是否有效,1表示无效,如果无效,则下次打开时会重新扫描分区表
    bdev->bd_invalidated = 1;
    err = blkdev_get(bdev, FMODE_READ, NULL);
    //......
}

add_partition()

struct hd_struct *add_partition(struct gendisk *disk, int partno,
                sector_t start, sector_t len, int flags,
                struct partition_meta_info *info)
{
    struct hd_struct *p;
    dev_t devt = MKDEV(0, 0);
    struct device *ddev = disk_to_dev(disk);
    struct device *pdev;
    struct disk_part_tbl *ptbl;
    const char *dname;
    int err;

    err = disk_expand_part_tbl(disk, partno);
    //......
    seqcount_init(&p->nr_sects_seq);
    pdev = part_to_dev(p);

    p->start_sect = start;
    p->alignment_offset =
        queue_limit_alignment_offset(&disk->queue->limits, start);
    p->discard_alignment =
        queue_limit_discard_alignment(&disk->queue->limits, start);
    p->nr_sects = len;
    p->partno = partno;
    p->policy = get_disk_ro(disk);

    if (info) {
        struct partition_meta_info *pinfo = alloc_part_info(disk);
        if (!pinfo)
            goto out_free_stats;
        memcpy(pinfo, info, sizeof(*info));
        p->info = pinfo;
    }

    dname = dev_name(ddev);
    if (isdigit(dname[strlen(dname) - 1]))
        dev_set_name(pdev, "%sp%d", dname, partno);
    else
        dev_set_name(pdev, "%s%d", dname, partno);

    device_initialize(pdev);
    pdev->class = &block_class;
    pdev->type = &part_type;
    pdev->parent = ddev;

    err = blk_alloc_devt(p, &devt);
    if (err)
        goto out_free_info;
    pdev->devt = devt;

    /* delay uevent until 'holders' subdir is created */
    dev_set_uevent_suppress(pdev, 1);
    err = device_add(pdev);
    if (err)
        goto out_put;

    err = -ENOMEM;
    p->holder_dir = kobject_create_and_add("holders", &pdev->kobj);
    if (!p->holder_dir)
        goto out_del;

    dev_set_uevent_suppress(pdev, 0);
    if (flags & ADDPART_FLAG_WHOLEDISK) {
        err = device_create_file(pdev, &dev_attr_whole_disk);
        if (err)
            goto out_del;
    }

    /* everything is up and running, commence */
    rcu_assign_pointer(ptbl->part[partno], p);

    /* suppress uevent if the disk suppresses it */
    if (!dev_get_uevent_suppress(ddev))
        kobject_uevent(&pdev->kobj, KOBJ_ADD);

    hd_ref_init(p);
    return p;
    //....
}

块设备操作

在所有的文件系统中,在获取文件的inode时,对于不是常规文件的特殊文件都会调用init_special_inode,对于块设备文件,该函数会将inode的文件操作函数结构设置为def_blk_fops (定义如下),其中打开文件的函数原型为:blkdev_open
const struct file_operations def_blk_fops 定义:

const struct file_operations def_blk_fops = {
    .open       = blkdev_open,//打开文件函数
    .release    = blkdev_close,
    .llseek     = block_llseek,
    .read       = new_sync_read,
    .write      = new_sync_write,
    .read_iter  = blkdev_read_iter,
    .write_iter = blkdev_write_iter,
    .mmap       = generic_file_mmap,
    .fsync      = blkdev_fsync,
    .unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = compat_blkdev_ioctl,
#endif
    .splice_read    = generic_file_splice_read,
    .splice_write   = iter_file_splice_write,
};

打开文件函数为blkdev_open。其原型为:

static int blkdev_open(struct inode * inode, struct file * filp){
    struct block_device *bdev;
    /*
     * Preserve backwards compatibility and allow large file access
     * even if userspace doesn't ask for it explicitly. Some mkfs
     * binary needs it. We might want to drop this workaround
     * during an unstable branch.
     */
    filp->f_flags |= O_LARGEFILE;

    if (filp->f_flags & O_NDELAY)
        filp->f_mode |= FMODE_NDELAY;
    if (filp->f_flags & O_EXCL)
        filp->f_mode |= FMODE_EXCL;
    if ((filp->f_flags & O_ACCMODE) == 3)
        filp->f_mode |= FMODE_WRITE_IOCTL;

    bdev = bd_acquire(inode);
    if (bdev == NULL)
        return -ENOMEM;

    filp->f_mapping = bdev->bd_inode->i_mapping;

    return blkdev_get(bdev, filp->f_mode, filp);
}

参数的含义很明显。它完成的工作有:

  • 调用bd_acquire(struct inode *)来获取块设备文件的block_device结构。该函数会调用bdget尝试从bdev文件系统中查找设备文件对应的inode,如果有就直接返回,如果没有就会分配一个新的inode并且初始化该inode在返回。设备文件的inode会被添加到block_devicebd_inodes中。设备对应的block_device也会在这一步被添加到全局的all_bdevs
  • 设置file的结构f_mappingbdev->bd_inode->i_mapping(filp->f_mapping = bdev->bd_inode->i_mapping;)。bdev->bd_inode在inode的创建和初始化中被初始化,具体的函数为alloc_inodegdget。其中的address_space_operations被设置为def_blk_aops,这是和设备交互的接口
  • 调用blkdev_get。该函数主要的工作是完成设备的打开动作,同事根据传入的模式还可能声明设备的持有者。其实质上blkdev_get的实质性动作是由__blkdev_get完成的。__blkdev_get主要做了如下几件事情
    • 调用get_gendisk获取块设备所对应的通用磁盘结构,这里可能需要查询bdev_map数据库
    • 阻塞磁盘的事件处理。如果是第一次打开该设备,则
      • 填充块设备数据结构bd_disk ,bd_queue,bd_contains
      • 如果是主设备(即不是分区),则:
        • 设置块设备数据结构的bd_part
        • 如果提供了disk->fops-open则调用
        • 如果分区无效bdev->bd_invalidate,则调用rescan_partitions重新扫描分区
        • 如果打开设备时返回了ENOMEDIUM错误,则条用invalidate_partitions将所有分区设置为无效
      • 如果是分区设备,则:
        • 获取主设备的块设备数据结构
        • 递归调用__blkdev_get,但是这次传入的是主设备的块设备数据结构。本次调用会走第一次打开设备并且是主设备的分支,由于是第一次打开,而且分区信息应该是无效的,这就会走到重新扫描分区的分支。
        • 设置块设备数据结构的bd_contains(它被设置为主设备的block_device)
        • 调用bd_set_size设置分区的大小信息
      • 否则如果不是第一次打开设备,则
        • 如果是主设备(这里通过bdev->bd_contains==bdev来进行性判断),参照该函数之前的流程,只有为主设备是该条件才成立,
        • 依据disk->fops->open的情况进行调用,以及按照不同条件分别调用rescan_partitions invalidate_partitions
        • 增加设备的打开技术,解除对设备时间的阻塞。

从以上blkdev_open打开的细节也可以看到,blkdev_open确实会调用驱动所提供的open函数,驱动可以在open中完成打开设备的必要工作。在打开之后设备就可以被使用了。

读写操作

在介绍具体的写操作之前,先研究下与读写操作紧密相关的数据结构

待更新

请求操作

当内核通过address_space_operations中的成员函数向设备发起读写操作时,读写操作都会被转变成一个对块设备的IO请求提交给设备。内核使用数据结构struct bio来表示一个对块设备的IO,其定义如下:

/*
 * main unit of I/O for the block layer and lower layers (ie drivers and
 * stacking drivers)
 */
struct bio {
    struct bio      *bi_next;   /* request queue link */
    struct block_device *bi_bdev;
    unsigned long       bi_flags;   /* status, command, etc */
    /* bottom bits READ/WRITE,
     * top bits priority
    */
    unsigned long       bi_rw;

    struct bvec_iter    bi_iter;
    /* Number of segments in this BIO after
     * physical address coalescing is performed.
     */
    unsigned int        bi_phys_segments;
    /*
     * To keep track of the max segment size, we account for the
     * sizes of the first and last mergeable segments in this bio.
     */
    unsigned int        bi_seg_front_size;
    unsigned int        bi_seg_back_size;

    atomic_t        bi_remaining;
    bio_end_io_t        *bi_end_io;
    void            *bi_private;

#ifdef CONFIG_BLK_DEV_THROTTLING
    bio_throtl_end_io_t *bi_throtl_end_io1;
    void            *bi_throtl_private1;
    bio_throtl_end_io_t *bi_throtl_end_io2;
    void            *bi_throtl_private2;
#endif
#ifdef CONFIG_BLK_CGROUP
    /*
     * Optional ioc and css associated with this bio.  Put on bio
     * release.  Read comment on top of bio_associate_current().
     */
    struct io_context   *bi_ioc;
    struct cgroup_subsys_state *bi_css;
#endif
    union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
        struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
    };

    unsigned short      bi_vcnt;    /* how many bio_vec's */
    /*
     * When using dircet-io (O_DIRECT), we can't get the inode from a bio
     * by walking bio->bi_io_vec->bv_page->mapping->host
     * since the page is anon.
     */
    struct inode        *bi_dio_inode;

    /*
     * Everything starting with bi_max_vecs will be preserved by bio_reset()
     */

    unsigned short      bi_max_vecs;    /* max bvl_vecs we can hold */

    atomic_t        bi_cnt;     /* pin count */

    struct bio_vec      *bi_io_vec; /* the actual vec list */

    struct bio_set      *bi_pool;

    /*
     * We can inline a number of vecs at the end of the bio, to avoid
     * double allocations for a small number of bio_vecs. This member
     * MUST obviously be kept at the very end of the bio.
     */
    struct bio_vec      bi_inline_vecs[0];
};

这个数据结构中,绝大部分可以参照注释对各个成员的含义进行理解;需要特别注意的是struct bio_vec *bi_io_vec;成员

/*
 * Request flags.  For use in the cmd_flags field of struct request, and in
 * bi_rw of struct bio.  Note that some flags are only valid in either one.
 */
enum rq_flag_bits {
    /* common flags */
    __REQ_WRITE,        /* not set, read. set, write */
    __REQ_FAILFAST_DEV, /* no driver retries of device errors */
    __REQ_FAILFAST_TRANSPORT, /* no driver retries of transport errors */
    __REQ_FAILFAST_DRIVER,  /* no driver retries of driver errors */

    __REQ_SYNC,     /* request is sync (sync write or read) */
    __REQ_META,     /* metadata io request */
    __REQ_PRIO,     /* boost priority in cfq */
    __REQ_DISCARD,      /* request to discard sectors */
    __REQ_SECURE,       /* secure discard (used with __REQ_DISCARD) */
    __REQ_WRITE_SAME,   /* write same block many times */

    __REQ_NOIDLE,       /* don't anticipate more IO after this one */
    __REQ_INTEGRITY,    /* I/O includes block integrity payload */
    __REQ_FUA,      /* forced unit access */
    __REQ_FLUSH,        /* request for cache flush */
    __REQ_POST_FLUSH_BARRIER,/* cache barrier after a data req */
    __REQ_BARRIER,      /* marks flush req as barrier */
    __REQ_BG,       /* background activity */
    __REQ_FG,       /* foreground activity */
    /* bio only flags */
    __REQ_RAHEAD,       /* read ahead, can fail anytime */
    __REQ_THROTTLED,    /* This bio has already been subjected to
                 * throttling rules. Don't do it again. */

    /* request only flags */
    __REQ_SORTED = __REQ_RAHEAD, /* elevator knows about this request */
    __REQ_SOFTBARRIER,  /* may not be passed by ioscheduler */
    __REQ_NOMERGE,      /* don't touch this for merging */
    __REQ_STARTED,      /* drive already may have started this one */
    __REQ_DONTPREP,     /* don't call prep for this one */
    __REQ_QUEUED,       /* uses queueing */
    __REQ_ELVPRIV,      /* elevator private data attached */
    __REQ_FAILED,       /* set if the request failed */
    __REQ_QUIET,        /* don't worry about errors */
    __REQ_PREEMPT,      /* set for "ide_preempt" requests and also
                   for requests for which the SCSI "quiesce"
                   state must be ignored. */
    __REQ_ALLOCED,      /* request came from our alloc pool */
    __REQ_COPY_USER,    /* contains copies of user pages */
    __REQ_FLUSH_SEQ,    /* request for flush sequence */
    __REQ_IO_STAT,      /* account I/O stat */
    __REQ_MIXED_MERGE,  /* merge of different types, fail separately */
    __REQ_PM,       /* runtime pm request */
    __REQ_HASHED,       /* on IO scheduler merge hash */
    __REQ_MQ_INFLIGHT,  /* track inflight for MQ */
    __REQ_URGENT,       /* urgent request */
    __REQ_NR_BITS,      /* stops here */
};

提交读写请求

这里submit_bio用于递交一个bio到block device层进行I/O操作。
kernel/block/blk-core.c

/**
 * submit_bio - submit a bio to the block device layer for I/O
 * @rw: whether to %READ or %WRITE, or maybe to %READA (read ahead)
 * @bio: The &struct bio which describes the I/O
 *
 * submit_bio() is very similar in purpose to generic_make_request(), and
 * uses that function to do most of the work. Both are fairly rough
 * interfaces; @bio must be presetup and ready for I/O.
 *
 */
void submit_bio(int rw, struct bio *bio)
{
    bio->bi_rw |= rw;

    /*
     * If it's a regular read/write or a barrier with data attached,
     * go through the normal accounting stuff before submission.
     */
    if (bio_has_data(bio)) {
        unsigned int count;

        //从request_queue队列中获取,或者直接获得sector的数据
        if (unlikely(rw & REQ_WRITE_SAME))
            count = bdev_logical_block_size(bio->bi_bdev) >> 9;
        else
            count = bio_sectors(bio);

        if (rw & WRITE) {
            count_vm_events(PGPGOUT, count);
        } else {
            task_io_account_read(bio->bi_iter.bi_size);
            count_vm_events(PGPGIN, count);
        }

#ifdef DCHECK_ROOT_FORCE
        check_wrt(rw, bio);
#endif

        if (unlikely(block_dump)) {
            char b[BDEVNAME_SIZE];
            struct task_struct *tsk;

            tsk = get_dirty_task(bio);
            printk(KERN_WARNING "%s(%d): %s block %Lu on %s (%u sectors)\n",
                tsk->comm, task_pid_nr(tsk),
                (rw & WRITE) ? "WRITE" : "READ",
                (unsigned long long)bio->bi_iter.bi_sector,
                bdevname(bio->bi_bdev, b),
                count);
        }
    }

    generic_make_request(bio);
}

接下来看看generic_make_request函数,用于处理一个设备驱动层面的用于I/O的buffer。其中参数struct bio描述了该buffer在内存中、在设备中的位置。

  • generic_make_request用来传递block devices 的I/O请求,所有的I/O所需的信息都被保存在struc bio数据结构中
  • generic_make_request不会有任何状态返回。当当前的request成功或者失败时,使用异步进行单独通知,通常由bio->bi_end_io所指向的函数完成。
  • generic_make_request函数调用者必须要保证如下条件:
    • bi_io_vec结构所指向的是正确的memory buffer;
    • bi_devbi_sector指向正确的地址
    • bio_end_iobi_private指向一个能对generic_make_request状态进行处理的函数
  • generic_make_request函数在某些request使用bi_next并需要合并的情况下会被调用;并递归调用该函数将**bio**submit到底层的设备。这也就意味着当调用了->make_request_fn之后bio就被禁止操作。
/**
 */
void generic_make_request(struct bio *bio)
{
    struct bio_list bio_list_on_stack;

    if (!generic_make_request_checks(bio))
        return;

    /* 我们希望函数`->make_request_fn`在同一时刻只被激活一次,当尝试第二次激活或            
     * 者称之为非当前首次使用者时会报错。所以,我们使用current->bio_list来对函数
     * `make_request_fn`所提交的request进行管理;current->bio_list通常也会被
     * 用作为标识generic_make_request是否在当前task中被激活的一个flag。
     * 如果是NULL,也就意味着没有make_request是激活的,
     * 如果不是NULL,那么一个make_request会被激活,一个新的request会被添加到尾部
     */
    if (current->bio_list) {
        bio_list_add(current->bio_list, bio);
        return;
    }
    /* 对于下面的循环,稍作注释:
     * 在进入循环之前`bio->bi->next`其实是NULL(在调用之前都被设置为NULL),所以 
     * 此时实际上只有一个单独的bio。因此模拟一个较长的bio list,新分配一个指针
     * 用于指向bio_list_on_stack,这样就类似于用bio_list_on_stack将bio_list
     * 进行了初始化,`->make_request`此时添加了更多的bios可用于递归调用
     */
    BUG_ON(bio->bi_next);
    bio_list_init(&bio_list_on_stack);
    current->bio_list = &bio_list_on_stack;
    do {
        struct request_queue *q = bdev_get_queue(bio->bi_bdev);

        q->make_request_fn(q, bio);

        bio = bio_list_pop(current->bio_list);
    } while (bio);
    current->bio_list = NULL; /* deactivate */
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 块设备驱动之二 的相关文章

  • 从 Python 调用 PARI/GP

    我想打电话PARI GP http pari math u bordeaux fr dochtml gpman html仅从Python计算函数nextprime n 对于不同的n是我定义的 不幸的是我无法得到帕里蟒蛇 http code
  • 如何在 Android TextView 中使用土耳其语字符,如“ş ç ı ö”?

    我想在 android TextView 中写入 ile 但它没有正确绘制 怎样才能使用这样的字符呢 例如 我将文本视图设置为 ile 它显示为 ile 我怎样才能解决这个问题 尝试以下方法 看看是否有帮助 source http grou
  • chown:不允许操作

    我有问题 我需要通过 php 脚本为系统中的不同用户设置文件所有者权限 所以我通过以下命令执行此操作 其中 1002 是系统的用户 ID file put contents filename content system chown 100
  • sendfile64 只复制约2GB

    我需要使用 sendfile64 复制大约 16GB 的文件 到目前为止我所取得的成就是 include
  • 位图内存不足错误

    我对这个错误有疑问 我从 URL 制作网站图标解析器 我这样做是这样的 public class GrabIconsFromWebPage public static String replaceUrl String url StringB
  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • Emma 不生成coverage.ec

    我设置了艾玛 它曾经对我有用 然后我们更改了源代码 现在它没有生成coverage ec根本不 它确实生成coverage em 测试临近结束时 出现错误消息 exec INSTRUMENTATION CODE 0 echo Downloa
  • 加载数据infile,Windows和Linux的区别

    我有一个需要导入到 MySQL 表的文件 这是我的命令 LOAD DATA LOCAL INFILE C test csv INTO TABLE logs fields terminated by LINES terminated BY n
  • android中listview显示数据库中的数据

    我是安卓新手 我想知道如何在列表视图中显示数据库中的数据 它不会向数据库添加数据 我只是显示我们存储在数据库中的任何内容 请帮助我实现这一目标 提前致谢 使用这些课程可能会对您有所帮助 用于数据库创建 package com example
  • Android 中的列表视图分页

    我有一个列表视图 其中显示了 50 个元素 我决定对视图进行分页 以便视图的每个部分都有 10 个元素 然后单击 下一个 按钮以获取下一个 10 个元素 如何设置10个数据 我关注这篇文章http rakhi577 wordpress co
  • 如何使用GDB修改内存内容?

    我知道我们可以使用几个命令来访问和读取内存 例如 print p x 但是如何更改任何特定位置的内存内容 在 GDB 中调试时 最简单的是设置程序变量 参见GDB 分配 http sourceware org gdb current onl
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 是否可以使用 CardView 为浮动操作按钮制作阴影?

    I know CardView不是为此而设计的 但理论上如果cardCornerRadius view size 2它应该导致圆圈 我错过了什么吗 绘制真实的动画阴影并不困难 您可以尝试在 Froyo 等任何 Android 设备上实现 L
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 无法使用文件提供程序从内部存储打开 PDF 以便在 Android 8 和 9 上查看

    仅适用于 Android 8 和 9 我这里有一个 PDF 文件管理器 String url file storage emulated 0 Android data com verna poc files Download mypdf p
  • Android AdMob:addView 在返回活动之前不会显示广告

    我正在尝试在游戏顶部添加横幅广告 我的活动使用带有自定义 SurfaceView 的relativelayout 我希望广告与 SurfaceView 重叠 广告会加载并可点击 但不会绘制到屏幕上 当我离开活动并返回时 会绘制广告 例如 通
  • Fragment 生命周期和在不存在的 Fragment 上调用 onCreate 的问题

    我正在 Android 中测试片段 并且片段生命周期有一些令人困惑的行为 我有一个活动 在横向和纵向模式下使用 xml 布局 我有一些代码可以访问在片段布局之一中定义的 EditText 对象 如果我以横向模式启动应用程序 一切都会正常 我
  • 如何正确编写AttributeSet的XML?

    我想创建一个面板适用于 Android 平台的其他小部件 http code google com p android misc widgets 在运行时 XmlPullParser parser getResources getXml R
  • 为什么带处理程序的连续自动对焦相机不允许切换相机闪光灯?

    到目前为止我所做的 我已经实现了用于读取二维码的自定义相机 需要继续聚焦相机以获得更好的二维码读取 我的问题当我使用处理程序每 秒聚焦一次时 相机闪光灯开 关按钮不起作用 或者打开和关闭相机闪光灯需要太多时间 当我删除每秒自动对焦相机的代码
  • 两种情况或 if 哪个更快? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我必须制作一个 非常 轻的脚本 它将接受用户的选项并调用脚本中的函数来执行一些任务 现在我可以使用 IF 和 CASE 选项 但我想知道两

随机推荐

  • 最近用了一个免费的服务器

    最近看到一个免费的服务器阿贝云 声称是永久免费使用的 有免费虚拟主机 也有免费的云服务器 带宽也不错 下载基本峰值能达到10M s
  • Android学习之完整的注册登录Demo(APP端+服务器端)

    因比赛或者项目需要 写了几个小打小闹的APP 有的处于 单机 状态 有的处于 半联网 状态 觉得学习有点操之过急 所以先缓一缓 梳理一下之前所学的知识 将之前做的一些小玩意儿 整理出来写成博客 第一篇便是大部分APP都具有的注册登录系统 代
  • 15款最好用的思维导图(心智图 )工具

    原文地址 http www linuxidc com Linux 2015 01 111807 htm 思维导图也叫心智图 是一项流行的全脑式学习方法 用来表示词 思路 任务或其他与围绕着一个中央关键词或想法项目的示意图 通过径向 图形和非
  • 【控制】基于PID实现水箱控制系统matlab代码

    1 内容介绍 计算机控制技术 课程是电气信息类专业的主干课程之一 实验教学是该课程教学的重要组成部分 本实验项目以水箱液位为控制参量 设计了包括系统硬件 基于Matlab的控制界面 PID控制程序等内容的 计算机控制技术 实验方案 通过教学
  • 二叉树:总结篇(需要掌握的二叉树技能都在这里了)

    二叉树 总结篇 需要掌握的二叉树技能都在这里了 二叉树的理论基础 二叉树的遍历方式 1 深度优先遍历 2 广度优先遍历 求二叉树的属性 1 是否对称 2 求最大深度 3 求最小深度 4 求有多少个节点 5 是否平衡 6 找所有路径 7 递归
  • 剑指offer_第20题_包含min函数的栈_Python

    题目描述 定义栈的数据结构 并在该类型中实现一个能够得到栈中所含最小元素的min函数 时间复杂度应为O 1 理解 什么是栈 算法复杂度 解题思路 思路1 class Solution def init self self stack sel
  • ant.design的input框同时绑定onBlur和onPressEnter事件且方法为同一个方法后的结果

    ant design的input框同时绑定onBlur和onPressEnter事件且方法为同一个方法后的结果 前几天在做项目时碰到了这么一件事 就是在input上面同时绑定onBlur事件和onPressEnter 而且这两个事件还同时使
  • Python爬虫实战案例——第二例

    某某美剧剧集下载 从搜索片名开始 本篇文章主要是为大家提供某些电影网站的较常规的下载电影的分析思路与代码思路 通过爬虫下载电影 我们会从搜索某部影片的关键字开始直到成功下载某一部电影 地址 aHR0cHM6Ly93d3cuOTltZWlqd
  • esp32 SPIFFS的使用

    读取方法 include FS h include SPIFFS h include AutoFile h void File Init SPIFFS begin true 挂载 时间较长 void contentWrite String
  • 目标检测 - 主流算法介绍 - 从RCNN到DETR

    目标检测是计算机视觉的一个非常重要的核心方向 它的主要任务目标定位和目标分类 在深度学习介入该领域之前 传统的目标检测思路包括区域选择 手动特征提取 分类器分类 由于手动提取特征的方法往往很难满足目标的多样化特征 传统方法始终没能很好的解决
  • Inno Setup 添加管理员权限

    1 在 Setup 节点添加 PrivilegesRequired admin 2 在Inno Setup的安装目录下有个SetupLdr e32文件 用exescope打开 然后在十六进制区域编辑 在右边ASCII显示区域无法编辑 修改到
  • 关于新版vscode的ul>li*3等,不提示快捷方式解决方法

    到文件首选项的设置里搜索emmet 然后找到Emmet Use Inline Completions 勾选上就可以了 快捷方式 如ul gt li 3 加上tab键 注意按enter键无效
  • MKL——常用函数说明

    Intel MKL 全称 Intel Math Kernel Library 提供经过高度优化和大量线程化处理的数学例程 面向性能要求极高的科学 工程及金融等领域的应用 MKL是一款商用函数库 提供C Fortran 和 Fortran 9
  • 以太坊的企业系统集成

    最流行的开源Java集成库 Apache Camel现在支持以太坊的JSON RPC API 以太坊生态系统 以太坊是一个开源 公共 区块链平台 用于运行智能合约 它提供了一个去中心化的图灵完备虚拟机 可以执行脚本和加密货币 用于补偿参与者
  • Android 根目录下和应用目录下的build.gradle的详解,以及groovy语法的讲解

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 前言 Gradle的作用 打apk包 打插件包 自动化构建 多渠道打包 自动化签名 后台java打包 生成文件 使用的是groovy语法
  • 数据中台-数据安全管理-11

    文章目录 数据安全管理 11 1 数据安全面临的调整 11 1 1 数据安全问题带来的4大损害 11 1 2 法律和政策背景 11 1 3 数据安全的4大技术挑战 1 平台安全 2 服务安全 3 数据本身的安全 4 APT攻击防御 11 1
  • 华为OD机试 - 阿里巴巴找黄金宝箱(I)(Java)

    题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱子 每个箱子上面贴有一个数字 箱子中可能有一个黄金宝箱 黄金宝箱满足排在它之前的所有箱子数字和等于排在它之后的所有箱子数字之和 第一个箱子
  • Verilog实现呼吸灯效果

    呼吸灯的效果采用PWM调波的形式 即快速的改变每个周期的占空比 一个周期内高电平时间占一个周期时间的比值 来实现点亮到熄灭的效果 示意如下图 而关于整个波形图 用50MHz的晶振 从0开始计数到49则为1us 而1ms是1us的1000倍
  • elasticsearch update 无结果

    这个一直不能用 for id in data front res es update index index doc type doc type id id body doc flag 0 print id res break total
  • Android 块设备驱动之二

    将块设备添到系统 添加磁盘和分区到系统中 add diskgendisk函数 register disk add partition 块设备操作 读写操作 请求操作 提交读写请求 将块设备添到系统 调用void blk register r