Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> E:安桌層及文件系統層的PRINTf輸出原理

E:安桌層及文件系統層的PRINTf輸出原理

編輯:關於Android編程

(/device/console操控台原理分析,通過調用此操控台來輸出信息,同時這兒涉及到/device/console調用TTY,然後TTY調用低層串口的分析 安桌LOG輸出原理)

LINUX內核源碼版本:linux-3.0.86

 

/dev/console即控制台,是與操作系統交互的設備,系統將一些調試信息直接輸出到控制台上,是TTY設備的一個子集

 

Tty:teltypewriters Teletypes簡稱電傳打印機:終端是一種字符型設備,它有多種類型,通常使用tty來簡稱各種類型的終端設備。Console是TTY設備的一種。Console調用到最低層就是TTY設備驅動。

TTY CONSOLE UART關系圖(由我的平台有4路串口,要分析到USER空間/dev/console最後是通過那一路口輸出的,注意和內核空間的printk的console操控台輸出端口是否一致,分析原理。由前面幾節分析可知printk輸出是由uboot傳入的參數來確定那一路串口輸出的的。Console=ttySAC0,115200n8第0路)

分析代碼前根據網絡資料得出的一張圖:

\

自己分析代碼後得到的圖:

\

 

紅色部分是分析了所有代碼後得出的結論:

下面過程很雜亂,到現在還是沒有分析清楚,只能是明白了個大概,因為結構體太多,沒交繁復雜。現在還是亂。只能再次分析一下流程(只針對/dev/console的寫過程。我們要知道用戶空間的console是支持輸入輸出的。Kernel空音的console只支持輸出信息。這是很重要的區別。)。

/dev/console

設備打開。得到tty_struct結構體,並且賦值在file中一個private_data中。同時由於

struct tty_driver *console_driver = console_device(&index);得來是由uboot傳入的參數來決定的。因此這兒/dev/console輸出0串口來決定。由其它節部分的參數解析部分得知。

struct tty_driver*console_driver = console_device(&index)->console_drivers(內核層register_console注冊函數時一個指針鏈表)->driver = c->device(c, index);

寫操作//把數據寫入file文件中的private_data指向的tty_struct中的一xmit變量(這兒有好層轉換),同時打開中斷。產生中斷條件。

Write->[ld->ops->write=n_tty_write]->[tty->ops=driver->ops]=[tty_operations uart_ops]=[.write= uart_write,]

 

3.中斷發送://中斷的注冊是在open函數中執行的。這兒自動過來執行的。

ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,

s3c24xx_serial_portname(port), ourport);---->

s3c24xx_serial_tx_chars->wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);發送數據

 

//下面是上面結論分析過程。

 

/dev/console字符設備注冊流程分析:

#define fs_initcall(fn)__define_initcall("5",fn,5)

chr_dev_init->tty_init->MKDEV(TTYAUX_MAJOR, 1)->console_fops操作函數

static const struct file_operationsconsole_fops= {

.llseek= no_llseek,

.read= tty_read,

.write= redirected_tty_write,

.poll= tty_poll,

.unlocked_ioctl= tty_ioctl,

.compat_ioctl= tty_compat_ioctl,

.open= tty_open,

.release= tty_release,

.fasync= tty_fasync,

};

完成了對設備的注冊,然後是打開設備和讀寫設備了,這個部分應該是用戶空間來完成的。我們不去看相關代碼,只從通用的打開讀寫通用操作方法來作分析。Open write read這三個函數由用戶空間來調用。當用戶空間調打開讀寫函數時會調用上面注冊的驅動的操作函數對應的函數。所以接下來分析Open write read

Open:tty_open

struct tty_driver *console_driver = console_device(&index)->console_drivers(內核層register_console注冊函數時一個指針鏈表)->driver = c->device(c, index);

retval = tty_alloc_file(filp);

struct tty_driver *console_driver = console_device(&index);

tty = tty_init_dev(driver, index, 0);//這個函數裡去構建LDISC結構體了,叫做線規程/線路規程line discipline

 

/*

//ldisc構建過程:

tty_init_dev(driver, index, 0);//tty_driver 0 0

initialize_tty_struct(tty, driver, idx);//tty_struct tty_driver 0

tty_ldisc_init(tty);

Ld->ops=tty_ldiscs[disc]=tty_ldiscs[0]

/*

\

ty_ldiscs怎麼得來的是關鍵:

start_kernel->console_init()->/* Setup the default TTY line discipline. */

tty_ldisc_begin();->/* Setup the default TTY line discipline. */

(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);->->

N_TTY=0

struct tty_ldisc_ops tty_ldisc_N_TTY = {

.magic = TTY_LDISC_MAGIC,

.name = "n_tty",

.open = n_tty_open,

.close = n_tty_close,

.flush_buffer = n_tty_flush_buffer,

.chars_in_buffer = n_tty_chars_in_buffer,

.read = n_tty_read,

.write = n_tty_write,

.ioctl = n_tty_ioctl,

.set_termios = n_tty_set_termios,

.poll = n_tty_poll,

.receive_buf = n_tty_receive_buf,

.write_wakeup = n_tty_write_wakeup

};

tty_ldiscs[disc] = new_ldisc;

tty_ldiscs[0] = tty_ldisc_N_TTY;等於上面TTY設備

通過上面分析可知

Ld->ops=tty_ldiscs[disc]=tty_ldiscs[0]=tty_ldisc_N_TTY

 

 

*/

tty->ldisc = ld;

tty_add_file(tty, filp);

struct tty_file_private *priv = file->private_dat=tty_struct;因此通過file文件中的private_data來操作tty_sruct。這個結構體時裡面包含tty_driver tty->ldisc多個變量。

 

 

*/

tty_add_file(tty, filp);

上面代碼完成通過文件的struct tty_file_private *priv = file->private_data變量來保存tty_struct(同時TTY結構體包含tty_drivers)驅動變量。

 

我們代碼的console_drivers如下:

static struct consoles3c24xx_serial_console= {

.name= S3C24XX_SERIAL_NAME,

.device=uart_console_device,

.flags= CON_PRINTBUFFER,

.index= -1,

.write= s3c24xx_serial_console_write,

.setup= s3c24xx_serial_console_setup,

.data= &s3c24xx_uart_drv,

};

struct tty_driver *uart_console_device(struct console *co, int *index)

{

struct uart_driver *p = co->data;

*index = co->index;//0通過UBOOT傳入參數等到0值

returnp->tty_driver;

}

static struct uart_drivers3c24xx_uart_drv= {

.owner= THIS_MODULE,

.driver_name= "s3c2410_serial",

.nr= CONFIG_SERIAL_SAMSUNG_UARTS,

#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE_SWITCH

.cons= NULL,

#else

.cons= S3C24XX_SERIAL_CONSOLE,

#endif

.dev_name= S3C24XX_SERIAL_NAME,

.major= S3C24XX_SERIAL_MAJOR,

.minor= S3C24XX_SERIAL_MINOR,

};

p->tty_driver是s3c24xx_uart_drv結構體的成員,但在上面數組的初始化中並沒有初始化此值,因此可以肯定是在在其它地方初始化的tty_driver的。

下面來分析tty_driver的初始化流程:

s3c24xx_serial_modinit->uart_register_driver(&s3c24xx_uart_drv)->:

struct tty_driver *normal;

drv->tty_driver = normal;

tty_set_operations(normal, &uart_ops); //driver->ops = op;

/*

static const structtty_operations uart_ops= {

.open= uart_open,

.close= uart_close,

.write= uart_write,

.put_char= uart_put_char,

.flush_chars= uart_flush_chars,

.write_room= uart_write_room,

.chars_in_buffer= uart_chars_in_buffer,

.flush_buffer= uart_flush_buffer,

.ioctl= uart_ioctl,

.throttle= uart_throttle,

.unthrottle= uart_unthrottle,

.send_xchar= uart_send_xchar,

.set_termios= uart_set_termios,

.set_ldisc= uart_set_ldisc,

.stop= uart_stop,

.start= uart_start,

.hangup= uart_hangup,

.break_ctl= uart_break_ctl,

.wait_until_sent= uart_wait_until_sent,

#ifdef CONFIG_PROC_FS

.proc_fops= &uart_proc_fops,

#endif

.tiocmget= uart_tiocmget,

.tiocmset= uart_tiocmset,

.get_icount= uart_get_icount,

#ifdef CONFIG_CONSOLE_POLL

.poll_init= uart_poll_init,

.poll_get_char= uart_poll_get_char,

.poll_put_char= uart_poll_put_char,

#endif

};

tty->ops= driver->ops;

struct tty_driver *driver;

struct tty_driver *console_driver = console_device(&index);

tty->ops=driver->ops->console_drivers->driver = c->device(c, index)->

tty_operations uart_ops=tty_operationsuart_ops;--->tty->ops=driver->ops=tty_operations uart_ops

 

*/

通過上面代碼分析可知:用操作/dev/console寫函數時

.write= redirected_tty_write,

static ssize_t tty_write(struct file *file, const char __user *buf,

size_t count, loff_t *ppos)

struct tty_struct *tty = file_tty(file);//包含tty_driver結構體。

ld = tty_ldisc_ref_wait(tty);

ld->ops->write//////因此找到這個函數才是我們的根本,看是如何實現的。????????????????

通過前面分析知:

Ld->ops=tty_ldiscs[disc]=tty_ldiscs[0]=tty_ldisc_N_TTY

因此ld->ops->write=n_tty_write//tty_ldisc_N_TTY中的函數完成線程規程檢查

最後還是調用tty->ops->write(tty, b, nr);來輸出數據。規則檢查部分不能把數據輸出的串口

綜上:對於/dev/console設備的寫函數的執行流程如下:

Write->[ld->ops->write=n_tty_write]->[tty->ops=driver->ops]=[tty_operations uart_ops]=[.write= uart_write,]

static int uart_write(struct tty_struct *tty,

const unsigned char *buf, int count)

__uart_start(tty);

port->ops->start_tx(port);//ops= s3c24xx_serial_ops;

static struct uart_opss3c24xx_serial_ops= {

.pm= s3c24xx_serial_pm,

.tx_empty= s3c24xx_serial_tx_empty,

.get_mctrl= s3c24xx_serial_get_mctrl,

.set_mctrl= s3c24xx_serial_set_mctrl,

.stop_tx= s3c24xx_serial_stop_tx,

.start_tx= s3c24xx_serial_start_tx,

.stop_rx= s3c24xx_serial_stop_rx,

.enable_ms= s3c24xx_serial_enable_ms,

.break_ctl= s3c24xx_serial_break_ctl,

.startup= s3c24xx_serial_startup,

.shutdown= s3c24xx_serial_shutdown,

.set_termios= s3c24xx_serial_set_termios,

.type= s3c24xx_serial_type,

.release_port= s3c24xx_serial_release_port,

.request_port= s3c24xx_serial_request_port,

.config_port= s3c24xx_serial_config_port,

.verify_port= s3c24xx_serial_verify_port,

.wake_peer= s3c24xx_serial_wake_peer,

};

s3c24xx_serial_start_tx//這個函數是打開中斷 我們/dev/console應該是通過中斷來把數據發送出去的。數據存在struct circ_buf xmit結構體中。這個函數只是把數據緩存在變量中和打開串口,發送數據是在中斷中進行的。tty->ops->open(tty, filp);====s3c24xx_serial_startup打開中斷ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,

s3c24xx_serial_portname(port), ourport);注冊串口發送函數。

static irqreturn_t

static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)

struct s3c24xx_uart_port *ourport = id;

struct circ_buf *xmit = &port->state->xmit;//這個是在s3c24xx_serial_start_tx中去指定的。

/*

ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,

s3c24xx_serial_portname(port), ourport);*/

上面中斷函數的id與/dev/console的tty_drvier是同一個變量。所以在觸發中斷發送時能正確的發送數據。wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);

 

 

 

 

/dev/tty字符設備注冊流程分析:

#define fs_initcall(fn)__define_initcall("5",fn,5)

chr_dev_init->tty_init->MKDEV(TTYAUX_MAJOR, 0)->tty_fops

上面只是注冊了一個tty設備。Tty其實是一多個的。最後是tty_register_driver-》for (i = 0; i < driver->num; i++) {

d = tty_register_device(driver, i, NULL);}-》device_create(tty_class, device, dev, NULL, name);

上面完成多個設備的注冊tty設備的注冊,這兒主要是串串的。所有的tty一字符輸輸入輸出設備都可以用tty_register_driver來注冊。可以注冊多個。上面/dev/console只是利用了其中的一類來解決問題。Tty設備是可以單獨來解決問題的。用tty_register_driver注冊。但我們分析的串口部分是用的tty_init來注冊設備。然後通過打開函數來與tty_struct和tty_drvier關聯解決問題。大致分析了代碼發/dev/conosle/ /dev/tty邏輯差不多。細節就不去分析了。可以明確的是對這兩個設備的輸入輸出都是由uboot傳入參數來決是那一個口的。前一個用於系統信息調試 ,後一個應該是作其它功能。對於這設備當作一般設備來理解。只是加入很多中間層,所以分析起來比較麻煩。暫不去細究。分析了好幾天還是亂。不是清晰的邏輯。

 

static const struct file_operationstty_fops= {

.llseek= no_llseek,

.read= tty_read,

.write= tty_write,

.poll= tty_poll,

.unlocked_ioctl= tty_ioctl,

.compat_ioctl= tty_compat_ioctl,

.open= tty_open,

.release= tty_release,

.fasync= tty_fasync,

};

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved