【驅動】串列埠驅動分析(二)-tty core

嵌入式與Linux那些事發表於2023-11-30

前言

tty這個名稱源於電傳打位元組的簡稱,在linux表示各種終端,終端通常都跟硬體相對應。比如對應於輸入裝置鍵盤滑鼠,輸出裝置顯示器的控制終端和串列埠終端。也有對應於不存在裝置的pty驅動。在如此眾多的終端模型之中,linux是怎麼將它們統一建模的呢?這就是我們今天要討論的問題。

tty驅動概貌

tty架構如下所示:

如上圖所示,使用者空間主要是透過系統呼叫與tty core互動。tty core根據用空間操作的型別再選擇跟line disciplinetty driver互動。

例如,設定硬體的ioctl指令就直接交給tty_driver處理。read和write操作就會交給 line discipline處理。

Line discipline是線路規程的意思。正如它的名字一樣,它表示的是這條終端”執行緒”的輸入與輸出規範設定。主要用來進行輸入/輸出資料的預處理。

處理之後,就會將資料交給tty driver ,它將字元轉換成終端可以理解的字串。將其傳給終端裝置。

值得注意的是,這個架構沒有為tty driver 提供read操作。也就是說tty coreline discipline都沒有辦法從tty driver裡直接讀終端資訊。這是因為tty driver對應的hardware並不一定是輸入資料和輸出 資料的共同負載者。

例如控制終端,輸出裝置是顯示器,輸入裝置是鍵盤。基於這樣的原理。在line discipline中有一個輸入快取區,並提供了一個名叫receive_buf()的介面函式。對應的終端裝置只要呼叫line discipinereceiver_buf函式,將資料寫入到輸入快取區就可以了。如果一個裝置同時是輸入裝置又是輸出裝置。那在裝置的中斷處理中呼叫receive_buf()將資料寫入即可.

tty驅動介面分析

tty_init()

/*
 * Ok, now we can initialize the rest of the tty devices and can count
 * on memory allocations, interrupts etc..
 */
int __init tty_init(void)
{
	tty_sysctl_init();
	cdev_init(&tty_cdev, &tty_fops);
	if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
		panic("Couldn't register /dev/tty driver\n");
	device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

	cdev_init(&console_cdev, &console_fops);
	if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
		panic("Couldn't register /dev/console driver\n");
	consdev = device_create_with_groups(tty_class, NULL,
					    MKDEV(TTYAUX_MAJOR, 1), NULL,
					    cons_dev_groups, "console");
	if (IS_ERR(consdev))
		consdev = NULL;

#ifdef CONFIG_VT
	vty_init(&console_fops);
#endif
	return 0;
}

tty_init主要做了以下工作:

  1. 初始化 tty 子系統的 sysctl 相關設定,包括註冊 sysctl 引數、建立 sysctl 目錄等。
  2. 初始化 tty 裝置的字元裝置物件,並將其與 tty 裝置操作函式 tty_fops 繫結。同時,建立一個名為 "tty" 的 tty 裝置節點,並將其裝置號設定為 MKDEV(TTYAUX_MAJOR, 0)
  3. 初始化控制檯裝置的字元裝置物件,並將其新增到字元裝置系統中。同時,建立一個名為 "console" 的控制檯裝置節點,並將其裝置號設定為 MKDEV(TTYAUX_MAJOR, 1)。該控制檯裝置節點還將在 sysfs 中建立一個名為 "console" 的目錄,並在該目錄下建立多個屬性檔案,用於控制控制檯的一些屬性。
  4. 如果核心支援虛擬終端,則初始化虛擬終端。

這裡我們看到了熟悉的cdev_init(),device_create()之類的函式,這正是字元裝置的建立流程。因此,我們說串列埠驅動也是一個字元裝置驅動。

而在serial8250_init()中,會呼叫platform_driver_register()去註冊serial8250_isa_driver,在裝置樹節點和serial8250_isa_driver name匹配的時候,就會進入probe流程。因此,也可以說串列埠驅動是匯流排裝置驅動模型。

tty_alloc_driver

/* Use TTY_DRIVER_* flags below */
#define tty_alloc_driver(lines, flags) \
		__tty_alloc_driver(lines, THIS_MODULE, flags)

__tty_alloc_driver()用於分配一個 tty 驅動程式的資料結構 struct tty_driver,並對其一些常用欄位進行初始化。

/**
 * __tty_alloc_driver -- allocate tty driver
 * @lines: count of lines this driver can handle at most
 * @owner: module which is repsonsible for this driver
 * @flags: some of TTY_DRIVER_* flags, will be set in driver->flags
 *
 * This should not be called directly, some of the provided macros should be
 * used instead. Use IS_ERR and friends on @retval.
 */
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
		unsigned long flags)
{
	struct tty_driver *driver;
	unsigned int cdevs = 1;
	int err;

	if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))
		return ERR_PTR(-EINVAL);
	
    /*分配一個 struct tty_driver 結構體,並對其中的一些欄位進行初始化,包括 num、owner、flags 等*/
	driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
	if (!driver)
		return ERR_PTR(-ENOMEM);

	kref_init(&driver->kref);
	driver->magic = TTY_DRIVER_MAGIC;
	driver->num = lines;
	driver->owner = owner;
	driver->flags = flags;
    
	/*如果 TTY_DRIVER_DEVPTS_MEM 標誌位沒有被設定,那麼函式會分配 driver->ttys 和 driver->termios,否則不需要分配*/
	if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {
		driver->ttys = kcalloc(lines, sizeof(*driver->ttys),
				GFP_KERNEL);
		driver->termios = kcalloc(lines, sizeof(*driver->termios),
				GFP_KERNEL);
		if (!driver->ttys || !driver->termios) {
			err = -ENOMEM;
			goto err_free_all;
		}
	}
	
    /*如果 TTY_DRIVER_DYNAMIC_ALLOC 標誌位沒有被設定,那麼函式會分配 driver->ports,否則不需要分配*/
	if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		driver->ports = kcalloc(lines, sizeof(*driver->ports),
				GFP_KERNEL);
		if (!driver->ports) {
			err = -ENOMEM;
			goto err_free_all;
		}
		cdevs = lines;
	}
    
	/*函式會根據 lines 的值分配相應數量的 driver->cdevs*/
	driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
	if (!driver->cdevs) {
		err = -ENOMEM;
		goto err_free_all;
	}

	return driver;
err_free_all:
	kfree(driver->ports);
	kfree(driver->ttys);
	kfree(driver->termios);
	kfree(driver->cdevs);
	kfree(driver);
	return ERR_PTR(err);
}

tty_register_driver

tty_register_driver用於註冊 tty 驅動程式的,被 tty 驅動程式呼叫以將自己註冊到核心中。

/*
 * Called by a tty driver to register itself.
 */
int tty_register_driver(struct tty_driver *driver)
{
	int error;
	int i;
	dev_t dev;
	struct device *d;
    
	/*確認是否要核心動態分配主裝置號*/
	if (!driver->major) {
        /*函式呼叫 alloc_chrdev_region 函式來動態分配主裝置號,並將分配的主裝置號和次裝置號儲存在 driver->major 和 driver->minor_start 欄位中*/
		error = alloc_chrdev_region(&dev, driver->minor_start,
						driver->num, driver->name);
		if (!error) {
			driver->major = MAJOR(dev);
			driver->minor_start = MINOR(dev);
		}
	} else {
        /*已經預先分配了主裝置號,函式呼叫 register_chrdev_region 函式來註冊裝置號*/
		dev = MKDEV(driver->major, driver->minor_start);
		error = register_chrdev_region(dev, driver->num, driver->name);
	}
	if (error < 0)
		goto err;
	/*判斷是否設定了 TTY_DRIVER_DYNAMIC_ALLOC 標誌位*/
	if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
        /*需要動態分配 tty 裝置號,函式呼叫 tty_cdev_add 函式來新增 tty 裝置號,並將每個 tty 裝置的字元裝置註冊到核心中*/
		error = tty_cdev_add(driver, dev, 0, driver->num);
		if (error)
			goto err_unreg_char;
	}

	mutex_lock(&tty_mutex);
    /*將 driver 新增到連結串列 tty_drivers 中*/
	list_add(&driver->tty_drivers, &tty_drivers);
	mutex_unlock(&tty_mutex);
    
	/*判斷 TTY_DRIVER_DYNAMIC_DEV 標誌位是否設定*/
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
		for (i = 0; i < driver->num; i++) {
            /*需要註冊固定的 tty 裝置號,函式在迴圈中呼叫 tty_register_device 函式來註冊每個 tty 裝置號,並將每個 tty 裝置註冊到核心中*/
			d = tty_register_device(driver, i, NULL);
			if (IS_ERR(d)) {
				error = PTR_ERR(d);
				goto err_unreg_devs;
			}
		}
	}
    /*註冊 /proc/tty/drivers 目錄中的資訊*/
	proc_tty_register_driver(driver);
    /*將 driver 結構體中的 flags 欄位設定為 TTY_DRIVER_INSTALLED,表示該驅動程式已經被成功註冊到核心中*/
	driver->flags |= TTY_DRIVER_INSTALLED;
	return 0;

err_unreg_devs:
	for (i--; i >= 0; i--)
		tty_unregister_device(driver, i);

	mutex_lock(&tty_mutex);
	list_del(&driver->tty_drivers);
	mutex_unlock(&tty_mutex);

err_unreg_char:
	unregister_chrdev_region(dev, driver->num);
err:
	return error;
}

tty_register_driver()函式操作比較簡單。就是為tty_driver建立字元裝置。然後將字元裝置的操作集指定為tty_fops。並且將tty_driver 掛載到tty_drivers連結串列中。這個連結串列中是以裝置號為關鍵字找到對應的driver。

特別的。如果沒有定義TTY_DRIVER_DYNAMIC_DEV。還會在sysfs中建立一個類裝置。這樣主要是為了udev管理裝置。

tty_unregister_device

tty_unregister_device用於登出一個 tty 裝置。該函式的作用是銷燬裝置節點和字元裝置,以便於釋放與該 tty 裝置相關的資源,例如記憶體和裝置檔案等.

/**
 * 	tty_unregister_device - unregister a tty device
 * 	@driver: the tty driver that describes the tty device
 * 	@index: the index in the tty driver for this tty device
 *
 * 	If a tty device is registered with a call to tty_register_device() then
 *	this function must be called when the tty device is gone.
 *
 *	Locking: ??
 */

void tty_unregister_device(struct tty_driver *driver, unsigned index)
{
	device_destroy(tty_class,
		MKDEV(driver->major, driver->minor_start) + index);
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		cdev_del(driver->cdevs[index]);
		driver->cdevs[index] = NULL;
	}
}

tty_unregister_device所做工作如下:

  1. 呼叫 device_destroy 函式來銷燬 tty 裝置對應的裝置節點。接受兩個引數:第一個引數 tty_class 表示 tty 類,第二個引數是 tty 裝置的裝置號,其中 MKDEV(driver->major, driver->minor_start) + index 表示 tty 裝置的裝置號,driver->major 表示 tty 裝置的主裝置號,driver->minor_start 表示 tty 裝置的次裝置號的起始值,index 表示 tty 裝置的索引
  2. 如果該 tty 驅動程式不是動態分配的,則呼叫 cdev_del 函式來登出該 tty 裝置對應的字元裝置。

get_tty_driver

get_tty_driver作用是在使用者空間的應用程式使用 tty 裝置時,獲取對應的 tty 驅動程式的資訊。

/**
 *	get_tty_driver		-	find device of a tty
 *	@dev_t: device identifier
 *	@index: returns the index of the tty
 *
 *	This routine returns a tty driver structure, given a device number
 *	and also passes back the index number.
 *
 *	Locking: caller must hold tty_mutex
 */

static struct tty_driver *get_tty_driver(dev_t device, int *index)
{
	struct tty_driver *p;
	
    /**/
	list_for_each_entry(p, &tty_drivers, tty_drivers) {
		dev_t base = MKDEV(p->major, p->minor_start);
		if (device < base || device >= base + p->num)
			continue;
		*index = device - base;
		return tty_driver_kref_get(p);
	}
	return NULL;
}

首先使用 list_for_each_entry 迴圈遍歷全域性連結串列 tty_drivers,該連結串列中儲存了所有已經註冊的 tty 驅動程式。對於每個 tty 驅動程式,函式將其裝置號的起始值和結束值計算出來,如果給定裝置號不在這個範圍內,則繼續遍歷下一個 tty 驅動程式。

如果給定裝置號在某個 tty 驅動程式的範圍內,則計算出該裝置號對應的 tty 裝置的索引值,並呼叫 tty_driver_kref_get 函式來獲取該 tty 驅動程式的引用計數。函式返回該 tty 驅動程式的結構體指標,並將找到的 tty 裝置的索引值儲存到 index 引數中。

需要注意的是,函式在訪問全域性連結串列 tty_drivers 時,需要持有互斥鎖 tty_mutex。因為多個應用程式可能同時訪問同一個 tty 驅動程式,如果沒有互斥鎖保護,可能會導致併發問題。

tty_open

從註冊的過程可以看到,所有的操作都會對應到tty_fops中。Open操作對應的操作介面是tty_open(),用於開啟一個 tty 裝置。函式的作用是在使用者空間的應用程式使用 tty 裝置時,開啟對應的 tty 裝置,並初始化相應的資料結構。

/**
 *	tty_open		-	open a tty device
 *	@inode: inode of device file
 *	@filp: file pointer to tty
 *
 *	tty_open and tty_release keep up the tty count that contains the
 *	number of opens done on a tty. We cannot use the inode-count, as
 *	different inodes might point to the same tty.
 *
 *	Open-counting is needed for pty masters, as well as for keeping
 *	track of serial lines: DTR is dropped when the last close happens.
 *	(This is not done solely through tty->count, now.  - Ted 1/27/92)
 *
 *	The termios state of a pty is reset on first open so that
 *	settings don't persist across reuse.
 *
 *	Locking: tty_mutex protects tty, tty_lookup_driver and tty_init_dev.
 *		 tty->count should protect the rest.
 *		 ->siglock protects ->signal/->sighand
 *
 *	Note: the tty_unlock/lock cases without a ref are only safe due to
 *	tty_mutex
 */

static int tty_open(struct inode *inode, struct file *filp)
{
	struct tty_struct *tty;
	int noctty, retval;
	struct tty_driver *driver = NULL;
	int index;
	dev_t device = inode->i_rdev;
	unsigned saved_flags = filp->f_flags;

	nonseekable_open(inode, filp);

retry_open:
    /*分配一個 tty 結構體*/
	retval = tty_alloc_file(filp);
	if (retval)
		return -ENOMEM;
	
    /*檢查檔案的標誌位,如果包含 O_NOCTTY 標誌,則禁止將該 tty 裝置設定為控制終端*/
	noctty = filp->f_flags & O_NOCTTY;
	index  = -1;
	retval = 0;
	/*嘗試開啟當前的 tty 裝置*/
	tty = tty_open_current_tty(device, filp);
	if (!tty) {
		mutex_lock(&tty_mutex);
        /*根據裝置號來查詢對應的 tty 驅動程式,並初始化該 tty 裝置,將找到的 tty 驅動程式儲存到 driver 變數中*/
		driver = tty_lookup_driver(device, filp, &noctty, &index);
		if (IS_ERR(driver)) {
			retval = PTR_ERR(driver);
			goto err_unlock;
		}

		/* check whether we're reopening an existing tty */
        /*查詢對應的 tty 裝置,並將找到的 tty 裝置結構體指標儲存到 tty 變數中*/
		tty = tty_driver_lookup_tty(driver, inode, index);
		if (IS_ERR(tty)) {
			retval = PTR_ERR(tty);
			goto err_unlock;
		}

		if (tty) {
            /*如果找到了該 tty 裝置,則需要重新開啟該 tty 裝置*/
			mutex_unlock(&tty_mutex);
			retval = tty_lock_interruptible(tty);
			tty_kref_put(tty);  /* drop kref from tty_driver_lookup_tty() */
			if (retval) {
				if (retval == -EINTR)
					retval = -ERESTARTSYS;
				goto err_unref;
			}
			retval = tty_reopen(tty);
			if (retval < 0) {
				tty_unlock(tty);
				tty = ERR_PTR(retval);
			}
		} else { /* Returns with the tty_lock held for now */
            /*需要初始化該 tty 裝置*/
			tty = tty_init_dev(driver, index);
            /*為該 tty 裝置分配一個 tty 結構體,並對其進行初始化*/
			mutex_unlock(&tty_mutex);
		}

		tty_driver_kref_put(driver);
	}

	if (IS_ERR(tty)) {
		retval = PTR_ERR(tty);
		if (retval != -EAGAIN || signal_pending(current))
			goto err_file;
		tty_free_file(filp);
		schedule();
		goto retry_open;
	}
	/*將該 tty 裝置與檔案結構體相關聯*/
	tty_add_file(tty, filp);

	check_tty_count(tty, __func__);
    /*如果該 tty 裝置是一個偽終端主裝置,則需要將 noctty 標誌設定為 1*/
	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
	    tty->driver->subtype == PTY_TYPE_MASTER)
		noctty = 1;

	tty_debug_hangup(tty, "(tty count=%d)\n", tty->count);
	
    /*呼叫 tty 裝置的 open 函式*/
	if (tty->ops->open)
		retval = tty->ops->open(tty, filp);
	else
		retval = -ENODEV;
	filp->f_flags = saved_flags;

	if (retval) {
		tty_debug_hangup(tty, "error %d, releasing...\n", retval);

		tty_unlock(tty); /* need to call tty_release without BTM */
		tty_release(inode, filp);
		if (retval != -ERESTARTSYS)
			return retval;

		if (signal_pending(current))
			return retval;

		schedule();
		/*
		 * Need to reset f_op in case a hangup happened.
		 */
		if (tty_hung_up_p(filp))
			filp->f_op = &tty_fops;
		goto retry_open;
	}
	clear_bit(TTY_HUPPED, &tty->flags);


	read_lock(&tasklist_lock);
	spin_lock_irq(&current->sighand->siglock);
	if (!noctty &&
	    current->signal->leader &&
	    !current->signal->tty &&
	    tty->session == NULL) {
		/*
		 * Don't let a process that only has write access to the tty
		 * obtain the privileges associated with having a tty as
		 * controlling terminal (being able to reopen it with full
		 * access through /dev/tty, being able to perform pushback).
		 * Many distributions set the group of all ttys to "tty" and
		 * grant write-only access to all terminals for setgid tty
		 * binaries, which should not imply full privileges on all ttys.
		 *
		 * This could theoretically break old code that performs open()
		 * on a write-only file descriptor. In that case, it might be
		 * necessary to also permit this if
		 * inode_permission(inode, MAY_READ) == 0.
		 */
		if (filp->f_mode & FMODE_READ)
			__proc_set_tty(tty);
	}
	spin_unlock_irq(&current->sighand->siglock);
	read_unlock(&tasklist_lock);
	tty_unlock(tty);
	return 0;
err_unlock:
	mutex_unlock(&tty_mutex);
err_unref:
	/* after locks to avoid deadlock */
	if (!IS_ERR_OR_NULL(driver))
		tty_driver_kref_put(driver);
err_file:
	tty_free_file(filp);
	return retval;
}

函式所作工作如下:

  1. 在開啟 tty 裝置時,該函式會檢查檔案的標誌位,如果包含 O_NOCTTY 標誌,則禁止將該 tty 裝置設定為控制終端。這是因為如果一個程式開啟一個 tty 裝置並將其設定為控制終端,其他程式就無法再將該 tty 裝置設定為控制終端,這可能會導致一些問題。

  2. 如果開啟當前的 tty 裝置失敗,則需要根據裝置號來查詢對應的 tty 驅動程式,並初始化該 tty 裝置。在查詢 tty 驅動程式時,需要呼叫 tty_lookup_driver 函式來查詢對應的 tty 驅動程式,並將找到的 tty 驅動程式儲存到 driver 變數中。如果找不到對應的 tty 驅動程式,則返回錯誤碼。

  3. 如果找到了對應的 tty 驅動程式,則呼叫 tty_driver_lookup_tty 函式來查詢對應的 tty 裝置,並將找到的 tty 裝置結構體指標儲存到 tty 變數中。如果找到了該 tty 裝置,則需要重新開啟該 tty 裝置。否則,需要初始化該 tty 裝置。在初始化 tty 裝置時,需要呼叫 tty_init_dev 函式來為該 tty 裝置分配一個 tty 結構體,並對其進行初始化。

  4. 在開啟 tty 裝置之後,函式會呼叫 tty_add_file 函式將該 tty 裝置與檔案結構體相關聯。此外,如果該 tty 裝置是一個偽終端主裝置,則需要將 noctty 標誌設定為 1。

  5. 最後,函式會呼叫 tty 裝置的 open 函式,如果存在的話,來進行一些特定的操作。如果 open 函式返回錯誤碼,則需要釋放該 tty 裝置並返回錯誤碼。如果 open 函式返回 -ERESTARTSYS,則需要重新開啟該 tty 裝置。如果有中斷髮生,也需要重新開啟該 tty 裝置。

tty_write

tty_write()作用是將使用者資料寫入 tty 裝置,並透過線路規則(line discipline)進行處理。

線路規則是 tty 裝置的一種機制,用於處理和轉換從使用者程式到核心和裝置的資料流。在寫入 tty 裝置之前,需要獲取該 tty 裝置的線路規則,並呼叫其 write 方法進行處理。

/**
 *	tty_write		-	write method for tty device file
 *	@file: tty file pointer
 *	@buf: user data to write
 *	@count: bytes to write
 *	@ppos: unused
 *
 *	Write data to a tty device via the line discipline.
 *
 *	Locking:
 *		Locks the line discipline as required
 *		Writes to the tty driver are serialized by the atomic_write_lock
 *	and are then processed in chunks to the device. The line discipline
 *	write method will not be invoked in parallel for each device.
 */

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);
 	struct tty_ldisc *ld;
	ssize_t ret;

	if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
		return -EIO;
	if (!tty || !tty->ops->write ||
		(test_bit(TTY_IO_ERROR, &tty->flags)))
			return -EIO;
	/* Short term debug to catch buggy drivers */
	if (tty->ops->write_room == NULL)
		printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
			tty->driver->name);
	ld = tty_ldisc_ref_wait(tty);
	if (!ld->ops->write)
		ret = -EIO;
	else
		ret = do_tty_write(ld->ops->write, tty, file, buf, count);
	tty_ldisc_deref(ld);
	return ret;
}

tty_write()所作工作如下:

  1. 首先從檔案指標中獲取 tty_struct 資料結構的指標,表示要寫入的 tty 裝置。
  2. 檢查傳入的 tty_struct 指標是否有效,以及是否有其他程式正在訪問該 tty 裝置。如果出現問題,返回輸入/輸出錯誤碼 -EIO
  3. 檢查 tty_struct 指標是否有效、tty 裝置是否支援寫操作,以及是否已經出現了輸入/輸出錯誤。如果出現問題,返回輸入/輸出錯誤碼 -EIO
  4. 檢查 tty 裝置是否實現了 write_room 方法,如果沒有,則輸出錯誤資訊。
  5. 獲取 tty 裝置的線路規則(line discipline),並等待獲取成功。
  6. 檢查線路規則的 write 方法是否存在,如果不存在,返回輸入/輸出錯誤碼 -EIO。否則,呼叫 do_tty_write 函式,將資料寫入 tty 裝置。
  7. 釋放線路規則引用計數器。
  8. 返回寫入操作的結果,如果寫入成功,則返回寫入的位元組數;否則,返回相應的錯誤碼。

tty_read

/**
 *	tty_read	-	read method for tty device files
 *	@file: pointer to tty file
 *	@buf: user buffer
 *	@count: size of user buffer
 *	@ppos: unused
 *
 *	Perform the read system call function on this terminal device. Checks
 *	for hung up devices before calling the line discipline method.
 *
 *	Locking:
 *		Locks the line discipline internally while needed. Multiple
 *	read calls may be outstanding in parallel.
 */

static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
			loff_t *ppos)
{
	int i;
	struct inode *inode = file_inode(file);
	struct tty_struct *tty = file_tty(file);
	struct tty_ldisc *ld;

	if (tty_paranoia_check(tty, inode, "tty_read"))
		return -EIO;
	if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
		return -EIO;

	/* We want to wait for the line discipline to sort out in this
	   situation */
	ld = tty_ldisc_ref_wait(tty);
	if (ld->ops->read)
		i = ld->ops->read(tty, file, buf, count);
	else
		i = -EIO;
	tty_ldisc_deref(ld);

	if (i > 0)
		tty_update_time(&inode->i_atime);

	return i;
}

tty_read()實現終端裝置檔案讀操作的函式 。

  1. 獲取 tty_struct 結構體、inodeline discipline 物件的指標。
  2. 呼叫 tty_paranoia_check() 函式檢查 tty_struct 結構體是否可用。如果檢查失敗,返回 -EIO。
  3. 檢查 tty_struct 結構體是否為空或者 TTY_IO_ERROR 標誌位已經設定。如果是,則返回 -EIO。
  4. 獲取 line discipline 物件的引用,確保它不會在 tty_read() 函式執行期間被解除安裝。
  5. 檢查 line disciplineread() 方法是否可用。如果可用,則呼叫該方法進行讀取操作,並將返回的位元組數儲存在變數 i 中。如果不可用,返回 -EIO。
  6. 釋放 line discipline 的引用。
  7. 如果讀取操作成功,呼叫 tty_update_time() 函式更新 inode 的訪問時間。
  8. 返回讀取的位元組數。

小結

在這一節裡,只對tty的構造做一個分析,具體的比如線路規程的內容我們瞭解知道就好,這裡不做深入分析。

本文參考

https://blog.csdn.net/pan0755/article/details/51693178

https://blog.csdn.net/qq_43286311/article/details/117824804

https://www.jianshu.com/p/09e87a725ed4

https://blog.csdn.net/weixin_40407893/article/details/117956968

https://blog.csdn.net/pan0755/article/details/51693178