UART驱动学习三(TTY驱动部分源码解析)

news2024/10/11 6:36:30

目录

  • 全局框架图
  • 一、tty_io.c 分析
    • 1. 关键数据结构和定义
    • 2. 文件操作结构体
    • 3. 初始化和注册
    • 4. 读写操作
    • 5. 挂起和恢复
    • 6. 信号处理
    • 7. 设备类
    • 8. 控制台通知
    • 9. 辅助函数
    • 10. 代码功能
    • 11. 带有注释的部分tty_io.c源码
  • 二、tty_ldisc.c 分析
    • 1. 关键数据结构和定义
    • 2. 行规程操作函数
    • 3. 引用计数和模块加载
    • 4. 序列化和锁
    • 5. 行规程引用和解锁
    • 6. 行规程锁定和解锁
    • 7. 行规程挂起和恢复
    • 8. 行规程设置和释放
    • 9. 行规程初始化和清理
    • 10. 系统控制(sysctl)
    • 11. 行规程操作的辅助函数
    • 12. 行规程队列处理
    • 13. 行规程配置
    • 14. 代码结构
  • 三、n_tty.c 分析
    • 1. 关键数据结构
    • 2. 行规程操作函数
    • 3. 辅助函数
    • 4. 行规程初始化和注册
    • 5. 行规程继承
    • 6. 行规程锁定和解锁
    • 7. 行规程数据处理
    • 8. 行规程状态检查
    • 9. 行规程辅助宏
  • 四、serial_core.c
    • 1. 主要数据结构
    • 2. 关键函数和操作
    • 3. 辅助函数
    • 4. 行规程(Line Discipline)相关函数
    • 5. 注册和注销驱动
    • 6. 添加和移除端口
    • 7. 控制台支持
    • 8. 电源管理
    • 9. 串行控制台支持
    • 10. 设备树支持
    • 11. 系统控制台序列支持

全局框架图

在这里插入图片描述

一、tty_io.c 分析

1. 关键数据结构和定义

  • struct tty_struct:定义了与TTY设备相关的所有信

  • struct ktermios tty_std_termios:用于存储终端线的配置信息,如输入输出标志、控制标志等。

2. 文件操作结构体

  • tty_fops:定义了与TTY设备相关的系统调用操作,包括:
    • open:打开TTY设备。
    • release:关闭TTY设备。
    • read_iter:从TTY设备读取数据。
    • write_iter:向TTY设备写入数据。
    • poll:检查TTY设备的状态。
    • unlocked_ioctl:执行与TTY设备相关的控制操作。

3. 初始化和注册

  • tty_init:初始化TTY子系统。
  • tty_register_driver:注册TTY设备驱动。
  • tty_unregister_driver:注销TTY设备驱动。

4. 读写操作

  • tty_read:实现TTY设备的读取操作。
  • tty_write:实现TTY设备的写入操作。

5. 挂起和恢复

  • stop_tty:挂起TTY设备。
  • start_tty:恢复TTY设备。

6. 信号处理

  • do_tty_hangup:处理TTY设备的挂起事件。

7. 设备类

  • tty_class:定义了Linux设备模型中的一个类,用于管理TTY设备。

8. 控制台通知

  • console_sysfs_notify:通知系统关于控制台设备状态的变化。

9. 辅助函数

  • tty_paranoia_check:在调试模式下检查TTY设备的一致性。
  • tty_ldisc_init:初始化行纪律。
  • tty_ldisc_release:释放行纪律。
  • tty_std_termios:定义了默认的终端线配置。

10. 代码功能

  1. 设备初始化:分配和初始化TTY结构,设置默认值。
  2. 设备注册:在系统设备模型中注册TTY设备。
  3. 文件操作:定义了文件操作函数,处理用户空间的读写请求。
  4. 控制操作:处理ioctl请求,如设置终端类型、获取/设置窗口大小等。
  5. 挂起和恢复:控制TTY设备的挂起和恢复状态。
  6. 信号处理:处理如挂起信号等信号。
  7. 设备类:创建和管理TTY设备类。
  8. 通知:在控制台设备状态变化时通知系统。

11. 带有注释的部分tty_io.c源码

struct ktermios tty_std_termios = {
	/* for the benefit of tty drivers  */
	.c_iflag = ICRNL | IXON,
	.c_oflag = OPOST | ONLCR,
	.c_cflag = B38400 | CS8 | CREAD | HUPCL,
	.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE |
		   IEXTEN,
	.c_cc = INIT_C_CC,
	.c_ispeed = 38400,
	.c_ospeed = 38400,
	/* .c_line = N_TTY, */
};

/**
 * 定义串行终端设备的文件操作结构体
 * 该结构体包含了对串行终端设备进行各种操作的函数指针,包括读、写、poll、ioctl等操作
 */
static const struct file_operations tty_fops = {
	.llseek = no_llseek,  // 禁用llseek操作,串行设备不支持随机访问
	.read_iter = tty_read,  // 读操作函数指针
	.write_iter = tty_write,  // 写操作函数指针
	.splice_read = generic_file_splice_read,  // 链接读操作函数指针
	.splice_write = iter_file_splice_write,  // 链接写操作函数指针
	.poll = tty_poll,  // poll操作函数指针,用于等待设备变为可读/可写
	.unlocked_ioctl = tty_ioctl,  // ioctl操作函数指针,用于设备控制
	.compat_ioctl = tty_compat_ioctl,  // 兼容ioctl操作函数指针,用于处理32位和64位进程的ioctl兼容性问题
	.open = tty_open,  // 打开设备的函数指针
	.release = tty_release,  // 关闭设备的函数指针
	.fasync = tty_fasync,  // 异步读/写控制函数指针
	.show_fdinfo = tty_show_fdinfo,  // 显示文件描述符信息的函数指针
};

/**
 * 定义控制台设备的文件操作结构体
 * 该结构体基于串行终端设备的文件操作,但重写了写操作和某些其他函数指针,以适应控制台设备的特殊需求
 */
static const struct file_operations console_fops = {
	.llseek = no_llseek,  // 禁用llseek操作,控制台设备同样不支持随机访问
	.read_iter = tty_read,  // 读操作函数指针,与串行终端设备相同
	.write_iter = redirected_tty_write,  // 写操作函数指针,重写以适应控制台设备的特殊写操作需求
	.splice_read = generic_file_splice_read,  // 链接读操作函数指针,与串行终端设备相同
	.splice_write = iter_file_splice_write,  // 链接写操作函数指针,与串行终端设备相同
	.poll = tty_poll,  // poll操作函数指针,与串行终端设备相同
	.unlocked_ioctl = tty_ioctl,  // ioctl操作函数指针,与串行终端设备相同
	.compat_ioctl = tty_compat_ioctl,  // 兼容ioctl操作函数指针,与串行终端设备相同
	.open = tty_open,  // 打开设备的函数指针,与串行终端设备相同
	.release = tty_release,  // 关闭设备的函数指针,与串行终端设备相同
	.fasync = tty_fasync,  // 异步读/写控制函数指针,与串行终端设备相同
};
static const struct file_operations hung_up_tty_fops = {
	.llseek = no_llseek,
	.read_iter = hung_up_tty_read,
	.write_iter = hung_up_tty_write,
	.poll = hung_up_tty_poll,
	.unlocked_ioctl = hung_up_tty_ioctl,
	.compat_ioctl = hung_up_tty_compat_ioctl,
	.release = tty_release,
	.fasync = hung_up_tty_fasync,
};


/**
 * tty_hangup - 触发挂起事件
 * @tty: 要挂起的tty设备
 *
 * 当这个线上发生载波丢失(实际或虚拟的)时,调度一个挂起序列在该事件后运行。
 * 触发挂起事件是为了处理连接的终止,确保所有相关的资源被正确释放。
 */
void tty_hangup(struct tty_struct *tty)
{
    // 调试信息,表明挂起事件的发生
    tty_debug_hangup(tty, "hangup\n");
    // 调度挂起工作,开始挂起序列
    schedule_work(&tty->hangup_work);
}

// 导出该符号,使得其他模块可以调用这个函数
EXPORT_SYMBOL(tty_hangup);

/*
 * 实现“安全注意键”(Secure Attention Key)功能,当用户按下安全注意键时,
 * 杀死与此终端(tty)关联的所有进程。设计用于超级偏执的应用程序,
 * 详见橙皮书(Orange Book)以获取更多详情。 
 *
 * 该代码可以更优雅;理想情况下,应先发送一个HUP信号,等待几秒钟,
 * 然后发送一个INT信号,最后发送KILL信号。但这样需要与init进程协调,
 * 确保在新的getty被允许生成之前,与当前tty关联的所有进程都已终止。
 *
 * 当前代码存在一个严重的漏洞 - 它没有捕获“飞行中的文件”(files in flight)。
 * 我们可能会通过AF_UNIX套接字发送描述符,关闭它,并稍后从套接字中获取。
 * 这需要修复(FIXME)。
 *
 * 一个严重的bug:do_SAK在中断上下文中被调用。这可能导致死锁。
 * 我们将其转移到进程上下文中处理。 AKPM - 2001年3月16日
 */
void __do_SAK(struct tty_struct *tty)
{
#ifdef TTY_SOFT_SAK
	// 当启用软件SAK时,直接挂起终端
	tty_hangup(tty);
#else
	// 获取与该tty关联的会话ID
	struct task_struct *g, *p;
	struct pid *session;
	int i;
	unsigned long flags;

	// 如果tty为空,则直接返回
	if (!tty)
		return;

	// 以原子方式获取会话ID
	spin_lock_irqsave(&tty->ctrl_lock, flags);
	session = get_pid(tty->session);
	spin_unlock_irqrestore(&tty->ctrl_lock, flags);

	// 清空终端的输入输出缓冲区
	tty_ldisc_flush(tty);
	tty_driver_flush_buffer(tty);

	// 加锁以保护任务列表
	read_lock(&tasklist_lock);
	// 杀死整个会话中的所有进程
	do_each_pid_task(session, PIDTYPE_SID, p)
	{
		tty_notice(tty, "SAK: killed process %d (%s): by session\n",
			   task_pid_nr(p), p->comm);
		group_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, PIDTYPE_SID);
	}
	while_each_pid_task(session, PIDTYPE_SID, p);

	// 杀死任何打开了该tty的进程
	do_each_thread(g, p)
	{
		if (p->signal->tty == tty) {
			tty_notice(
				tty,
				"SAK: killed process %d (%s): by controlling tty\n",
				task_pid_nr(p), p->comm);
			group_send_sig_info(SIGKILL, SEND_SIG_PRIV, p,
					    PIDTYPE_SID);
			continue;
		}
		task_lock(p);
		i = iterate_fd(p->files, 0, this_tty, tty);
		if (i != 0) {
			tty_notice(tty,
				   "SAK: killed process %d (%s): by fd#%d\n",
				   task_pid_nr(p), p->comm, i - 1);
			group_send_sig_info(SIGKILL, SEND_SIG_PRIV, p,
					    PIDTYPE_SID);
		}
		task_unlock(p);
	}
	while_each_thread(g, p);
	// 解锁任务列表
	read_unlock(&tasklist_lock);
	// 释放会话ID的引用
	put_pid(session);
#endif
}

/*
 * 这里的 tty 队列处理(tq)稍微有些竞态条件问题 —— tty->SAK_work 可能已经被排队。
 * 幸运的是,我们不需要担心这个问题,因为如果 ->SAK_work 已经被排队,
 * 我们写入它的值将与它已经拥有的值相同。这确保了数据处理的一致性和正确性。--akpm
 */
/**
 * 触发指定串行终端结构的信号异步键盘(SAK)处理。
 *
 * @param tty 指向串行终端结构的指针,不能为空。
 *
 * 此函数调度与 tty 结构关联的 SAK_work 以执行。如果输入参数 tty 为空指针,
 * 函数不做任何处理并立即返回。这是为了避免在 tty 结构指针无效时进行不必要的处理或潜在错误。
 */
void do_SAK(struct tty_struct *tty)
{
	// 检查 tty 指针是否为空,如果为空,则不做任何处理并立即返回。
	if (!tty)
		return;

	// 调度与 tty 结构关联的 SAK_work 以执行。
	schedule_work(&tty->SAK_work);
}
// 导出 do_SAK 函数符号,允许其他模块或组件使用此函数。
EXPORT_SYMBOL(do_SAK);

/**
 * tty_put_char - 向终端输出一个字符
 * @tty: 终端结构指针
 * @ch: 待输出的字符
 *
 * 该函数通过终端的操作接口向终端设备输出一个字节。如果提供了 put_char 方法,则使用该方法;
 * 否则,使用 write 方法。返回成功输出的字符数。
 *
 * 注意:驱动层中的特定 put_char 操作可能很快会被废弃。不要直接调用它,使用此函数。
 */
int tty_put_char(struct tty_struct *tty, unsigned char ch)
{
	if (tty->ops->put_char)
		return tty->ops->put_char(tty, ch);
	return tty->ops->write(tty, &ch, 1);
}
EXPORT_SYMBOL_GPL(tty_put_char);

/**
 * tty_register_device_attr - 注册一个tty设备
 * @driver: 描述tty设备的tty驱动程序
 * @index: 该tty设备在tty驱动程序中的索引
 * @device: 与此tty设备关联的struct device,如果不存在,则可以为NULL
 * @drvdata: 要设置给设备的驱动数据
 * @attr_grp: 要在设备上设置的属性组
 *
 * 返回此tty设备的struct device指针,如果出错则返回ERR_PTR(-EFOO)
 *
 * 如果tty驱动程序的标志设置了TTY_DRIVER_DYNAMIC_DEV位,则需要调用此函数来注册单个tty设备。
 * 如果没有设置该位,tty驱动程序不应调用此函数。
 *
 * 锁定:??
 */
struct device *tty_register_device_attr(struct tty_driver *driver,
					unsigned index, struct device *device,
					void *drvdata,
					const struct attribute_group **attr_grp)
{
	char name[64];
	dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
	struct ktermios *tp;
	struct device *dev;
	int retval;

	// 检查索引有效性
	if (index >= driver->num) {
		pr_err("%s: Attempt to register invalid tty line number (%d)\n",
		       driver->name, index);
		return ERR_PTR(-EINVAL);
	}

	// 生成设备名称
	if (driver->type == TTY_DRIVER_TYPE_PTY)
		pty_line_name(driver, index, name);
	else
		tty_line_name(driver, index, name);

	// 分配并设置设备结构
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return ERR_PTR(-ENOMEM);

	dev->devt = devt;
	dev->class = tty_class;
	dev->parent = device;
	dev->release = tty_device_create_release;
	dev_set_name(dev, "%s", name);
	dev->groups = attr_grp;
	dev_set_drvdata(dev, drvdata);

	// 抑制uevent事件
	dev_set_uevent_suppress(dev, 1);

	// 注册设备
	retval = device_register(dev);
	if (retval)
		goto err_put;

	// 如果不是动态分配,则处理termios数据和字符设备注册
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
		tp = driver->termios[index];
		if (tp) {
			driver->termios[index] = NULL;
			kfree(tp);
		}

		retval = tty_cdev_add(driver, devt, index, 1);
		if (retval)
			goto err_del;
	}

	// 启用uevent事件
	dev_set_uevent_suppress(dev, 0);
	kobject_uevent(&dev->kobj, KOBJ_ADD);

	return dev;

err_del:
	device_del(dev);
err_put:
	put_device(dev);

	return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(tty_register_device_attr);

/**
 * tty_unregister_device - 取消注册一个tty设备
 * @driver: 描述tty设备的tty驱动程序
 * @index: 该tty设备在tty驱动程序中的索引
 *
 * 如果tty设备已通过tty_register_device()注册,则在设备移除时必须调用此函数。
 *
 * 锁定:??
 */
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;
	}
}
EXPORT_SYMBOL(tty_unregister_device);

/**
 * tty_driver_kref_put - 释放tty驱动程序的引用计数
 * @driver: 指向tty驱动程序的指针
 *
 * 此函数通过kref(空闲对象引用计数)机制来管理tty驱动程序的引用计数。
 * 当引用计数降到零时,调用destructor函数来释放相关资源。
 */
void tty_driver_kref_put(struct tty_driver *driver)
{
	kref_put(&driver->kref, destruct_tty_driver);
}
EXPORT_SYMBOL(tty_driver_kref_put);

/**
 * tty_set_operations - 设置tty驱动程序的操作集合
 * @driver: 指向tty驱动程序的指针
 * @op: 指向包含tty操作的结构的指针
 *
 * 此函数用于将一组操作函数关联到tty驱动程序中,这些操作函数可以
 * 包括打开、关闭、读取、写入等设备控制操作。
 */
void tty_set_operations(struct tty_driver *driver,
			const struct tty_operations *op)
{
	driver->ops = op;
};
EXPORT_SYMBOL(tty_set_operations);

/**
 * put_tty_driver - 释放tty驱动程序的引用
 * @d: 指向tty驱动程序的指针
 *
 * 这是一个方便函数,调用tty_driver_kref_put来释放tty驱动程序的引用。
 * 当不再需要使用tty驱动程序时,可以通过这个函数来释放其资源。
 */
void put_tty_driver(struct tty_driver *d)
{
	tty_driver_kref_put(d);
}
EXPORT_SYMBOL(put_tty_driver);

/*
 * 该函数用于TTY驱动程序注册自身。
 *
 * @param driver: 要注册的TTY驱动结构体指针
 * @return: 注册成功返回0,失败返回负数错误码
 */

int tty_register_driver(struct tty_driver *driver)
{
	// 错误处理码
	int error;
	// 用于循环迭代
	int i;
	// 设备标识符
	dev_t dev;
	// 设备结构体指针
	struct device *d;

	// 如果未指定主设备号,尝试动态分配设备号范围
	if (!driver->major) {
		// 尝试分配字符设备号范围
		error = alloc_chrdev_region(&dev, driver->minor_start,
					    driver->num, driver->name);
		if (!error) {
			// 分配成功,更新主设备号和次设备号起始值
			driver->major = MAJOR(dev);
			driver->minor_start = MINOR(dev);
		}
	} else {
		// 如果指定了主设备号,直接注册指定的设备号
		dev = MKDEV(driver->major, driver->minor_start);
		error = register_chrdev_region(dev, driver->num, driver->name);
	}
	if (error < 0)
		goto err;

	// 对于动态分配设备号的情况,将字符设备添加到系统中
	if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
		error = tty_cdev_add(driver, dev, 0, driver->num);
		if (error)
			goto err_unreg_char;
	}

	// 锁定互斥锁以确保线程安全地操作TTY驱动列表
	mutex_lock(&tty_mutex);
	// 将驱动添加到TTY驱动列表中
	list_add(&driver->tty_drivers, &tty_drivers);
	mutex_unlock(&tty_mutex);

	// 如果使用静态设备号,注册每个设备
	if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
		for (i = 0; i < driver->num; i++) {
			d = tty_register_device(driver, i, NULL);
			if (IS_ERR(d)) {
				error = PTR_ERR(d);
				goto err_unreg_devs;
			}
		}
	}
	// 在/proc文件系统中注册与驱动相关的信息
	proc_tty_register_driver(driver);
	// 标记驱动已安装
	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;
}
// 导出此符号以便系统其他部分调用此函数
EXPORT_SYMBOL(tty_register_driver);

/*
 * 该函数由 tty 驱动调用以注销自身。
 * 此函数主要执行清理工作,包括注销设备号,并从 tty 驱动列表中移除驱动。
 *
 * 参数:
 *   driver: 要注销的 tty 驱动指针。
 *
 * 返回值:
 *   0: 注销成功。
 *   -EBUSY: 驱动仍在使用中,无法注销。
 */
int tty_unregister_driver(struct tty_driver *driver)
{
#if 0
	/* FIXME */
	// 检查驱动是否仍在使用,如果是,则返回错误表示驱动忙。
	if (driver->refcount)
		return -EBUSY;
#endif
	// 注销与 tty 驱动关联的字符设备区域。
	unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
				 driver->num);
	// 锁定互斥锁以确保线程安全地修改 tty 驱动列表。
	mutex_lock(&tty_mutex);
	// 从 tty 驱动列表中移除驱动。
	list_del(&driver->tty_drivers);
	// 解锁互斥锁以释放锁。
	mutex_unlock(&tty_mutex);
	// 返回 0 表示注销成功。
	return 0;
}
EXPORT_SYMBOL(tty_unregister_driver);

/**
 * tty_class_init函数用于在Linux内核中初始化tty类
 * 它创建一个名为"tty"的类对象,并指定其devnode操作函数
 * 
 * @return 返回0表示成功,非0表示出错
 */
static int __init tty_class_init(void)
{
	// 创建一个名为"tty"的类,关联到当前模块
	tty_class = class_create(THIS_MODULE, "tty");
	if (IS_ERR(tty_class))
		return PTR_ERR(tty_class);
	// 指定tty类的devnode操作函数为tty_devnode
	tty_class->devnode = tty_devnode;
	return 0;
}
postcore_initcall(tty_class_init);

/**
 * 向系统通知控制台设备的状态变化
 *
 * 当控制台设备的状态发生改变时,通过sysfs_notify函数通知系统,以便系统可以更新其缓存或其他状态信息
 * 这对于维护系统对控制台设备的准确了解至关重要
 */
void console_sysfs_notify(void)
{
	// 如果consdev指向的设备对象不为空,则通过sysfs_notify函数发送通知
	if (consdev)
		sysfs_notify(&consdev->kobj, NULL, "active");
}

/*
 * 初始化tty设备的函数。
 * 这个函数负责初始化剩余的tty设备,并进行内存分配、中断设置等操作。
 */
int __init tty_init(void)
{
	// 初始化tty系统的控制参数
	tty_sysctl_init();

	// 初始化tty字符设备的cdev和文件操作
	cdev_init(&tty_cdev, &tty_fops);

	// 注册/tty设备
	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");

	// 创建tty设备
	device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

	// 初始化console字符设备的cdev和文件操作
	cdev_init(&console_cdev, &console_fops);

	// 注册/dev/console设备
	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");

	// 创建console设备,如果出错则设consdev为NULL
	consdev = device_create_with_groups(tty_class, NULL,
					    MKDEV(TTYAUX_MAJOR, 1), NULL,
					    cons_dev_groups, "console");
	if (IS_ERR(consdev))
		consdev = NULL;

		// 如果配置了虚拟终端,则初始化vty
#ifdef CONFIG_VT
	vty_init(&console_fops);
#endif

	return 0;
}

二、tty_ldisc.c 分析

1. 关键数据结构和定义

  • tty_ldisc_ops:定义了行规程的操作集,包括打开、关闭、读取、写入等操作。
  • tty_ldisc:表示一个行规程实例,包含指向其操作的指针。

2. 行规程操作函数

  • tty_register_ldisc:注册一个新的行规程。
  • tty_unregister_ldisc:注销一个行规程。
  • get_ldops:获取行规程的操作结构。
  • put_ldops:释放行规程的操作结构。

3. 引用计数和模块加载

  • tty_ldisc_get:获取行规程的引用,并尝试自动加载行规程模块。
  • tty_ldisc_put:释放行规程的引用。

4. 序列化和锁

  • tty_ldiscs_lock:用于保护行规程列表的锁。
  • tty_ldisc_sem:用于控制对行规程的访问。

5. 行规程引用和解锁

  • tty_ldisc_ref_wait:等待行规程的引用。
  • tty_ldisc_ref:获取行规程的引用。
  • tty_ldisc_deref:释放行规程的引用。

6. 行规程锁定和解锁

  • tty_ldisc_lock:锁定行规程。
  • tty_ldisc_unlock:解锁行规程。

7. 行规程挂起和恢复

  • tty_ldisc_hangup:处理行规程的挂起事件。
  • tty_ldisc_restore:恢复行规程。

8. 行规程设置和释放

  • tty_set_ldisc:设置TTY的行规程。
  • tty_ldisc_release:释放TTY的行规程。

9. 行规程初始化和清理

  • tty_ldisc_init:初始化TTY的行规程。
  • tty_ldisc_deinit:清理TTY的行规程。

10. 系统控制(sysctl)

  • tty_sysctl_init:初始化行规程的系统控制表。

11. 行规程操作的辅助函数

  • tty_ldisc_open:打开行规程。
  • tty_ldisc_close:关闭行规程。
  • tty_ldisc_failto:行规程失败回退。
  • tty_ldisc_setup:设置行规程。
  • tty_ldisc_kill:杀死行规程。

12. 行规程队列处理

  • tty_ldisc_flush:刷新行规程的队列。

13. 行规程配置

  • tty_set_termios_ldisc:设置TTY的termios结构体中的行规程字段。

14. 代码结构

  这段代码中涉及的结构关系主要围绕着TTY(Teletype)设备和行规程(line discipline)的管理。

  1. tty_struct:代表一个TTY设备实例。它包含了TTY设备的所有状态信息,包括指向行规程操作的指针。

  2. tty_ldisc:代表一个行规程实例。它包含了指向行规程操作的指针,以及一个指向tty_struct的指针,表示该行规程所属的TTY设备。

  3. tty_ldisc_ops:定义了行规程的操作集。这些操作包括打开、关闭、读取、写入等。每个行规程必须实现这些操作。

  4. tty_driver:代表一个TTY设备驱动。它包含了驱动操作的集合,以及驱动所管理的TTY设备的列表。

  5. tty_ldiscs:一个数组,包含了所有可能的行规程的操作指针。

  6. tty_ldiscs_lock:一个锁,用于保护对tty_ldiscs数组的访问,确保在注册或注销行规程时的线程安全。

  7. tty_ldisc_sem:一个信号量,用于保护对tty_struct中行规程的访问。

以下是这些结构之间的一些关键关系:

  • 每个tty_struct都有一个指向tty_ldisc的指针,表示当前TTY设备的行规程。
  • 每个tty_ldisc都有一个指向tty_ldisc_ops的指针,用于执行行规程的操作。
  • tty_ldiscs数组中的每个元素都对应一个可能的行规程,并且包含了一个指向tty_ldisc_ops的指针。
  • 当注册或注销行规程时,会对tty_ldiscs数组进行操作,并且需要获取tty_ldiscs_lock锁。
  • 当对TTY设备的行规程进行操作时,如打开或关闭,需要获取tty_ldisc_sem信号量。

代码中的函数和操作通常遵循以下模式:

  • 使用tty_ldisc_get函数获取tty_ldisc实例。
  • 使用tty_ldisc_put函数释放tty_ldisc实例。
  • 使用tty_ldisc_ref_wait和tty_ldisc_deref函数来引用和释放行规程。
  • 使用tty_set_ldisc函数设置TTY设备的行规程。
  • 使用tty_ldisc_open和tty_ldisc_close函数来打开和关闭行规程。
  • 使用tty_ldisc_hangup函数来处理行规程的挂起事件。

这些结构和函数共同协作,管理TTY设备的行规程,确保数据的正确传输和处理。

三、n_tty.c 分析

  N_TTY 是 Linux 中的一个术语,指的是 “normal tty”,即标准的终端行规程。以下是代码的关键部分和功能的详细解释:

1. 关键数据结构

  • n_tty_data:定义了 N_TTY 行规程的数据结构,包括缓冲区、读写指针、标志位等。

2. 行规程操作函数

  • n_tty_open:初始化 N_TTY 行规程,为新的 TTY 设备分配资源。
  • n_tty_close:清理 N_TTY 行规程,释放资源。
  • n_tty_read:实现从 TTY 设备读取数据的逻辑。
  • n_tty_write:实现向 TTY 设备写入数据的逻辑。
  • n_tty_ioctl:处理 TTY 设备的控制命令。
  • n_tty_set_termios:处理行规程的终端参数设置。
  • n_tty_poll:实现 TTY 设备的轮询逻辑。
  • n_tty_receive_buf:处理从 TTY 设备接收的数据缓冲区。
  • n_tty_write_wakeup:在可写时唤醒等待的进程。

3. 辅助函数

  • process_output:处理输出字符,包括处理换行、回车、制表符等特殊字符。
  • do_output_char:输出单个字符,处理 OPOST 行规程。
  • __process_echoes:处理回显缓冲区,将回显字符写入 TTY 设备。
  • add_echo_byte:向回显缓冲区添加字符。
  • echo_char:回显字符,处理控制字符的回显。
  • eraser:处理擦除字符的逻辑。
  • isig:处理信号字符的逻辑。

4. 行规程初始化和注册

  • n_tty_init:初始化 N_TTY 行规程,并在系统启动时注册。

5. 行规程继承

  • n_tty_inherit_ops:允许其他行规程继承 N_TTY 的操作函数。

6. 行规程锁定和解锁

  • n_tty_lock:锁定行规程,防止并发访问。
  • n_tty_unlock:解锁行规程。

7. 行规程数据处理

  • n_tty_receive_char:处理接收到的单个字符。
  • n_tty_receive_buf_common:处理接收到的字符缓冲区。

8. 行规程状态检查

  • job_control:检查作业控制状态,决定是否发送信号。

9. 行规程辅助宏

  • MASK:用于处理循环缓冲区的索引。
  • read_cnt:计算缓冲区中字符的数量。

四、serial_core.c

  这段代码是 Linux 内核中串行驱动的核心部分,它提供了UART(通用异步接收/发送器)设备的驱动支持。以下是代码中的主要结构和它们之间的关系:

1. 主要数据结构

  1. uart_state:表示一个UART设备的实例,包含设备的状态和配置信息。

  2. uart_port:表示一个UART端口,包含端口的硬件参数和操作函数。

  3. uart_driver:表示一个UART驱动,包含驱动的名称、端口数量和操作函数。

2. 关键函数和操作

  1. uart_open:打开UART设备时调用的函数。

  2. uart_close:关闭UART设备时调用的函数。

  3. uart_write:向UART设备写数据的函数。

  4. uart_read:从UART设备读取数据的函数。

  5. uart_ioctl:处理UART设备的控制命令。

  6. uart_set_termios:设置UART设备的终端参数。

  7. uart_startup:启动UART设备时调用的函数。

  8. uart_shutdown:关闭UART设备时调用的函数。

  9. uart_hangup:挂断UART设备时调用的函数。

3. 辅助函数

  1. uart_port_startup:启动UART端口时调用的函数。

  2. uart_port_shutdown:关闭UART端口时调用的函数。

  3. uart_change_speed:改变UART设备波特率的函数。

  4. uart_change_pm:改变UART设备的电源管理状态。

  5. uart_write_wakeup:当UART设备准备好写入时调用的函数。

  6. uart_stop:停止UART设备发送数据的函数。

  7. uart_start:开始UART设备发送数据的函数。

  8. uart_update_mctrl:更新UART设备的调制解调器控制信号。

  9. uart_port_dtr_rts:设置UART端口的DTR和RTS信号。

4. 行规程(Line Discipline)相关函数

  1. uart_ldisc_open:打开行规程时调用的函数。

  2. uart_ldisc_close:关闭行规程时调用的函数。

  3. uart_ldisc_setup:设置行规程时调用的函数。

  4. uart_ldisc_release:释放行规程时调用的函数。

5. 注册和注销驱动

  1. uart_register_driver:注册UART驱动到内核。

  2. uart_unregister_driver:注销UART驱动。

6. 添加和移除端口

  1. uart_add_one_port:向驱动添加一个UART端口。

  2. uart_remove_one_port:从驱动移除一个UART端口。

7. 控制台支持

  1. uart_console_device:获取控制台设备的UART驱动。

8. 电源管理

  1. uart_change_pm:改变UART设备的电源管理状态。

9. 串行控制台支持

  1. uart_get_console:获取串行控制台的UART端口。

  2. uart_set_options:设置串行控制台的选项。

10. 设备树支持

  1. uart_get_rs485_mode:从设备树获取RS485模式的配置。

11. 系统控制台序列支持

  1. uart_try_toggle_sysrq:尝试通过UART端口切换系统控制台序列。

  本文章内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!😁😁😁

  未完待续哦…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2195177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始打造华丽的国庆生活记录本地HTML网站

目录 目录 前言 准备工作 所需工具 文件夹结构 基础知识入门 HTML基础 CSS基础 JavaScript基础 步骤详解 1. 创建项目文件夹 2. 编写HTML文件 3. 添加CSS样式 4. 增加交互功能 5. 添加图片和视频 6. 美化网站 7. 实现响应式设计 8. 测试与优化 附加功能 创建…

Windows 搭建 Gitea

一、准备工作 1. 安装 Git&#xff1a;Gitea 依赖 Git 进行代码管理&#xff0c;所以首先需要确保系统中安装了 Git。 下载地址&#xff1a;https://git-scm.com/downloads/win 2. 安装数据库&#xff08;可选&#xff09; 默认情况下&#xff0c;Gitea 使用 SQLite 作为内…

【springboot】使用代码生成器快速开发

接上一项目&#xff0c;使用mybatis-plus-generator实现简易代码文件生成 在fast-demo-web模块中的pom.xml中添加mybatis-plus-generator、freemarker和Lombok依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator&…

Windows 11:如何轻松安装或卸载 Copilot 应用(多种方法)

起初&#xff0c;Copilot 是一个与 Windows 11 和 Windows 10 系统紧密结合的内置 AI 助手&#xff0c;能够通过回答问题、调整系统设置等功能来提高你的工作效率。 但从 Windows 11 24H2 开始&#xff0c;Copilot 功能已经从系统中剥离出来&#xff0c;成了一个基于 Microsoft…

【kubernetes】环境准备及K8S二进制安装【最新最全】

一,规划 1,架构 主节点(3台 master)+ 工作节点 (1台 node1) Pod网段: 10.0.0.0/16 Service网段: 10.255.0.0/16 实验环境规划: 操作系统:centos7.6 配置: 4Gib内存/6vCPU/100G硬盘 注意:也可以用4vCPU 网络:NAT 开启虚拟机的虚拟化: 2,K8S集群角色 Ip 主机名…

【嵌入式实时操作系统开发】智能家居入门4(FreeRTOS、MQTT服务器、MQTT协议、STM32、微信小程序)

前面已经发了智能家居入门的1、2、3了&#xff0c;在实际开发中一般都会使用到实时操作系统&#xff0c;这里就以FreeRTOS为例子&#xff0c;使用标准库。记录由裸机转到实时操作系统所遇到的问题以及总体流程。相较于裸机&#xff0c;系统实时性强了很多&#xff0c;小程序下发…

JavaSE——面向对象9.1:代码块详解

目录 一、静态代码块 二、构造代码块 三、局部代码块 四、总结 一、静态代码块 在《JavaSE——面向对象9&#xff1a;static、final关键字、代码块、单例模式》这篇文章中已经介绍过&#xff0c;静态代码块随着类的加载而加载&#xff0c;且只加载一次&#xff0c;这里不再…

【优选算法】---分治 归并排序

分治 归并排序 一、排序数组 / 归并排序的复习1、题目解析2、算法原理3、代码 二、逆序对的总数1、题目解析2、算法原理3、代码 三、计算右侧小于当前元素的个数1、题目解析2、算法原理3、代码 四、翻转对1、题目解析2、算法原理3、代码 一、排序数组 / 归并排序的复习 归并排…

Windows下Jenkins控制台中文乱码

问题描述 问题情况如下图&#xff1a; 环境信息 Windows 11 家庭中文版java 21.0.4 2024-07-16 LTSJenkins 2.452.3 解决方法 增加系统JAVA_TOOL_OPTIONS&#xff0c;并设置值为-Dfile.encodingGBK。 打开设置方法&#xff1a;桌面上右键点击“此电脑”图标&#xff0c;选…

软考高级之系统架构师之计算机硬件基础

概述 局部性原理是指在指定时间内&#xff0c;程序趋于在有限的内存区域内重复访问。通常将局部性分为空间局部性和时间局部性。空间局部性是指已访问过的内存地址附近的位置很可能被连续访问。时间局部性是指已访问过的内存地址在较短的时间内还可能被多次访问。 计算机执行…

请散户股民看过来,密切关注两件大事

明天股市要开市&#xff0c;不仅散户股民期盼节后股市大涨&#xff0c;上面也同样想在节后来上一个“开门红”。 为此&#xff0c;上面没休假&#xff0c;关起门来办了两件大事&#xff0c;这两天发布消息已提前预热了。 两件大事如下&#xff1a; 一是&#xff0c;上交所10…

柯桥生活口语学习之在化妆品店可以用到的韩语句子

화장품을 사고 싶어요. 我想买化妆品。 어떤 화장품을 원하세요? 您想买什么化妆品。 스킨로션을 찾고 있어요. 我想买化妆水&#xff0c;乳液。 피부 타입은 어떠세요? 您是什么皮肤类型&#xff1f; 민감성 피부예요. 我是敏感性皮肤。 평소에 쓰시는 제품은 뭐예…

【判断推理】逻辑基础

1.1 命题 用语言、符号或者式子表达的&#xff0c;可以判断真假的陈述句称为命题&#xff0c;一般写为 若p&#xff0c;则q 真命题&#xff1a;判断为真的语句假命题&#xff1a;判断为假的语句 eg1&#xff1a;小张是中国人&#xff08;若是小张&#xff0c;则是中国人&#…

【传感器技术】【9 温度测量,热电偶传感器,膨胀式温度传感器,压力测量,弹性式压力表】

上理考研周导师的哔哩哔哩频道 我在频道里讲课哦 目录 9.1、 温度概述 1&#xff0e; 温度与温标 2&#xff0e; 温度测量的主要方法和分类 9.2、 膨胀式温度传感器 1&#xff0e; 液体膨胀式 2&#xff0e; 固体膨胀式 3&#xff0e; 气体膨胀式 9.3、 热电偶传感器 1. …

Cesium的一些神奇概念及技术流程(1)

近期要深度研究Cesium。关于Cesium的用法、渲染流程等方面我看很多人都写过。我就写写其中一些可能平时用不到但是比较有趣的内容。因为边研究边写&#xff0c;所以会陆续出几集&#xff0c;然后合并在一起&#xff0c;欢迎大家跟踪。 我的这些文章不打算把一些基本概念展开解…

OJ在线评测系统 微服务 OpenFeign调整后端上 内部调用Client代码书写 实现客户端接口 配置服务参数 接口文档

OpenFeign内部调用上 我们的代码已经搬运完毕了 但是我们的服务之间是无法相互调用的 我们可以使用OpenFeign进行远程调用 一个http调用客户端 提供了更方便的方式让你远程调用其他的服务 Nacos注册中心获取服务的调用地址 如果没有实现OpenFeign 也能实现跨服务的调用 …

Windows 11 version 24H2 LTSC 2024 中文版、英文版 (x64、ARM64) 下载 (updated Oct 2024)

Windows 11 version 24H2 & LTSC 2024 中文版、英文版 (x64、ARM64) 下载 (updated Oct 2024) Windows 11, version 24H2&#xff0c;企业版 arm64 x64 请访问原文链接&#xff1a;https://sysin.org/blog/windows-11/ 查看最新版。原创作品&#xff0c;转载请保留出处。…

出国必备神器!这5款中英翻译工具让你秒变外语达人

在这个全球化的时代&#xff0c;中英互译已然成为我们日常生活和工作中不可或缺的一环。面对众多的翻译工具&#xff0c;如何选择一款既高效又人性化的翻译助手呢&#xff1f;今天&#xff0c;就让我为大家揭秘几款热门的中英互译工具&#xff0c;并分享我的使用感受。 一、福昕…

【数据结构与算法】线性表

文章目录 一.什么是线性表&#xff1f;二.线性表如何存储&#xff1f;三.线性表的类型 我们知道从应用中抽象出共性的逻辑结构和基本操作就是抽象数据类型&#xff0c;然后实现其存储结构和基本操作。下面我们依然按这个思路来认识线性表 一.什么是线性表&#xff1f; 定义 线性…