https://www.bilibili.com/video/BV1kk4y117Tu

4【第四章】文件I/O

4_6_综合实验_处理表格

  • hexdump -C main.c 可以用来查看二进制数据,并且在右侧显示相应的文本对照。

4_7_文件IO系统调用内部机制

  • /proc/pid/fd 对应进程打开的 fd,默认有 0,1,2 三个,对应 stdin, stdout, stderr.

内部调用

  • fs/open.c SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) -> do_sys_open(AT_FDCWD, filename, flags, mode)
  • do_sys_open(AT_FDCWD, filename, flags, mode) -> fd = get_unused_fd_flags(flags) 获取未使用的文件描述符, struct file *f = do_filp_open(dfd, tmp, &op) 打开文件结构体 , fd_install(fd, f) 把文件结构体放入文件描述符数组中。
  • fs/namei.c struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op)
  • fs/file.c int get_unused_fd_flags(unsigned flags) -> __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags) -> fdt = files_fdtable(files) -> fd = find_next_fd(fdt, fd), files->next_fd = fd + 1
  • fs/file.c void fd_install(unsigned int fd, struct file *file) -> __fd_install(current->files, fd, file) -> fdt = rcu_dereference_sched(files->fdt), rcu_assign_pointer(fdt->fd[fd], file)
  • current 来源于 include/asm-generic #define current get_current() -> #define get_current() (current_thread_info()->task) -> thread_info.h 里面的 struct thread_info -> sched.h struct task_struct *task -> fdtable.h struct files_struct *files -> struct fdtable fdtab

4_8_dup函数的使用

  • int dup(int old); , 返回一个指向 old 的新的最小未使用 fd.
  • int dup2(int old,int new); , 先检查 new 有没有被打开,如果打开,就先关闭。 然后让 new 指向 old.

第7章 输入系统应用编程

7.2 现场编程获取输入设备信息

  • drivers\input\evdev.c, static long evdev_do_ioctl(struct file *file, unsigned int cmd, void __user *p, int compat_mode) 内核中 ioctl 对应的函数
  • evdev_do_ioctl 中 switch 判断分支 EVIOCGVERSIONinclude\uapi\linux\input.h
    
    #define EVIOCGVERSION       _IOR('E', 0x01, int)            /* get driver version */
    #define EVIOCGID        _IOR('E', 0x02, struct input_id)    /* get device ID */
    #define EVIOCGREP       _IOR('E', 0x03, unsigned int[2])    /* get repeat settings */
    #define EVIOCSREP       _IOW('E', 0x03, unsigned int[2])    /* set repeat settings */

define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) / get event bits /

+ 获取设备 id 代码
struct input_id id;
err = ioctl(fd, EVIOCGID, &id);
if (err == 0)
{
    printf("bustype = 0x%x\n", id.bustype );
    printf("vendor  = 0x%x\n", id.vendor  );
    printf("product = 0x%x\n", id.product );
    printf("version = 0x%x\n", id.version );
}
+ 获取设备 evbits 代码, `EVIOCGBIT(ev,len)` 中 ev 为 0 表示获取 evbit, 为 1 表示获取 keybit,按照 `include\linux\input.h` 里面的 `struct input_dev` 中的偏移来算。
unsigned int evbit[2];

len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
if (len > 0 && len <= sizeof(evbit))
{
    printf("support ev type: ");
    for (i = 0; i < len; i++)
    {
        byte = ((unsigned char *)evbit)[i];
        for (bit = 0; bit < 8; bit++)
        {
            if (byte & (1<<bit)) {
                printf("%s ", ev_names[i*8 + bit]);
            }
        }
    }
    printf("\n");
}
## 7_3 查询 休眠唤醒 读取输入数据
+ read 对应的驱动中的函数在 `drivers\input\evdev.c` `static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)`, 通过 `input_event_to_user(buffer + read, &event)` 给用户空间传数据,`struct input_event event`.
+ 应用中读取数据的代码:
while (1)
{
    len = read(fd, &event, sizeof(event));
    if (len == sizeof(event))
    {
        printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
    }
    else
    {
        printf("read err %d\n", len);
    }
}
## 7_6 电阻屏和电容屏
+ 上报数据中的 `ABS_MT_TOUCH_MAJOR` 和 `ABS_MT_WIDTH_MAJOR` 指的是接触区域的椭圆尺寸,正常没啥用。
## 7_7 tslib框架分析
** tslib,暂时不太需要,以后需要的时候,再回头看。**

# 第8章 网络通信 
## 8_2 tcp 编程
+ `tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */` , 自己给出来的数据,需要进行字节序转换。
+ `iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));` 把方便的 `struct sockaddr_in` 转为不方便的 `struct sockaddr`
+ `iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);`
+ 服务端接收数据
    if (-1 != iSocketClient)
    {
        iClientNum++;
        printf("Get connect from client %d : %s\n",  iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
        if (!fork())
        {
            /* 子进程的源码 */
            while (1)
            {
                /* 接收客户端发来的数据并显示出来 */
                iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
                if (iRecvLen <= 0)
                {
                    close(iSocketClient);
                    return -1;
                }
                else
                {
                    ucRecvBuf[iRecvLen] = '\0';
                    printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
                }
            }               
        }
    }
+ `signal(SIGCHLD,SIG_IGN);` 显式的忽略子进程退出信号,防止 `fork` 子进程退出后变为僵尸进程。 当然也可以根据需要设置一个信号处理函数。
+ `iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));`
+ 客户端发送数据
if (-1 == iRet)
{
    printf("connect error!\n");
    return -1;
}

while (1)
{
    if (fgets(ucSendBuf, 999, stdin))
    {
        iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
        if (iSendLen <= 0)
        {
            close(iSocketClient);
            return -1;
        }
    }
}
## 8_3 udp 编程
+ 服务端接收数据
while (1)
{
    iAddrLen = sizeof(struct sockaddr);
    iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
    if (iRecvLen > 0)
    {
        ucRecvBuf[iRecvLen] = '\0';
        printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
    }
}
+ 客户端发送数据
while (1)
{
    if (fgets(ucSendBuf, 999, stdin))
    {
        iAddrLen = sizeof(struct sockaddr);
        iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
                              (const struct sockaddr *)&tSocketServerAddr, iAddrLen);
        if (iSendLen <= 0)
        {
            close(iSocketClient);
            return -1;
        }
    }
}


# 串口
## 10_3 tty 体系设备节点
+ tty 终端,ubuntu 直接切换的就是 tty, 
+ `tty0` 特指前台终端,如果说当前切换到了 `tty3`,那么 `tty0` 也是指向 `tty3`.
+ 可以使用 `echo hello > /dev/tty3` 来向 `tty3` 发消息,也可以用 `echo hello > /dev/tty0` 向前台发消息。
+ `while [ 1 ]; do echo msg_to_front > /dev/tty0; sleep 3; done` 来间隔3秒向前台发信息。就算切换 tty,也可以自动在当前前台显示出来。
+ `/dev/tty` 不含数字的 tty,表示当前 shell 所对应的终端,如果当前是 tty3, 那么 访问 `/dev/tty` 就是访问 `/dev/tty3`.
+ 串口对应的 tty,一般是 `ttyS0` 或者 `ttymxc0` 等等。
+ `console` 表示控制台,比 tty 权限大。 正常情况下,一般是选择某个 tty 作为 console.
+ 内核启动时,可以用 `cmdline` 来指定,比如 `console=tty1`,  如果有多个值的时候,以最后一个为准。 **注意: `console=tty0` 和 `console=tty` 都表示使用前台作为 console.
+ 可以通过 `cat /proc/cmdline` 来获取 `cmdline` 的参数设置。 如果 `cmdline` 中没有指定 console,那么默认就是第一个 `tty`,根据驱动安装的先后,可能是第一个串口,也有可能是第一个虚拟终端。
+ 在 reboot 之后,可以按住 `esc` 进入系统选择界面,选择 advance 选项,把 linux 这一行中的 quiet 去掉,然后增加 `console=tty0` , 然后退出选择第一个进入系统。 系统启动过程中 console 是等于前台,等到系统启动完成,才会按照指定的的 console 来对应设备。
+ console 也是可以用 `echo hello > /dev/console` 来打印信息。

## 10_4 tty 驱动程序框架
+ [The TTY demystified](http://www.linusakesson.net/programming/tty/), [解密TTY](https://www.cnblogs.com/liqiuhao/p/9031803.html)
+ 行编辑。大多数用户都会在输入时犯错,所以退格键会很有用。这当然可以由应用程序本身来实现,但是根据UNIX设计“哲学”,应用程序应尽可能保持简单。为了方便起见,操作系统提供了一个编辑缓冲区和一些基本的编辑命令(退格,清除单个单词,清除行,重新打印),这些命令在行规范(line discipline)内默认启用。高级应用程序可以通过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模式(cooked and canonical)来禁用这些功能。大多数交互程序(编辑器,邮件客户端,shell,及所有依赖curses或readline的程序)均以原始模式运行,并自行处理所有的行编辑命令。行规范还包含字符回显和回车换行(译者注:\r\n 和 \n)间自动转换的选项。如果你喜欢,可以把它看作是一个原始的内核级sed(1)

发表评论