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

发表评论