覃谈谭的blog


Just for me

misc驱动结构分析

0 条评论 学习总结 Linux驱动 adtxl

1.misc驱动简介

  • misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于杂散设备,也就是misc设备,例如像adc、buzzer等这些设备一般都归属于misc中。所有的misc类设备都是字符设备,也就是misc类是字符设备中分出来的一个小类。
  • misc类设备在应用层的操作接口:/dev/xxx,设备类对应在/sys/class/misc
  • misc类设备有自己的一套驱动框架,所以我们写一个misc设备的驱动直接利用的是内核中提供的驱动程序框架来实现的。misc驱动框架是对内核提供的原始的字符设备注册接口的一个类层次的封装,很多典型的字符设备都可以归于misc设备,都可以利用misc提供的驱动框架来编写驱动代码,通过misc驱动框架来进行管理。

2.misc驱动框架源码分析

在内核源码中,misc驱动框架源码的实现在:driver/char/misc.c,相应的头文件在:include/linux/miscdevice.h
如果我们添加自己的misc类设备,那么驱动源文件最好在driver/misc这个目录下
先看miscdevice.h的源代码:

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_MISCDEVICE_H
#define _LINUX_MISCDEVICE_H
#include <linux/major.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/device.h>

/*
 *  These allocations are managed by device@lanana.org. If you use an
 *  entry that is not in assigned your entry may well be moved and
 *  reassigned, or set dynamic if a fixed value is not justified.
 */

#define PSMOUSE_MINOR       1
#define MS_BUSMOUSE_MINOR   2   /* unused */
#define ATIXL_BUSMOUSE_MINOR    3   /* unused */
/*#define AMIGAMOUSE_MINOR  4   FIXME OBSOLETE */
#define ATARIMOUSE_MINOR    5   /* unused */
#define SUN_MOUSE_MINOR     6   /* unused */
#define APOLLO_MOUSE_MINOR  7   /* unused */
#define PC110PAD_MINOR      9   /* unused */
/*#define ADB_MOUSE_MINOR   10  FIXME OBSOLETE */
#define WATCHDOG_MINOR      130 /* Watchdog timer     */
#define TEMP_MINOR      131 /* Temperature Sensor */
#define APM_MINOR_DEV       134
#define RTC_MINOR       135
#define EFI_RTC_MINOR       136 /* EFI Time services */
#define VHCI_MINOR      137
#define SUN_OPENPROM_MINOR  139
#define DMAPI_MINOR     140 /* unused */
#define NVRAM_MINOR     144
#define SGI_MMTIMER     153
#define STORE_QUEUE_MINOR   155 /* unused */
#define I2O_MINOR       166
#define AGPGART_MINOR       175
#define HWRNG_MINOR     183
#define MICROCODE_MINOR     184
#define IRNET_MINOR     187
#define D7S_MINOR       193
#define VFIO_MINOR      196
#define TUN_MINOR       200
#define CUSE_MINOR      203
#define MWAVE_MINOR     219 /* ACP/Mwave Modem */
#define MPT_MINOR       220
#define MPT2SAS_MINOR       221
#define MPT3SAS_MINOR       222
#define UINPUT_MINOR        223
#define MISC_MCELOG_MINOR   227
#define HPET_MINOR      228
#define FUSE_MINOR      229
#define KVM_MINOR       232
#define BTRFS_MINOR     234
#define AUTOFS_MINOR        235
#define MAPPER_CTRL_MINOR   236
#define LOOP_CTRL_MINOR     237
#define VHOST_NET_MINOR     238
#define UHID_MINOR      239
#define USERIO_MINOR        240
#define VHOST_VSOCK_MINOR   241
#define RFKILL_MINOR        242
#define MISC_DYNAMIC_MINOR  255

struct device;
struct attribute_group;

struct miscdevice  {
    int minor;  // 次设备号
    const char *name;   //设备名称
    const struct file_operations *fops; // 文件操作结构体
    struct list_head list;  // 作为一个链表结点挂接到misc设备维护的一个链表头上去
    struct device *parent;  // 次设备的父设备
    struct device *this_device; // 本设备的device结构体指针
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};

extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);

/*
 * Helper macro for drivers that don't do anything special in the initcall.
 * This helps in eleminating of boilerplate code.
 */
#define builtin_misc_device(__misc_device) \
    builtin_driver(__misc_device, misc_register)

/*
 * Helper macro for drivers that don't do anything special in module init / exit
 * call. This helps in eleminating of boilerplate code.
 */
#define module_misc_device(__misc_device) \
    module_driver(__misc_device, misc_register, misc_deregister)

#define MODULE_ALIAS_MISCDEV(minor)             \
    MODULE_ALIAS("char-major-" __stringify(MISC_MAJOR)  \
    "-" __stringify(minor))
#endif

下面是misc驱动框架的源码实现,misc.c


#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/*
 * Head entry for the doubly linked miscdevice list
 */
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);

/*
 * Assigned numbers, used for dynamic minors
 */
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);

#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{
    mutex_lock(&misc_mtx);
    return seq_list_start(&misc_list, *pos);
}

static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
    return seq_list_next(v, &misc_list, pos);
}

static void misc_seq_stop(struct seq_file *seq, void *v)
{
    mutex_unlock(&misc_mtx);
}

static int misc_seq_show(struct seq_file *seq, void *v)
{
    const struct miscdevice *p = list_entry(v, struct miscdevice, list);

    seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
    return 0;
}


static const struct seq_operations misc_seq_ops = {
    .start = misc_seq_start,
    .next  = misc_seq_next,
    .stop  = misc_seq_stop,
    .show  = misc_seq_show,
};
#endif

static int misc_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);
    struct miscdevice *c;
    int err = -ENODEV;
    const struct file_operations *new_fops = NULL;

    mutex_lock(&misc_mtx);

    list_for_each_entry(c, &misc_list, list) {
        if (c->minor == minor) {
            new_fops = fops_get(c->fops);
            break;
        }
    }

    if (!new_fops) {
        mutex_unlock(&misc_mtx);
        request_module("char-major-%d-%d", MISC_MAJOR, minor);
        mutex_lock(&misc_mtx);

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == minor) {
                new_fops = fops_get(c->fops);
                break;
            }
        }
        if (!new_fops)
            goto fail;
    }

    /*
     * Place the miscdevice in the file's
     * private_data so it can be used by the
     * file operations, including f_op->open below
     */
    file->private_data = c;

    err = 0;
    replace_fops(file, new_fops);
    if (file->f_op->open)
        err = file->f_op->open(inode, file);
fail:
    mutex_unlock(&misc_mtx);
    return err;
}

static struct class *misc_class;

static const struct file_operations misc_fops = {
    .owner      = THIS_MODULE,
    .open       = misc_open,
    .llseek     = noop_llseek,
};

/**
 *  misc_register   -   register a miscellaneous device
 *  @misc: device structure
 *
 *  Register a miscellaneous device with the kernel. If the minor
 *  number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *  and placed in the minor field of the structure. For other cases
 *  the minor number requested is used.
 *
 *  The structure passed is linked into the kernel and may not be
 *  destroyed until it has been unregistered. By default, an open()
 *  syscall to the device sets file->private_data to point to the
 *  structure. Drivers don't need open in fops for this.
 *
 *  A zero is returned on success and a negative errno code for
 *  failure.
 */

int misc_register(struct miscdevice *misc)
{
    dev_t dev;
    int err = 0;
    bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);

    INIT_LIST_HEAD(&misc->list);

    mutex_lock(&misc_mtx);

    if (is_dynamic) {
        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);

        if (i >= DYNAMIC_MINORS) {
            err = -EBUSY;
            goto out;
        }
        misc->minor = DYNAMIC_MINORS - i - 1;
        set_bit(i, misc_minors);
    } else {
        struct miscdevice *c;

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == misc->minor) {
                err = -EBUSY;
                goto out;
            }
        }
    }

    dev = MKDEV(MISC_MAJOR, misc->minor);

    misc->this_device =
        device_create_with_groups(misc_class, misc->parent, dev,
                      misc, misc->groups, "%s", misc->name);
    if (IS_ERR(misc->this_device)) {
        if (is_dynamic) {
            int i = DYNAMIC_MINORS - misc->minor - 1;

            if (i < DYNAMIC_MINORS && i >= 0)
                clear_bit(i, misc_minors);
            misc->minor = MISC_DYNAMIC_MINOR;
        }
        err = PTR_ERR(misc->this_device);
        goto out;
    }

    /*
     * Add it to the front, so that later devices can "override"
     * earlier defaults
     */
    list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}
EXPORT_SYMBOL(misc_register);

/**
 *  misc_deregister - unregister a miscellaneous device
 *  @misc: device to unregister
 *
 *  Unregister a miscellaneous device that was previously
 *  successfully registered with misc_register().
 */

void misc_deregister(struct miscdevice *misc)
{
    int i = DYNAMIC_MINORS - misc->minor - 1;

    if (WARN_ON(list_empty(&misc->list)))
        return;

    mutex_lock(&misc_mtx);
    list_del(&misc->list);
    device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
    if (i < DYNAMIC_MINORS && i >= 0)
        clear_bit(i, misc_minors);
    mutex_unlock(&misc_mtx);
}
EXPORT_SYMBOL(misc_deregister);

static char *misc_devnode(struct device *dev, umode_t *mode)
{
    struct miscdevice *c = dev_get_drvdata(dev);

    if (mode && c->mode)
        *mode = c->mode;
    if (c->nodename)
        return kstrdup(c->nodename, GFP_KERNEL);
    return NULL;
}

// misc_init()函数是misc驱动框架模块注册时的一个初始化函数,只有进行了初始化,我们才能够利用misc提供的框架来进行编写misc设备驱动程序和管理misc类设备。
static int __init misc_init(void)
{
    int err;
    struct proc_dir_entry *ret;

    ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
    // 在sys文件系统下创建misc设备类
    misc_class = class_create(THIS_MODULE, "misc");
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    // 注册misc字符设备,主设备是10
    if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    if (ret)
        remove_proc_entry("misc", NULL);
    return err;
}
subsys_initcall(misc_init);

Linux内核中链表list文件结构分析

0 条评论 学习总结 Linux 链表 数据结构 adtxl

1. 函数原型

内核中的链表结构如下,只有前后两个指针,没有数据项,可以很方便的构成双向链表

struct list_head {
    struct list_head *next, *prev;
};

1.1. static inline void INIT_LIST_HEAD(struct list_head *list)

运行的时候初始化链表,两个指针都指向结点自己的地址

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    WRITE_ONCE(list->next, list);
    list->prev = list;
}

1.2. static inline void list_add(struct list_head new, struct list_head head);

从指定结点后面插入一个结点,new为要插入的新节点的地址,head为要插入的结点,新结点从head结点后面插入

/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

其中,__list_add()函数定义如下,在知道前后结点的情况下,插入结点

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    if (!__list_add_valid(new, prev, next))
        return;

    next->prev = new;
    new->next = next;
    new->prev = prev;
    WRITE_ONCE(prev->next, new);
}

1.3. static inline void list_add_tail(struct list_head new, struct list_head head)

从链表尾部插入结点

/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

1.4. static inline void list_del(struct list_head *entry)

两个宏定义,删除下来的prev、next指针指向这两个特殊值,这样设置是为了保证不在链表中的结点项不可访问--对LIST_POISON1和LIST_POISON2的访问都将引起页故障。
/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x122 + POISON_POINTER_DELTA)
static inline void list_del(struct list_head *entry)
{
    __list_del_entry(entry);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}

其中__list_del_entry()函数定义如下:

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    WRITE_ONCE(prev->next, next);
}

/*
 * Delete a list entry and clear the 'prev' pointer.
 *
 * This is a special-purpose list clearing method used in the networking code
 * for lists allocated as per-cpu, where we don't want to incur the extra
 * WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
 * needs to check the node 'prev' pointer instead of calling list_empty().
 */
static inline void __list_del_clearprev(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->prev = NULL;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void __list_del_entry(struct list_head *entry)
{
    if (!__list_del_entry_valid(entry))
        return;

    __list_del(entry->prev, entry->next);
}

1.5. static inline void list_replace(struct list_head old,struct list_head new)

替换链表中的结点,

/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
                struct list_head *new)
{
    new->next = old->next;
    new->next->prev = new;
    new->prev = old->prev;
    new->prev->next = new;
}

1.6. static inline void list_replace_init(struct list_head old,struct list_head new)

替换,将被替换的结点初始化为一个新链表

static inline void list_replace_init(struct list_head *old,
                    struct list_head *new)
{
    list_replace(old, new);
    INIT_LIST_HEAD(old);
}

1.7. static inline void list_swap(struct list_head entry1,struct list_head entry2)

交换两个结点

/**
 * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
 * @entry1: the location to place entry2
 * @entry2: the location to place entry1
 */
static inline void list_swap(struct list_head *entry1,
                 struct list_head *entry2)
{
    struct list_head *pos = entry2->prev;

    list_del(entry2);
    list_replace(entry1, entry2);
    if (pos == entry1)
        pos = entry2;
    list_add(entry1, pos);
}

1.8. static inline void list_del_init(struct list_head *entry)

删除一项并初始化

/**
 * list_del_init - deletes entry from list and reinitialize it.
 * @entry: the element to delete from the list.
 */
static inline void list_del_init(struct list_head *entry)
{
    __list_del_entry(entry);
    INIT_LIST_HEAD(entry);
}

1.9. static inline void list_move(struct list_head list, struct list_head head)

搬移操作,将原本属于链表的一个结点移动到另一个链表的操作

/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del_entry(list);
    list_add(list, head);
}

1.10. static inline void list_move_tail(struct list_head list,struct list_head head)

/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                  struct list_head *head)
{
    __list_del_entry(list);
    list_add_tail(list, head);
}

1.11. static inline void list_bulk_move_tail(struct list_head head, struct list_head first, struct list_head *last)

/**
 * list_bulk_move_tail - move a subsection of a list to its tail
 * @head: the head that will follow our entry
 * @first: first entry to move
 * @last: last entry to move, can be the same as first
 *
 * Move all entries between @first and including @last before @head.
 * All three entries must belong to the same linked list.
 */
static inline void list_bulk_move_tail(struct list_head *head,
                       struct list_head *first,
                       struct list_head *last)
{
    first->prev->next = last->next;
    last->next->prev = first->prev;

    head->prev->next = first;
    first->prev = head->prev;

    last->next = head;
    head->prev = last;
}

1.12. static inline int list_is_first(const struct list_head list,const struct list_head head)

判断结点是否为首结点

/**
 * list_is_first -- tests whether @list is the first entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_first(const struct list_head *list,
                    const struct list_head *head)
{
    return list->prev == head;
}

1.13. static inline int list_is_last(const struct list_head list, const struct list_head head)

判断结点是否为尾结点

/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                const struct list_head *head)
{
    return list->next == head;
}

1.14. static inline int list_empty(const struct list_head *head)

判断是否是一个空链表

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
    return READ_ONCE(head->next) == head;
}

1.15. static inline int list_empty_careful(const struct list_head *head)

/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}

1.16. static inline void list_rotate_left(struct list_head *head)

翻转链表

/**
 * list_rotate_left - rotate the list to the left
 * @head: the head of the list
 */
static inline void list_rotate_left(struct list_head *head)
{
    struct list_head *first;

    if (!list_empty(head)) {
        first = head->next;
        list_move_tail(first, head);
    }
}

1.17. static inline void list_rotate_to_front(struct list_head list,struct list_head head)

/**
 * list_rotate_to_front() - Rotate list to specific item.
 * @list: The desired new front of the list.
 * @head: The head of the list.
 *
 * Rotates list so that @list becomes the new front of the list.
 */
static inline void list_rotate_to_front(struct list_head *list,
                    struct list_head *head)
{
    /*
     * Deletes the list head from the list denoted by @head and
     * places it as the tail of @list, this effectively rotates the
     * list so that @list is at the front.
     */
    list_move_tail(head, list);
}

1.18. static inline int list_is_singular(const struct list_head *head)

判断一个链表是否只有一项

/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
    return !list_empty(head) && (head->next == head->prev);
}

1.19. static inline void list_cut_position(struct list_head list,struct list_head head, struct list_head *entry)

将一个链表拆分为两个

/**
 * list_cut_position - cut a list into two
 * @list: a new list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *  and if so we won't cut the list
 *
 * This helper moves the initial part of @head, up to and
 * including @entry, from @head to @list. You should
 * pass on @entry an element you know is on @head. @list
 * should be an empty list or a list you do not care about
 * losing its data.
 *
 */
static inline void list_cut_position(struct list_head *list,
        struct list_head *head, struct list_head *entry)
{
    if (list_empty(head))
        return;
    if (list_is_singular(head) &&
        (head->next != entry && head != entry))
        return;
    if (entry == head)
        INIT_LIST_HEAD(list);
    else
        __list_cut_position(list, head, entry);
}

1.20. static inline void list_cut_before(struct list_head list,struct list_head head,struct list_head *entry)

/**
 * list_cut_before - cut a list into two, before given entry
 * @list: a new list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *
 * This helper moves the initial part of @head, up to but
 * excluding @entry, from @head to @list.  You should pass
 * in @entry an element you know is on @head.  @list should
 * be an empty list or a list you do not care about losing
 * its data.
 * If @entry == @head, all entries on @head are moved to
 * @list.
 */
static inline void list_cut_before(struct list_head *list,
                   struct list_head *head,
                   struct list_head *entry)
{
    if (head->next == entry) {
        INIT_LIST_HEAD(list);
        return;
    }
    list->next = head->next;
    list->next->prev = list;
    list->prev = entry->prev;
    list->prev->next = list;
    head->next = entry;
    entry->prev = head;
}

1.21. static inline void list_splice(const struct list_head list,struct list_head head)

连接两个链表

/**
 * list_splice - join two lists, this is designed for stacks
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(const struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head, head->next);
}

1.22. static inline void list_splice_tail(struct list_head list,struct list_head head)

/**
 * list_splice_tail - join two lists, each list being a queue
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice_tail(struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head->prev, head);
}

1.23. static inline void list_splice_init(struct list_head list, struct list_head head)

/**
 * list_splice_init - join two lists and reinitialise the emptied list.
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * The list at @list is reinitialised
 */
static inline void list_splice_init(struct list_head *list,
                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}

1.24. static inline void list_splice_tail_init(struct list_head list, struct list_head head)

/**
 * list_splice_tail_init - join two lists and reinitialise the emptied list
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * Each of the lists is a queue.
 * The list at @list is reinitialised
 */
static inline void list_splice_tail_init(struct list_head *list,
                     struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}

2. 使用的宏定义

2.1. LIST_HEAD_INIT

#define LIST_HEAD_INIT(name) { &(name), &(name) }

2.2. LIST_HEAD

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

2.3. list_entry

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

2.4. list_first_entry

#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

2.5. list_last_entry

#define list_last_entry(ptr, type, member) \
    list_entry((ptr)->prev, type, member)

2.6. list_first_entry_or_null

#define list_first_entry_or_null(ptr, type, member) ({ \
    struct list_head *head__ = (ptr); \
    struct list_head *pos__ = READ_ONCE(head__->next); \
    pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
})

2.7. list_next_entry

#define list_next_entry(pos, member) \
    list_entry((pos)->member.next, typeof(*(pos)), member)

2.8. list_prev_entry

#define list_prev_entry(pos, member) \
    list_entry((pos)->member.prev, typeof(*(pos)), member)

2.9. list_for_each

#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

2.10. list_for_each_prev

#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; pos != (head); pos = pos->prev)

2.11. list_for_each_safe

#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)

2.12. list_for_each_prev_safe

#define list_for_each_prev_safe(pos, n, head) \
    for (pos = (head)->prev, n = pos->prev; \
         pos != (head); \
         pos = n, n = pos->prev)

2.13. list_for_each_entry

#define list_for_each_entry(pos, head, member)              \
    for (pos = list_first_entry(head, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = list_next_entry(pos, member))

2.14. list_for_each_entry_reverse

#define list_for_each_entry_reverse(pos, head, member)          \
    for (pos = list_last_entry(head, typeof(*pos), member);     \
         &pos->member != (head);                    \
         pos = list_prev_entry(pos, member))

2.15. list_prepare_entry

#define list_prepare_entry(pos, head, member) \
    ((pos) ? : list_entry(head, typeof(*pos), member))

2.16. list_for_each_entry_continue

#define list_for_each_entry_continue(pos, head, member)         \
    for (pos = list_next_entry(pos, member);            \
         &pos->member != (head);                    \
         pos = list_next_entry(pos, member))

2.17. list_for_each_entry_from_reverse

#define list_for_each_entry_from_reverse(pos, head, member)     \
    for (; &pos->member != (head);                  \
         pos = list_prev_entry(pos, member))

2.18. list_for_each_entry_safe

#define list_for_each_entry_safe(pos, n, head, member)          \
    for (pos = list_first_entry(head, typeof(*pos), member),    \
        n = list_next_entry(pos, member);           \
         &pos->member != (head);                    \
         pos = n, n = list_next_entry(n, member))

2.19. list_for_each_entry_safe_continue

#define list_for_each_entry_safe_continue(pos, n, head, member)         \
    for (pos = list_next_entry(pos, member),                \
        n = list_next_entry(pos, member);               \
         &pos->member != (head);                        \
         pos = n, n = list_next_entry(n, member))

2.20. list_for_each_entry_safe_from

#define list_for_each_entry_safe_from(pos, n, head, member)             \
    for (n = list_next_entry(pos, member);                  \
         &pos->member != (head);                        \
         pos = n, n = list_next_entry(n, member))

2.21. list_for_each_entry_safe_reverse

#define list_for_each_entry_safe_reverse(pos, n, head, member)      \
    for (pos = list_last_entry(head, typeof(*pos), member),     \
        n = list_prev_entry(pos, member);           \
         &pos->member != (head);                    \
         pos = n, n = list_prev_entry(n, member))

2.22. list_safe_reset_next

#define list_safe_reset_next(pos, n, member)                \
    n = list_next_entry(pos, member)

Linux-系统操作

0 条评论 学习总结 Linux adtxl

1.帮助命令

  • man
    man是manual的缩写
    用法: man 命令
    man 也是一条命令,分为9章,可以使用man命令获得man的帮助,如man 7 man

  • help
    内部命令使用help帮助,help 命令
    外部命令使用help帮助,命令 --help
    使用type 命令可以查看是内部命令还是外部命令

  • info
    info帮助比help更详细

2.文件命令

  • pwd命令
    显示当前目录的绝对路径

  • ls命令
    查看当前目录下的文件

    • 基本语法:
      ls [选项,选型] 参数......
    • 常用选项
      -l 长格式显示文件
      -a 显示隐藏文件
      -r 逆序显示
      -t 按照时间顺序显示
      -R 递归显示
  • cd命令
    更改当前的操作目录

    • 常用操作:
      cd - :返回上一目录
  • mkdir命令
    建立目录

    • 常用选项:
      -p 建立多级目录
  • rmdir命令
    删除空目录

  • cp命令
    复制文件和目录

    • 基本语法:
      cp [选项] 文件路径
      cp [选项] 文件... 路径
    • 常用选项:
      -r 复制目录,不加选项只能复制文件
      -p 复制时保留用户、权限、时间等文件属性
      -a 等同于-dpR,显示复制过程
  • mv命令
    移动或者重命名文件

    • 基本语法:
      mv [选项] 源文件 目标文件
      mv [选项] 源文件 目录
  • rm命令
    删除文件

    • 常用选项:
      -r 删除目录,非空的
      -f 删除文件不进行提示
  • 通配符

    • 定义:shell 内建的符号
    • 用途:操作多个相似的文件
    • 常用的通配符:
      * 匹配任何字符串
      ? 匹配一个字符串
      [xyz] 匹配xyz任意一个字符
      [a-z] 匹配一个范围
      [!xyz] 不匹配

3. 文本查看命令

  • cat 文本内容显示到终端
  • head 查看文件开头
  • tail 查看文件结尾
    • 常用参数:-f 文件内容更新后,显示信息同步更新
  • wc 统计文件内容信息
  • more
  • less

4.打包与压缩命令

  • 打包命令
    tar命令是Linux中的备份命令,在打包完成后,需要对文件进行压缩,压缩的命令是gzip和bzip2.
    经常使用的扩展名是.tar.gz .tar.bz2 .tgz .tbz2
    • 常用选项:
      c 打包
      x 解包
      f 指定操作类型为文件
  • 压缩和解压缩
    可以先使用tar命令打包,再单独使用命令gzip和bzip2命令。但在日常的使用中,通常和tar命令配合使用
    • 常用选项:
      -z: gzip格式压缩和解压缩
      -j: bzip2格式压缩和解压缩

5.Vi编辑器

进入vim后即为正常模式,可以复制粘贴。按i进入插入模式,可以进行文本的输入。从插入模式退出,按ESC进入正常模式,然后输入:或者\进入命令模式,在命令行下输入:wq,:q可退出。

  • 正常模式
    进入其他模式的转换命令

    • i 进入插入模式
    • v 进入可视化模式
    • : 进入命令模式
    • esc 从其他模式回到正常模式
      基本操作:
      使用h j k l控制上下左右的移动,一些基本操作
    • y 复制
      一般都是按行复制,使用yy命令,使用数字加yy可以复制多行,使用y$可以复制从光标到行尾全部内容
    • d 剪切
      dd剪切一整行
    • p 粘贴
    • u 撤销
    • ctrl+r 重做,把撤销指令重做
    • x 删除单个字符
    • r 替换单个字符
    • G 定位指定的行
      数字加G定位到指定行
    • ^ 定位到行首
    • $ 定位到行尾
  • 命令模式

    • :w 写入
    • :q 退出
    • :! 执行shell命令
    • :s 替换
      使用方法s/old/new,只是用s只替换光标所在行的内容,使用%s可替换所有行的第一个字符。使用%s/old/new/g可以替换所有,global
      3,5s/old/new是指替换3到5行的
    • / 查找
      使用n查看下一个查找到的内容,使用shift+n查看上一个
    • :set 设置命令
      :set nu, :set nonu
  • 可视模式
    进入可视模式的方式

    • v 字符可视模式
    • V 行可视模式
    • ctrl+v 块可视化模式

6.用户和用户组管理及密码管理

  • 用户管理常用命令
    • useradd 新建用户
      useradd 用户名用户是否存在,
      使用 id 用户名 可以知道用户是否存在
    • userdel 删除用户
      userdel 用户名即可删除用户,但会保留用户的家目录
      使用-r参数可以删除用户目录
    • passwd 修改用户密码
    • usermod 修改用户属性
      可以修改用户家目录、用户组等信息
    • chage 修改用户属性
      可以修改用户的生命周期
  • 用户组管理命令
    • groupadd 新建用户组
    • groupdel 删除用户组
  • su和sudo命令
    • su 切换用户
      su - USERNAME
    • sudo 以其他用户身份执行命令
    • visudo 设置需要使用sudo的用户(组)
  • 用户和用户组配置文件
    /etc/passwd/
    /etc/shadow/
    /etc/group/

7.文件权限

  • 文件类型:
    - 普通文件
    d 目录文件
    b 块特殊文件
    c 字符特殊文件
    l 符号链接(类似Windows快捷方式)
    f 命名管道
    s 套接字文件

  • 文件权限的表示
    字符权限的表示法:
    r 读
    w 写
    x 执行
    数字权限的表示法:
    r = 4
    w = 2
    x = 1
    如 rw-r-xr--意为
    rw- 文件属主的权限
    r-x 文件属组的权限
    r-- 其它用户的权限

  • 文件权限的修改
    root用户权限不受限

  • chmod 更该文件、目录权限
    字符表示法:
    u g o a参数表示用户属主、属组、其他用户、和全部
    u=x,u+x,u-x设置、增加、减少权限
    chmod u+x /tmp/testfile
    数字表示法:
    chmod 755 /tmp/testfile

  • chown 更改属主、属组

  • chgrp 可以单独改属组,不常用
    使用ctrl+r,可以查找历史命令


makefile学习笔记

0 条评论 学习总结 Make 学习笔记 makefile adtxl

1.1. 程序的编译和链接

在Linux下,源文件被编译成中间代码,即.o文件。有时候,由于源文件太多,编译生成的目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给目标文件打个包,在Windows下叫“库文件”,即.lib文件。在Linux下,是Archive File,也就是.a文件。

1.2. Makefile介绍

只要Makefile写的够好,只需要一个make命令就可以完成编译和链接程序,make命令会自动智能地根据当前文件的修改情况来确定哪些文件需要重编译。从而自己编译所需要的文件和链接目标程序。

1.3. makefile的规则

tagfet... : prerequisites...
command

target是一个目标文件,可以是Object File,也可以是可执行文件,还可以是一个标签(Label)
prerequisites就是要生成target目标文件所需要的文件
command是make需要执行的命令
以上是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。

1.4. Makefile里有什么?

Makefile里主要包含了五个东西:显示规则、隐晦规则、变量定义、文件指示和注释。
1.显示规则。显示规则说明了如何生成一个或多个的目标文件。这是由Makefile的书写着明显指出,要生成的文件,文件的依赖性,生成的命令。
2.隐晦规则。由于make具有自动推导的功能,所以隐晦的规则可以让我们比较粗糙简略地书写Makefile,这是由make所支持的。
3.变量定义。
4.文件指示。一个是在一个Makefile中引用另一个Makefile;另一个是根据某些情况制定Makefile中的有效部分。
5.注释。用#
最后在Makefile中的命令,必须以Tab键开始。

1.5. Makefile的文件名

最好使用“Makefile”这个文件名,也可以使用别的文件名,如果要指定特定的Makefile,可以使用make的“-f”和“--file”参数。

1.6. 引用其它的Makefile

在Makefile中可以使用include关键字可以把别的Makefile包含进来,include的语法:
include <filename>
filename可以是当前操作系统Shell的文件模式(可以包含路径和通配符)
-include表示,无论include过程中出现什么错误,都不要报错继续执行。

1.7. 环境变量MAKEFILES

如果在当前环境中定义了环境变量MAKEFILES,那么make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其他的Makefile,用空格分隔。和include不同的是,从这个环境中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也不会处理。但一般不建议使用这个环境变量。

1.8. make的工作方式

GNU的make工作时执行步骤如下:
1.读入所有的Makefile
2.读入被include的其它Makefile
3.初始化文件中的变量
4.推导隐晦规则,并分析所有规则
5.为所有的目标文件创建依赖关系链
6.根据依赖关系,决定哪些目标要重新生成
7.执行生成命令

1.9. 书写规则

1.9.1. 在规则中使用通配符

make支持三个通配符:“*”,“?”,“[...]”。
波浪号(“~”)字符在文件名中有比较特殊的用途。如果是“~/test”,表示当前用户的test目录。“~txl/test”表示用户txl的宿主目录下的test目录。

1.9.2. 文件搜寻

使用变量“VPATH”可以让make在指定目录下找寻文件
VPATH=src:../headers
上面定义了两个目录,“src”和“../headers”,目录由冒号分隔

使用关键字“vpath”也可以,且更为灵活
使用方法:
1.vpath
为符合模式的文件指定搜索目录
2.vpath
清楚符合模式的文件的搜索目录。
3.vpath
清楚所有已被设置好了的文件搜索目录。

vpath使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符。例如,“%.h”表示所有以“.h”结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的目录。

1.9.3. 伪目标

为了避免和文件重名的这种情况,可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行clean这个目标,只有“make clean”这样。于是整个过程可以这样写:

.PHONY : clean
clean:
rm * .o temp

1.9.4. 多目标

Makefile的规则中目标可以不止一个,其支持多目标,有可能我们的目标同时依赖于一个文件,并且其生成的命令大体类似。

1.9.5. 静态模式

静态模式可以让我们更容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活性。语法如下:

<targets...>:<target-pattern>:<prereq-patterns...>
<commands>

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合
target-pattern是指明了targets的模式,也就是目标集模式
prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义

1.9.6. 自动生成依赖性

1.10. 书写命令

每条命令必须以Tab键开头

1.10.1. 显示命令

用@字符在命令行前,这个命令将不会被显示出来,如
@echo 正在编译XXX模块......
当make执行时,会输出“正在编译XXX模块......”,但不会输出命令
make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令
make参数“-s”或“--slient则是全面禁止命令的显示”

1.10.2. 命令执行

如果要让上一条命令的结果应用在下一条命令时,应该使用分号分隔这两条命令。

1.10.3. 命令出错

在命令前加一个减号“-”,标记为不管命令出不出错都认为是成功的。
还有一个全局方法,给make加上参数“-i”或是“--ignore-errors”参数,makefile的所有命令都会忽略错误。
make的参数“-k”或是“--keep-going”,意为如果某规则中的命令出错了,就终止该规则的执行,继续执行其它的规则。

1.10.4. 嵌套执行make

在一些大的工程中,在每个目录下面都写一个该目录的makefile,例如

subsystem:
cd subdir && $(MAKE)

1.10.5. 定义命令包

如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以define开始,以endef结束。

1.11. 使用变量

1.11.1. 变量的基础

变量在声明时需要给与初值,在使用时,需要在变量名前加上“$”符号,但最好用()或{}把变量包括起来。如果要使用$字符,需要用“$$”来表示。

1.11.2. 变量中的变量

用变量构造变量的值
第一种,用=号,左侧是变量,右侧是变量的值

?=
FOO ?= bar
其含义是,如果FOO被定义过,那么变量FOO的值就是“bar”;如果FOO先前被定义过,那么这条语句将什么也不做

1.11.3. 变量的高级用法

第一种:
替换变量中的共有的部分,格式为
$(var:a=b),即把变量var中以“a”字串结尾的“a”替换成“b”字串,结尾的意思是空格或是结束符。
第二种:
把变量的值再当成变量

1.11.4. 追加变量值

使用“+=操作符给变量追加值”

1.11.5. override指示符

如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果想在Makefile中设置这类参数的值,可以使用“override”指示符。语法:

override <variable> = <value>
override <variable> := <value>

1.11.6. 多行变量

使用define关键字也可以设置变量值,使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令。
define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束

define two-lines
echo foo
echo $(bar)
endef

1.11.7. 环境变量

make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已经定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。
如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使用这个变量了。
当make嵌套调用时,上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用export关键字来声明。

1.11.8. 目标变量

可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和全局变量同名。因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。其语法:

<target...>:<variable-assignment>
<target...>:overide<variable-assignment>

第二个语法是针对于make命令行带入的变量,这个变量会作用到由这个目标所引发的所有规则中去。

1.11.9. 模式变量

模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
我们知道,make的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以.o结尾的目标定义目标变量:
%.o:CFLAGS=-O
同样,模式变量的语法和“目标变量”一样:

<pattern...>:<variable-assignment>
<pattern...>:override<variable-assignment>

1.12. 使用条件判断

使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
条件表达式的语法为:

<conditional-directive>
<text-if-true>
endif

或者
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
表示条件关键字,有四个: ifeq ifneq ifdef: 语法是 ifdef 如果变量的值非空,那么表达式为真,否则为假 ifndef

1.13. 使用函数

在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和智能。
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(<function><arguments>)
参数间以逗号分隔,函数名和参数之间以“空格”分隔。

字符串处理函数:

$(subst,,)
名称:字符串替换函数————subst
功能:把字串中的字符串替换成
返回:函数返回被替换过后的字符串

$(subst ee,EE,feet on the street),
把feet on the street中的ee替换成EE

$(patsubst,<>)

1.14 make的运行

1.14.1 make的退出码

make命令执行后有三个退出码:
0————表示成功执行
1————如果make运行时出现任何错误,其返回1
2————如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2

1.14.2 指定Makefile

我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的“-f”或是“--file参数”(“--makefile”参数也行)。
make -f hchen.mk

1.14.3 指定目标

一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。任何在makefile中的目标都可以被指定成终极目标,但是除了以“-”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。
'all'
这个伪目标是所有目标的目标,其功能一般是编译所有的目标
'clean'
这个伪目标功能是删除所有被make创建的文件
'install'
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去
'print'
这个伪目标的功能是例出改变过的源文件
'tar'
这个伪目标的功能是把源程序程序打包备份。也就是个tar文件
'dist'
这个伪目标的功能是创建一个压缩文件,一般是把tar文件压成z文件或者gz文件
'TAGS'
这个伪目标的功能是更新所有的目标,以备完整地重编译使用
'check'和'test'
这两个伪目标一般用来测试makefile的流程

1.14.4 检查规则

有时不想让makefile中的规则执行起来,只想检查一下我们的命令或是执行序列。可以使用如下参数:
'-n'
'--just-print'
'--dry-run'
'--recon'
以上均是不执行参数

'-t'
'--touch'
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成编译过的状态。

'-q'
'--question'
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印一条错误信息。

"-W"
"--what-if="
"--assume-new="
"--new-file="
这个参数需要指定一个文件,一般是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所产生的规则命令。

1.14.5 make的参数

“-b”,“-m”,这两个参数的作用是忽略和其他版本make的兼容性

“-B”,“--always-make”认为所有的目标都需要更新

“-C \

”,“--directory=\”指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。

1.15 隐含规则

隐含规则,是makefile中隐含的,早先约定了的,不需要再写出来的规则。
隐含规则会使用一些我们的系统变量,我们可以改变这些系统变量的值来定制隐含规则运行时的参数。如系统变量CFLAGS可以控制编译时的编译器参数。
可以通过“模式规则”的方式写下自己的隐含规则。
使用隐含规则生成需要的目标,所需要做的就是不要写出这个目标的规则。

1.15.1 隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。可以在makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在环境变量中设置这些值,无论怎么样,只要设置了这些特定的量,那么就会对隐含规则起作用。

1.15.2 定义模式规则

可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有“%”字符。“%”的意思是表示一个或多个任意字符。在依赖目标中同样可以使用“%”,只是依赖目标中的“%的取值”,取决于其目标。

自动化变量:
在模式规则中,目标和依赖文件都是一系列的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次对模式规则的解析时,都会是不同的目标和依赖文件。
自动化变量就是完成这个功能的。所谓自动化变量,就是这种变量会把模式所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。下面是所有的自动化变量及其说明:
\$a:
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,“$@”就是匹配与目标中模式定义的集合。
**\$%:
仅当目标是函数库文件中,表示规则中的目标成员名。
\$<**:
依赖目标中的第一个目标名字。如果依赖目标是以模式(即“%”)定义的,那么“$<”将是符合模式的一系列的文件集。注意,其实一个一个取出来的。
\$?:
所有比目标新的依赖目标的集合。以空格分隔。
\$^:
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。
\$+:
和“$^”很像,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
**\$***:
这个变量表示目标模式中“%”及其之前的部分。

(未完,待续......)