1. ホーム
  2. アンドロイド

Android ロギングシステムドライバ ロガーソースコード解析

2022-02-24 09:35:41

        ご存知のように、Androidはカーネル空間にドライバとして実装された軽量なロギングシステムを提供し、このロギングシステムをユーザ空間で利用するために、Androidアプリケーションを書くかシステムコンポーネントを書くかによって、JavaインタフェースとC/C++インタフェースが提供されています。前回の記事で <スパン Androidシステム開発におけるLOGの活用法入門 今回は、Loggerドライバのソースコードをさらに解析し、Androidのロギングシステムをより深く理解することを目指します。

書籍「"Android System Source Code Scenario Analysis"」がincomingprogrammer.comで発売されました( http://0xcc0xcd.com をクリックすると入場できます。

        Androidのロギングシステムは、ドライバとしてカーネル空間に実装されているため、解析するにはAndroidカーネルのソースコードを入手する必要がありますので、前出の Ubuntu上のAndroidの最新ソースコードのダウンロード、コンパイル、インストール 最新のAndroidカーネル(Linux Kernel)のソースコードのダウンロード、コンパイル、およびUbuntuへのインストール Androidのソースコードプロジェクトをダウンロードするための2つの記事。Loggerドライバは、主に以下の2つのファイルから構成されています。

       kernel/common/drivers/staging/android/logger.h

       kernel/common/drivers/staging/android/logger.c

       次に、Logger ドライバーの関連データ構造を個別に紹介し、Logger ドライバーのソースコード、ロギング・システム初期化シナリオ、ロギング・リード・シナリオ、ロギング・ライト・シナリオのシナリオ解析をそれぞれ実行します。

       I. ロガードライバーに関連するデータ構造

      まず、logger.hヘッダーファイルの内容から見ていきましょう。

#ifndef _LINUX_LOGGER_H
#define _LINUX_LOGGER_H

#include <linux/types.h>
#include <linux/ioctl.h>

struct logger_entry {
	__u16 len; /* length of the payload */
	__u16 __pad; /* no matter what, we get 2 bytes of padding */
	__s32 pid; /* generating process's pid */
	__s32 tid; /* generating process's tid */
	__s32 sec; /* seconds since Epoch */
	__s32 nsec; /* nanoseconds */
	char msg[0]; /* the entry's payload */
};

#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
#define LOGGER_LOG_MAIN "log_main" /* everything else */

#define LOGGER_ENTRY_MAX_LEN (4*1024)
#define LOGGER_ENTRY_MAX_PAYLOAD \
	(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

#define __LOGGERIO 0xAE

#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */
#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */
#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */

#endif /* _LINUX_LOGGER_H */

        struct logger_entryはLogレコードを記述するための構造体です。メンバ変数lenはこのレコードのペイロードの長さを記録し、ペイロードはログレコード自体の長さを指定しますが、このレコードを記述するために使用するstruct logger_entry 構造体を指定するわけではありません。android.util.Logインターフェースを呼び出してロギングシステムを利用する際に、ログの優先度、Tag文字列、Msg文字列を指定し、優先度 + Tag + Msgの内容の長さを合計して、レコードのペイロード長にすることを思い出してください。メンバ変数__padは構造体の整列に使用される。メンバ変数pidとtidはどのプロセスが記録を書いたかを記録する。メンバ変数secとnsecはログが書かれた時間を記録する。メンバ変数msgはペイロードの内容を記録し、そのサイズはメンバー変数lenで決定される。

       次に、2つのマクロを定義します。

       #define LOGGER_ENTRY_MAX_LEN (4*1024)

       #define LOGGER_ENTRY_MAX_PAYLOAD

                         (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

      この2つのマクロからわかるように、各ログレコードのペイロード長と構造体logger_entryの長さは4Kバイトを超えることはできません。

      logger.hファイルには、他にも読者が自分で解析できるマクロが定義されていますので、以下の解析で遭遇したときに詳しく説明します。

      logger.cファイルの他の関連するデータ構造の定義を見てください。

/*
 * struct logger_log - represents a specific log, such as 'main' or 'radio'
 * This structure lives from module insertion until module removal, so it does
 * This structure lives from module insertion until module removal, so it does
 * The structure is protected by the
 The structure is protected by the * mutex 'mutex'.
 */
struct logger_log {
	unsigned char * buffer; /* the ring buffer itself */
	struct miscdevice misc; /* misc device representing the log */
	wait_queue_head_t wq; /* wait queue for readers */
	struct list_head readers; /* this log's readers */
	struct mutex mutex; /* mutex protecting buffer */
	size_t w_off; /* current write head offset */
	size_t head; /* new readers start here */
	size_t size; /* size of the log */
};

/*
 * struct logger_reader - a logging device open for reading
 /* size_t size; /* size of the log */ }
 * This object lives from open to release, so we don't need additional * reference counting.
 * The structure is protected by log->mutex.
 */
struct logger_reader {
	struct logger_log * log; /* associated log */
	struct list_head list; /* entry in logger_log's list */
	size_t r_off; /* current read head offset */
};

/* logger_offset - returns index 'n' into the log via (optimized) modulus */
#define logger_offset(n) ((n) & (log-> size - 1))

        struct logger_log は、実際にロギングが格納される場所です。 buffer メンバ変数は、ロギング情報を格納するためのメモリバッファで、そのサイズは size メンバ変数で決定されます。miscメンバ変数からわかるように、loggerドライバが使用するデバイスはmiscタイプのデバイスであり、Androidエミュレータ上でcat /proc/devicesコマンドを実行することでアクセスできます(参照) 最新のAndroidカーネルのソースコード(Linux Kernel)をダウンロードし、コンパイルしてUbuntuにインストールします。 マスターデバイス番号の詳細については Android学習ブート編 メンバ変数wqは、ログ読み込み待ちのプロセスを保持する待ち行列です。メンバ変数readersは、現在ログを読んでいるプロセスを保持するために用いられ、構造体logger_readerで記述されます。メンバ変数mutexは、ログを同時アクセスから保護するために用いられるミューテックス(無関数関数)です。ご覧のように、ここでのログシステムの読み書き問題は、実際にはproducer-consumer問題であり、したがって、ログへの同時アクセスを保護するために相互排除が必要です。w_off メンバー変数は、次のログがどこから書き込まれるべきかを追跡するために使用されます。head メンバ変数は開いているログファイルのどこからログを読むべきかを示すために使われます。

       struct logger_reader はログを読み込むプロセスを表し、log メンバ変数は読み込むべきログバッファを指す。 list メンバ変数は他のリーダープロセスに接続するために使われる。r_off メンバ変数は読み込むべきログのバッファ内の現在位置を示す。

       struct logger_log 構造体のログ情報を保持するためのメモリバッファは、循環型バッファで、再利用される。バッファの内容は、struct logger_entry 単位で保持され、各単位は次のように構成されます。

       struct logger_entry | 優先度 | タグ | msg

       メモリバッファのバッファは循環バッファなので、オフセット値を与えると、バッファ内の位置は以下のlogger_offsetで決定されます。

       #define logger_offset(n) ((n) & (log->size - 1))

       II. Logger ドライバモジュールの初期化処理の解析。

       logger.cファイルの続きで、3つのロギング・デバイスが定義されています。

/*
 * Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which
 * must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than
 * LONG_MAX minus LOGGER_ENTRY_MAX_LEN.
 */
#define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \
static unsigned char _buf_ ## VAR[SIZE]; \
static struct logger_log VAR = { \
	.buffer = _buf_ ## VAR, \
	.misc = { \
		.minor = MISC_DYNAMIC_MINOR, \
		.name = NAME, \
		.fops = &logger_fops, \
		.parent = NULL, \
	}, \
	.wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \
	.readers = LIST_HEAD_INIT(VAR .readers), \
	.mutex = __MUTEX_INITIALIZER(VAR .mutex), \
	.w_off = 0, \
	.head = 0, \
	.size = SIZE, \
};

DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024)
DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024)
DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024)

       はそれぞれlog_main, log_events, log_radio という名前で、サブデバイスの番号はMISC_DYNAMIC_MINOR、つまり登録時に動的に割り当てられるものです。logger.h ファイルに、これら3つのマクロの定義があります。

       #define LOGGER_LOG_RADIO "log_radio" /* ラジオ関連メッセージ */
       #define LOGGER_LOG_EVENTS "log_events" /* システム/ハードウェアイベント */
       #define LOGGER_LOG_MAIN "log_main" /* その他すべて */

       コメントには、3つのロギングデバイスの目的が書かれています。登録されているロギングデバイスのファイル操作方法は、logger_fopsです。

static struct file_operations logger_fops = {
	.owner = THIS_MODULE,
	.read = logger_read,
	.aio_write = logger_aio_write,
	.poll = logger_poll,
	.unlocked_ioctl = logger_ioctl,
	.compat_ioctl = logger_ioctl,
	.open = logger_open,
	.release = logger_release,
};

       ログドライバモジュールの初期化関数はlogger_initです。

static int __init logger_init(void)
{
	int ret;

	ret = init_log(&log_main);
	if (unlikely(ret))
		goto out;

	ret = init_log(&log_events);
	if (unlikely(ret))
		goto out;

	ret = init_log(&log_radio);
	if (unlikely(ret))
		goto out;

out:
	return ret;
}
device_initcall(logger_init);

        logger_init関数は、init_log関数を呼び出すことで、上記の3つのロギングデバイスを初期化します。

static int __init init_log(struct logger_log *log)
{
	int ret;

	ret = misc_register(&log->misc);
	if (unlikely(ret)) {
		printk(KERN_ERR "logger: failed to register misc "
		       "device for log '%s'! \n", log->misc.name);
		return ret;
	}

	printk(KERN_INFO "logger: created %luK log '%s'\n",
	       (unsigned long) log->size >> 10, log->misc.name);

	return 0;
}

        init_log関数は、基本的にmisc_register関数を呼び出して、miscデバイスを登録します。この関数は、kernel/common/drivers/char/misc.cファイルで次のように定義されています。

/**
 * misc_register - register a miscellaneous device
 * @misc: device structure
 * 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
 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.
 For other cases * the minor number requested is used.
 * The structure passed is linked into the kernel and may not be
 The structure passed is linked into the kernel and may not be * destroyed until it has been unregistered.
 For other cases * the minor number requested is used.
 * A zero is returned on success and a negative errno code for
 * failure.
 */

int misc_register(struct miscdevice * misc)
{
        struct miscdevice * c;
        dev_t dev;
        int err = 0;

        INIT_LIST_HEAD(&misc->list);

        mutex_lock(&misc_mtx);
        list_for_each_entry(c, &misc_list, list) {
                if (c->minor == misc->minor) {
                        mutex_unlock(&misc_mtx);
                        return -EBUSY;
                }
        }

        if (misc->minor == MISC_DYNAMIC_MINOR) {
                int i = DYNAMIC_MINORS;
                while (--i >= 0)
                        if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
                                break;
                if (i<0) {
                        mutex_unlock(&misc_mtx);
                        return -EBUSY;
                }
                misc->minor = i;
        }

        if (misc->minor < DYNAMIC_MINORS)
                misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
        dev = MKDEV(MISC_MAJOR, misc->minor);

        misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
                                          "%s", misc->name);
        if (IS_ERR(misc->this_device)) {
                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;
}


        登録が完了したら、device_createでデバイスファイルのノードを作成します。ここでは、デバイスファイル /dev/log/main, /dev/log/events, /dev/log/radio を作成し、ユーザ空間がこれら3つのファイルを読み書きすることでドライバと対話できるようにする。

        III. Loggerドライバのロギング読み込み処理の解析。

        logger.cファイルの続きで、ログデバイスファイルを読み込むための登録されたメソッドはlogger_readです。

/*
 * logger_read - our log's read() method
 *
 * Behavior:
 * - O_NONBLOCK works.
 * - O_NONBLOCK works
 * - If there are no log entries to read, blocks until log is written to
 * - Atomically reads exactly one log entry
 *Atomically reads exactly one log entry to
 * Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read
 Will set errno to EINVAL if read * buffer is insufficient to hold next entry.
 */
static ssize_t logger_read(struct file *file, char __user *buf,
			   size_t count, loff_t *pos)
{
	struct logger_reader *reader = file->private_data;
	struct logger_log *log = reader->log;
	ssize_t ret;
	DEFINE_WAIT(wait);

start:
	while (1) {
		prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);

		mutex_lock(&log->mutex);
		ret = (log->w_off == reader->r_off);
		mutex_unlock(&log->mutex);
		if (!ret)
			break;

		if (file->f_flags & O_NONBLOCK) {
			ret = -EAGAIN;
			break;
		}

		if (signal_pending(current)) {
			ret = -EINTR;
			break;
		}

		schedule();
	}

	finish_wait(&log->wq, &wait);
	if (ret)
		return ret;

	mutex_lock(&log->mutex);

	/* is there still something to read or did we race? */
	if (unlikely(log->w_off == reader->r_off)) {
		mutex_unlock(&log->mutex);
		goto start;
	}

	/* get the size of the next entry */
	ret = get_entry_len(log, reader->r_off);
	if (count < ret) {
		ret = -EINVAL;
		goto out;
	}

	/* get exactly one entry from the log */
	ret = do_read_log_to_user(log, reader, buf, ret);

out:
	mutex_unlock(&log->mutex);

	return ret;
}

       なお、関数の冒頭で、読み込むロギングコンテキストを示す logger_reader 構造体は、以下のように logger_open メソッドで開くデバイスファイルを開く際に設定するファイルポインタの private_data メンバー変数内に格納されています。

/*
 * logger_open - the log's open() file operation
 * * logger_open
 * Note how near a no-op this is in the write-only case. keep it that way!
 Keep it that way! */
static int logger_open(struct inode *inode, struct file *file)
{
	struct logger_log *log;
	int ret;

	ret = nonseekable_open(inode, file);
	if (ret)
		return ret;

	log = get_log_from_minor(MINOR(inode->i_rdev));
	if (!log)
		return -ENODEV;

	if (file->f_mode & FMODE_READ) {
		struct logger_reader *reader;

		reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);
		if (!reader)
			return -ENOMEM;

		reader->log = log;
		INIT_LIST_HEAD(&reader->list);

		mutex_lock(&log->mutex);
		reader->r_off = log->head;
		list_add_tail(&reader->list, &log->readers);
		mutex_unlock(&log->mutex);

		file->private_data = reader;
	} else
		file->private_data = log;

	return 0;
}

       新しいログデバイスファイルがオープンされると、ログはlog->headの位置から読み込まれ、struct logger_readerのメンバ変数r_offに格納されます。

       スタートマーカーのwhileループは、ログが読み込まれるのを待っています。読み込むべき新しいログがもうない場合、読み込み処理はスリープし、起床する前に新しいログが書き込まれるのを待ちます。これはprepare_waitとscheduleの呼び出しによって行われます。もし、読み込むべき新しいログがなく、デバイスファイルがノンブロッキングのO_NONBLOCKでオープンされていないか、この時点で処理すべきシグナルがある場合(signal_pending(current))、それは直接戻り、新しいログが書き込まれるのを待つことはしないでしょう。現在読み込み可能な新しいログがあるかどうかを判断する方法は、以下の通りです。

       ret = (log->w_off == reader->r_off) となります。

       つまり、現在のバッファの書き込み位置と、現在の読み込み処理の読み込み位置が等しいかどうかを判断し、等しくない場合は、新たに読み込むべきログが存在することになるのです。

       次に、新たに読み込むべきログがある場合、それは、まず、get_entry_lenを介して次に読み込めるログレコードの長さです。このことから、ログ読み込み処理はログレコードを1レコードずつ読み込んでいることが分かります。get_entry_lenは以下のように実装されています。

/*
 * get_entry_len - Grabs the length of the payload of the next entry starting
 * from 'off'.
 *
 * Caller needs to hold log->mutex.
 */
static __u32 get_entry_len(struct logger_log *log, size_t off)
{
	__u16 val;

	switch (log->size - off) {
	case 1:
		memcpy(&val, log->buffer + off, 1);
		memcpy(((char *) &val) + 1, log->buffer, 1);
		break;
	default:
		memcpy(&val, log->buffer + off, 2);
	}

	return sizeof(struct logger_entry) + val;
}

        上述したように、各ログレコードは、ログレコードを記述する構造体logger_entryと、ログ本体であるペイロードの大きく2つの部分から構成されています。struct logger_entryの長さは固定なので、ペイロードの長さが分かれば、ログレコード全体の長さが分かります。そして、ペイロードの長さはstruct logger_entryのメンバ変数lenに記録されており、メンバ変数lenのアドレスはstruct logger_entryのアドレスと同じなので、レコードの先頭の2バイトを読むだけでいいのだそうです。また、ロギングバッファは循環型なので、2バイトを連結する以外は、1バイト目がバッファの最後のバイトに、2バイト目がバッファの最初のセクションに格納される可能性があります。そこで、前者については、バッファの最後のバイトと最初のバイトをそれぞれローカル変数valに読み込んでログレコードのペイロード長を求める場合と、後者については、連続する2バイトの値を直接ローカル変数valに読み込む場合の2つのケースが考えられている。この2つのケースは、ログバッファのサイズとバッファ内の読み込むべきログレコードの位置の差を判定し、その差が1であれば前者のケースであることを意味することで区別される。最後に、ペイロードの長さである val を struct logger_entry の長さに足して、読み込むべきログレコードの長さの合計を求める。

       次に、読み込むログの長さが決まったら、 do_read_log_to_user 関数を呼び出して、実際の読み込み動作を行います。

static ssize_t do_read_log_to_user(struct logger_log *log,
				   struct logger_reader *reader,
				   char __user *buf,
				   size_t count)
{
	size_t len;

	/* We read from the log in two disjoint operations.
	 * We read from the log in two disjoint operations.
	 First, we read from * the current read head offset up to 'count' bytes or to the end of
	 First, we read from * the current read head offset up to 'count' bytes or to the end of * the log, whichever comes first.
	 */
	len = min(count, log->size - reader->r_off);
	if (copy_to_user(buf, log->buffer + reader->r_off, len))
		return -EFAULT;

	/*
	 * Second, we read any remaining bytes, starting back at the head of
	 * the log.
	 */
	if (count ! = len)
		if (copy_to_user(buf + len, log->buffer, count - len))
			return -EFAULT;

	reader->r_off = logger_offset(reader->r_off + count);

	return count;
}

        この関数は、単に copy_to_user 関数を呼び出して、カーネル空間にある指定されたログバッファの内容をユーザ空間のメモリバッファにコピーして終了すると同時に、現在の読み込みログプロセスのコンテキスト情報の読み込みオフセット r_off を次のログレコードの開始位置まで進める。

        IV.  Loggerドライバのログレコード書き込み処理の解析。

        logger.cファイルの続きで、ロギングデバイスファイルへの書き込みメソッドとして登録されているのはlogger_aio_writeです。

/*
 * logger_aio_write - our write method, implementing support for write(),
 * writev(), and aio_write(). Writes are our fast path, and we try to optimize
 * them above all else.
 */
ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,
			 unsigned long nr_segs, loff_t ppos)
{
	struct logger_log *log = file_get_log(iocb->ki_filp);
	size_t orig = log->w_off;
	struct logger_entry header;
	struct timespec now;
	ssize_t ret = 0;

	now = current_kernel_time();

	header.pid = current->tgid;
	header.tid = current->pid;
	header.sec = now.tv_sec;
	header.nsec = now.tv_nsec;
	header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD);

	/* null writes succeed, return zero */
	if (unlikely(!header.len))
		return 0;

	mutex_lock(&log->mutex);

	/*
	 * Fix up any readers, pulling them forward to the first readable
	 * We do this now
	 We do this now * because if we partially fail, we can end up with clobbered log
	 We do this now * because if we partially fail, we can end up with clobbered log * entries that encroach on readable buffer.
	 */
	fix_up_readers(log, sizeof(struct logger_entry) + header.len);

	do_write_log(log, &header, sizeof(struct logger_entry));

	while (nr_segs-- > 0) {
		size_t len;
		ssize_t nr;

		/* figure out how much of this vector we can keep */
		len = min_t(size_t, iov-> iov_len, header.len - ret);

		/* write out this segment's payload */
		nr = do_write_log_from_user(log, iov->iov_base, len);
		if (unlikely(nr < 0)) {
			log->w_off = orig;
			mutex_unlock(&log->mutex);
			return nr;
		}

		iov++;
		ret += nr;
	}

	mutex_unlock(&log->mutex);

	/* wake up any blocked readers */
	wake_up_interruptible(&log->wq);

	return ret;
}

        入力パラメータiocbはioコンテキスト、iovは書き込むべきコンテンツ、長さはnr_segsであり、書き込むべきセグメントがnr_segs個あることを示す。書き込まれる各ログの構造は次のような形式であることが分かっています。

        struct logger_entry | priority | tag | msg

        priority、tag、msgの3つのセグメントは、iovパラメータによってユーザー空間から渡され、iovの3つの要素に対応する。logger_entryはカーネル空間で次のように構築されます。

        struct logger_entry ヘッダーです。
struct timespec になりました。
<スパン now = current_kernel_time();
<スパン header.pid = current->tgid;
header.tid = current->pid;
header.sec = now.tv_sec;
header.nsec = now.tv_nsec;
header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD) とします。

        次に、do_write_log を呼び出して、まず logger_entry 構造体をログバッファに書き込みます。

/*
 * do_write_log - writes 'len' bytes from 'buf' to 'log'
 *
 * The caller needs to hold log->mutex.
 */
static void do_write_log(struct logger_log *log, const void *buf, size_t count)
{
	size_t len;

	len = min(count, log->size - log->w_off);
	memcpy(log->buffer + log->w_off, buf, len);

	if (count ! = len)
		memcpy(log->buffer, buf + len, count - len);

	log->w_off = logger_offset(log->w_off + count);

}

       logger_entry はカーネルスタック空間に割り当てられているので、memcpy で直接コピーすればOKです。

       次に、whileループでiovの内容をログバッファに書き込みます。つまり、ログ優先度priority、ログTag、ログ本文Msg:を書き込みます。

while (nr_segs-- > 0) {
		size_t len;
		ssize_t nr;

		/* figure out how much of this vector we can keep */
		len = min_t(size_t, iov-> iov_len, header.len - ret);

		/* write out this segment's payload */
		nr = do_write_log_from_user(log, iov->iov_base, len);
		if (unlikely(nr < 0)) {
			log->w_off = orig;
			mutex_unlock(&log->mutex);
			return nr;
		}

		iov++;
		ret += nr;
}

         iovの内容はユーザー空間から受け渡されるので、書き込むにはdo_write_log_from_userを呼び出す必要があります。

static ssize_t do_write_log_from_user(struct logger_log *log,
				      const void __user *buf, size_t count)
{
	size_t len;

	len = min(count, log->size - log->w_off);
	if (len && copy_from_user(log->buffer + log->w_off, buf, len))
		return -EFAULT;

	if (count ! = len)
		if (copy_from_user(log->buffer, buf + len, count - len))
			return -EFAULT;

	log->w_off = logger_offset(log->w_off + count);

	return count;
}

        ここで、もう一つ重要なステップが抜けています。

 /*
  * Fix up any readers, pulling them forward to the first readable
  * entry after (what will be) the new write offset.
  We do this now * because if we partially fail, we can end up with clobbered log
  We do this now * because if we partially fail, we can end up with clobbered log * entries that encroach on readable buffer.
  */
fix_up_readers(log, sizeof(struct logger_entry) + header.len);

        なぜfix_up_readerを呼び出す必要があるのでしょうか?また、この関数は何のためにあるのでしょうか?まあ、ログバッファはリサイクルされるからです。つまり、古いログレコードの読み込みが間に合わず、バッファが枯渇した場合、新しいレコードを受け入れるために古いレコードを上書きする必要があります。そして、この上書きされる部分の内容は、ある読者が次に読むべきログがある場所であったり、新しい読者のためのログ読み出し開始位置ヘッドがある場所であったりするのである。したがって、これらの位置は、新しい有効な位置を指すように調整する必要があります。fix_up_reader 関数の実装を見てみましょう。

/*
 * fix_up_readers - walk the list of all readers and "fix up" any who were
 * lapped by the writer; also do the same for the default "start head".
 * We do this by "pulling forward" the readers and start head to the first
 * entry after the new write head.
 *The caller needs to hold the log.
 * The caller needs to hold log->mutex.
 */
static void fix_up_readers(struct logger_log *log, size_t len)
{
	size_t old = log->w_off;
	size_t new = logger_offset(old + len);
	struct logger_reader *reader;

	if (clock_interval(old, new, log->head))
		log->head = get_next_entry(log, log->head, len);

	list_for_each_entry(reader, &log->readers, list)
		if (clock_interval(old, new, reader->r_off))
			reader->r_off = get_next_entry(log, reader->r_off, len);
}

        log->headとすべてのリーダーリーダーの現在の読み取りオフセットreader->r_offがカバーエリア内にあるかどうかを判断し、もしあれば、get_next_entryを呼び出して次の有効レコードの開始位置を取得し、現在位置を調整します。

/*
 * get_next_entry - return the offset of the first valid entry at least 'len'
 * bytes after 'off'.
 *
 * Caller must hold log->mutex.
 */
static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)
{
	size_t count = 0;

	do {
		size_t nr = get_entry_len(log, off);
		off = logger_offset(off + nr);
		count += nr;
	} while (count < len);

	return off;
}

        また、log->headとすべてのリーダーリーダーの現在のリードオフセットreader->r_offがカバーされている領域内にあるかどうかを判断するには、clock_interval関数で行います。

/*
 * clock_interval - is a < c < b in mod-space? Put another way, does the line
 * from a to b cross c?
 */
static inline int clock_interval(size_t a, size_t b, size_t c)
{
	if (b < a) {
		if (a < c || b >= c)
			return 1;
	} else {
		if (a < c & & b > = c)
			return 1;
	}

	return 0;
}

        最後に、ログが書き込まれた後、新しいログを待っているリーダープロセ スを起動する必要があります。

        /* ブロックされたリーダーを起動する */
wake_up_interruptible(&log->wq);

        この時点でLoggerドライバの主要なロジックが解析され、他にもlogger_poll、logger_ioctl、logger_release関数などのインターフェースがあり、自分で読んで解析するのは比較的簡単なことです。ここでもう一点、Loggerドライバモジュールはシステム終了時にアンロードされないため、このモジュールには module_exit 関数があり、モジュール内部で定義されたオブジェクトの参照カウント技法は使用されない。

      今回は、Androidのロギングシステムのカーネル空間での実装に焦点を当てました。次回は、ユーザー空間のAndroidアプリケーションが利用できるJavaおよびC/C++のLOG呼び出しインターフェースの実装について説明していきますので、ご期待ください。

ラオ・ルオの新浪微博。 http://weibo.com/shengyangluo とフォローを歓迎します