Linux使用C语言获取进程信息

Linux使用C语言获取进程信息

Author: OnceDay Date: 2024年2月22日

漫漫长路,才刚刚开始…

全系列文章可查看专栏: Linux实践记录_Once_day的博客-CSDN博客

参考文档:

  • Linux proc目录详解_/proc/mounts-CSDN博客
  • Linux下/proc目录介绍 - 知乎 (zhihu.com)
  • Linux内核通信之—proc文件系统(详解) - 知乎 (zhihu.com)
  • Linux /proc目录详解 - 滴水瓦 - 博客园 (cnblogs.com)
  • Linux之(16)程序管理-CSDN博客

文章目录

      • Linux使用C语言获取进程信息
        • 1. Linux进程概述
          • 1.1 进程介绍
          • 1.2 介绍/proc目录信息
          • 1.3 常用进程相关命令
        • 2. C语言编码获取进程信息
          • 2.1 读取comm线程名字
          • 2.2 读取cmdline命令行文本
          • 2.3 读取exe可执行程序路径
          • 2.4 如何判断内核进程
          • 2.5 修改comm和cmdline信息
          • 2.6 实现一个简易的killall工具
        • 附录
          • 附录1: opendir和readdir函数简介
          • 附录2: 介绍fread函数
          • 附录3: 介绍fgets函数
          • 附录4: 介绍readlink函数
          • 附录5: 介绍kill函数

1. Linux进程概述
1.1 进程介绍

在计算机的世界里,Linux进程是一个非常基础而且关键的概念。它可以被理解为正在执行的一个程序的实例。每个进程都有自己独特的身份,我们称之为进程ID(PID),就像每个人都有自己的身份证号码一样。Linux操作系统是一种多任务操作系统,可以同时运行多个进程,就像一个杂技团队能同时上演多个节目一样。

现在,想象一下进程是厨房里的一个厨师,而计算机资源(如CPU、内存)则是厨房里的炉子、锅碗瓢盆。每个厨师都需要这些资源来完成他们的烹饪任务。Linux系统就像一个细心的餐厅经理,确保所有厨师轮流合理使用这些资源,并监督他们的工作进度。

首先,进程的创建通常通过fork()系统调用完成。这个调用会创建一个与当前进程几乎完全相同的副本,包括代码、数据和堆栈,但拥有新的进程标识符(PID)。这个新进程可以继续执行,或者通过exec()系列函数来加载新的程序。

进程有不同的状态,包括运行(正在使用CPU)、等待(等待资源可用)、停止(被挂起)和僵尸(已完成执行但等待父进程读取状态的进程)。就像厨师有时在炒菜,有时在等待食材,有时在休息,有时则在等待经理来检查他们做的菜。

Linux提供了很多管理进程的工具,如ps命令可以查看当前的进程,top命令可以实时监控进程的状态,而kill命令可以用来结束某个进程。这就好比厨房里有各种管理工具,可以查看厨师的工作列表,监控他们的工作状态,或者让某个厨师停止当前的工作。

进程间通信(IPC)是Linux系统中进程协作的重要机制。常见的IPC方式有管道(pipe)、命名管道(named pipe)、信号(signal)、共享内存(shared memory)、消息队列(message queue)和套接字(socket)。这些机制允许进程间传递数据和同步操作。

进程的同步主要通过信号量(semaphore)和互斥锁(mutex)来实现。信号量用于控制对共享资源的访问,而互斥锁则确保同一时间只有一个进程可以访问某个资源。

进程的终止可以通过多种方式,如正常退出、被其他进程杀死(通过发送信号)、或系统强制终止。进程结束后,其资源会被回收,但进程的退出状态(exit status)会被保存,这对于调试和错误处理非常重要。

在Linux中,进程管理还涉及到进程的优先级(nice value)和实时调度。优先级决定了进程获取CPU时间片的优先程度,而实时调度则允许进程以更高的优先级运行,通常用于需要快速响应的任务。

最后,Linux系统还支持进程的层级关系,也就是父子进程关系。当一个进程创建一个新进程时,原来的进程就是父进程,新创建的就是子进程。这有点儿像家族企业,父亲开了一家餐馆后,孩子长大也可能在旁边开一家小吃店。

了解Linux进程是掌握操作系统和进行系统编程的基础。虽然这是一门复杂的技术,但它对于运行和管理Linux系统至关重要。不同的进程就像社会中不同的个体,它们相互独立却又相云协作,共同构成了一个运作高效的系统。

1.2 介绍/proc目录信息

在Linux操作系统中,/proc目录是一个非常特殊和有趣的存在,它实际上并不是存储在硬盘上的真实文件系统,而是一个虚拟文件系统,通常被称为进程信息伪文件系统(Process Information Pseudo-File System)。/proc提供了一个窗口,通过它可以窥见内核中的世界,包括系统信息及正在运行的进程详情。

/proc目录中的文件和子目录大都以数字命名,这些数字对应着系统中的进程ID(PID)。每一个这样的目录里面,包含了与该PID相关的信息,例如进程的内存映射(/proc/<pid>/maps)、环境变量(/proc/<pid>/environ)、可执行文件的链接(/proc/<pid>/exe)等等。这些文件为系统管理员和程序员提供了一种简便的方式来监控进程和系统的内部状态。

除了进程相关信息,/proc目录还包含了许多全系统范围的信息。例如,/proc/meminfo文件包含了内存使用情况的详细信息,/proc/cpuinfo提供了CPU的相关信息,/proc/net目录包含了网络协议和配置的信息,等等。

值得一提的是,/proc目录下的文件大多是可读的文本文件,可以直接使用常用的文本工具查看,如catless等。但也有一些文件是可写的,通过对这些文件写入特定的值,用户或程序员可以调整或配置内核参数,这是实现内核动态调优的一种方式。

一个常见的应用场景是,当系统管理员想要快速检查系统的运行状态,或者程序员在开发过程中需要获取系统或进程的某些信息时,他们就会查阅/proc目录下的文件。例如,通过读取/proc/uptime文件,可以获取系统已经运行了多长时间;通过查看/proc/loadavg文件,可以了解系统的平均负载情况。

以下是/proc目录下的一些常见子目录和文件的详细总结表格。请注意,这个表格并不包含所有可能存在的文件和目录,因为/proc目录的内容可能会根据Linux内核的版本和系统配置有所不同

路径描述
/proc/[pid]每个进程都有一个对应的目录,包含该进程的详细信息
/proc/[pid]/attr安全属性,如SELinux上下文
/proc/[pid]/auxvELF解释器传递给进程的信息
/proc/[pid]/cmdline进程启动时的完整命令行
/proc/[pid]/comm进程的命令名称
/proc/[pid]/coredump_filter核心转储文件的过滤设置
/proc/[pid]/cpusetCPU亲和性设置
/proc/[pid]/cwd当前工作目录的符号链接
/proc/[pid]/environ环境变量列表
/proc/[pid]/exe可执行文件的符号链接
/proc/[pid]/fd打开文件的文件描述符
/proc/[pid]/fdinfo文件描述符信息
/proc/[pid]/limits资源限制
/proc/[pid]/maps内存映射信息
/proc/[pid]/mem进程内存页访问
/proc/[pid]/mountinfo挂载信息
/proc/[pid]/mounts当前进程挂载的文件系统列表
/proc/[pid]/mountstats挂载统计信息
/proc/[pid]/ns命名空间信息
/proc/[pid]/numa_mapsNUMA内存映射信息
/proc/[pid]/oom_adjOOM(内存不足)调整值
/proc/[pid]/oom_scoreOOM评分
/proc/[pid]/oom_score_adjOOM评分调整
/proc/[pid]/root根目录的符号链接
/proc/[pid]/smaps内存映射的详细内存使用情况
/proc/[pid]/stat进程状态信息
/proc/[pid]/statm内存使用状态信息
/proc/[pid]/status进程状态信息(可读格式)
/proc/[pid]/task包含进程中每个线程的信息
/proc/[pid]/thread-self当前线程的信息
/proc/self当前进程的信息链接
/proc/self/task当前进程的线程信息
/proc/apm高级电源管理(APM)信息
/proc/buddyinfo内存碎片信息
/proc/cmdline启动时传递给内核的参数
/proc/cpuinfoCPU信息
/proc/crypto加密算法信息
/proc/devices设备列表
/proc/diskstats磁盘I/O统计信息
/proc/dmaISA DMA通道信息
/proc/execdomains执行域信息
/proc/fb帧缓冲设备信息
/proc/filesystems支持的文件系统类型
/proc/interruptsIRQ中断信息
/proc/iomem物理设备内存映射信息
/proc/ioports输入输出端口范围信息
/proc/kallsyms内核符号信息
/proc/kcore物理内存映射(核心文件格式)
/proc/kmsg内核消息记录
/proc/loadavgCPU和I/O负载平均值
/proc/locks内核锁定的文件信息
/proc/mdstatRAID配置信息
/proc/meminfo内存使用信息
/proc/mounts系统挂载信息
/proc/modules加载的内核模块列表
/proc/partitions分区信息
/proc/pciPCI设备信息
/proc/slabinfoSlab缓存信息
/proc/statCPU活动统计信息
/proc/sysrq-trigger系统请求触发器
/proc/swaps交换空间使用情况
/proc/uptime系统运行时间
/proc/version内核版本信息
/proc/bus总线信息
/proc/driver驱动信息
/proc/fs文件系统信息
/proc/ideIDE设备信息
/proc/irq中断请求设备信息
/proc/net网络设备信息
/proc/scsiSCSI设备信息
/proc/ttyTTY设备信息
/proc/net/dev网络适配器及统计信息
/proc/vmstat虚拟内存统计信息
/proc/vmcore内核panic时的内存映像
/proc/diskstats磁盘信息
/proc/schedstat调度器统计信息
/proc/zoneinfo内存空间统计信息

这个表格提供了/proc目录下一些关键文件和目录的简要描述。由于/proc目录的内容非常丰富,这里只列出了一部分。在实际使用中,可以通过ls -l /proc命令来查看当前系统/proc目录下的完整内容。

1.3 常用进程相关命令

可参考文档: Linux之(16)程序管理-CSDN博客

在Linux系统中,有一系列强大的进程相关工具命令,它们是系统管理员和开发者日常工作中不可或缺的助手。以下是一些常用的进程工具及其简要介绍和示例:

  1. ps(Process Status):这是最基本的进程查看工具,用于列出系统中当前运行的进程。例如,命令ps aux会列出系统中所有进程的详细信息,其中a代表所有用户的进程,u代表用户以及其他详细信息,x代表没有控制终端的进程。

    ubuntu->~:$ ps aux
    USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root           1  0.0  0.1 167488 11228 ?        Ss   Jan14   2:04 /sbin/init
    root           2  0.0  0.0      0     0 ?        S    Jan14   0:01 [kthreadd]
    root           3  0.0  0.0      0     0 ?        I<   Jan14   0:00 [rcu_gp]
    ......
    
  2. top:这个命令提供了一个实时更新的进程状态动态视图。它显示了CPU和内存的使用情况,以及进程的动态列表,可以实时查看系统的性能状况。只需在终端输入top即可启动。

    ubuntu->~:$ top
    top - 22:54:50 up 38 days, 23:36,  1 user,  load average: 0.06, 0.05, 0.01
    Tasks: 123 total,   1 running, 122 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  1.6 sy,  0.0 ni, 98.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    MiB Mem :   7437.5 total,   2603.7 free,    400.8 used,   4433.0 buff/cache
    MiB Swap:      0.0 total,      0.0 free,      0.0 used.   6719.4 avail Mem PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                             1 root      20   0  167488  11228   6444 S   0.0   0.1   2:04.38 systemd                             2 root      20   0       0      0      0 S   0.0   0.0   0:01.59 kthreadd                            3 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_gp                              
    ......   
    
  3. htop:相比tophtop提供了一个更为友好和可交互的界面,它允许用户通过键盘操作进行进程管理,如结束进程等。启动方法是在终端输入htop

在这里插入图片描述

  1. kill:这是用于发送信号给进程的命令,通常用于终止进程。例如,kill -9 <pid>会发送SIGKILL信号来强制终止指定PID的进程。

  2. pkillkillall:这两个命令也用于发送信号给进程,但它们可以根据进程名而不是PID来指定进程。例如,pkill nginx将终止所有名为nginx的进程。

  3. pstree:该命令用于以树形结构显示进程的层次关系,这对于理解进程之间的父子关系非常有帮助。只需输入pstree即可显示当前进程树。

    ubuntu->~:$ pstree
    systemd─┬─ModemManager───2*[{ModemManager}]├─YDLive─┬─YDService─┬─sh───10*[{sh}]│        │           └─23*[{YDService}]│        └─10*[{YDLive}]├─acpid├─2*[agetty]├─auditd───{auditd}
    
  4. vmstat:虽然不是直接用于进程管理,但vmstat命令能提供关于系统内存、交换、IO、CPU活动等重要信息,这对于分析进程性能有重要的参考价值。例如,vmstat 1会每秒刷新显示系统状态。

    ubuntu->~:$ vmstat 
    procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st0  0      0 2665612 220868 4320632    0    0    41    25    0    3  0  0 99  0  0
    
  5. lsof:List Open Files的缩写,用于列出被进程打开的文件,对于查找使用了哪些文件或设备非常有用。例如,lsof -p <pid>将显示指定PID进程打开的文件。

  6. strace:这个工具用于跟踪进程执行时的系统调用,对于程序调试和性能分析至关重要。例如,strace -p <pid>将跟踪并显示指定进程的系统调用。

  7. nicerenice:这两个命令用于调整进程的优先级。nice用于启动一个进程时设置其优先级,而renice则用于修改已经运行进程的优先级。例如,renice 10 -p <pid>会将指定PID的进程优先级调整为10。

这些工具是Linux上进行进程查看和管理的基石,通过它们可以实现对系统进程的精细化控制和监控。掌握它们的使用,可以帮助您更好地理解和管理Linux系统的运行状态。

2. C语言编码获取进程信息
2.1 读取comm线程名字

在C语言中,可以通过遍历/proc目录并读取每个进程目录下的comm文件来获取所有进程的名称。以下是一个简单的示例代码,它将列出/proc下所有进程的comm信息:

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>int main(void)
{DIR           *dir;struct dirent *entry;char           path[512];char           comm[256];FILE          *fp;// 打开/proc目录dir = opendir("/proc");if (dir == NULL) {perror("opendir failed");exit(EXIT_FAILURE);}// 遍历目录项while ((entry = readdir(dir)) != NULL) {// 检查目录项名称是否为数字(进程ID)if (entry->d_type == DT_DIR && strtol(entry->d_name, NULL, 10) > 0) {// 构建comm文件的路径snprintf(path, sizeof(path), "/proc/%s/comm", entry->d_name);// 打开comm文件fp = fopen(path, "r");if (fp == NULL) {continue;}// 读取comm文件的内容(进程名称)if (fgets(comm, sizeof(comm), fp) != NULL) {// 移除换行符并打印进程名称comm[strcspn(comm, "\n")] = 0;printf("PID: %s, Comm: %s\n", entry->d_name, comm);}// 关闭comm文件fclose(fp);}}// 关闭/proc目录closedir(dir);return 0;
}

此程序执行以下步骤:

  1. 打开/proc目录。
  2. 遍历目录项,检查目录名称是否为数字,因为进程目录总是以数字命名。
  3. 对于每个进程目录,构建指向其comm文件的路径。
  4. 打开comm文件并读取进程名称。
  5. 打印进程ID和名称。
  6. 关闭已打开的文件和目录。

我们在ubuntu环境下编译和该程序,输出如下(可以读出进程的名字):

ubuntu->cs-test:$ gcc -o read-comm read-comm.c 
ubuntu->cs-test:$ ./read-comm 
PID: 1, Comm: systemd
PID: 2, Comm: kthreadd
PID: 3, Comm: rcu_gp
PID: 4, Comm: rcu_par_gp
PID: 5, Comm: netns
......
2.2 读取cmdline命令行文本

在Linux系统中,/proc目录是一个虚拟的文件系统,它提供了一个接口来访问内核数据结构。它不仅包含系统信息,还包含了每个进程信息的目录,例如/proc/[pid],其中[pid]是进程ID。在每个进程的目录下,有一个名为cmdline的文件,它包含了启动该进程的命令行参数。

要用C语言编码获取指定进程的cmdline信息,可以按照以下步骤编写代码:

  1. 构造/proc/[pid]/cmdline文件的路径。
  2. 打开这个文件。
  3. 读取文件内容。
  4. 关闭文件。

以下是一个例子,演示了如何获取进程ID为1234的cmdline信息:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>int main(int argc, char **argv)
{int    pid, i;FILE  *file;char   path[256], cmdline[PATH_MAX + 1];size_t size;// 读取命令行参数if (argc != 2) {fprintf(stderr, "Usage: %s <pid>\n", argv[0]);return 1;}pid = atoi(argv[1]);// 构造文件路径snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);// 打开文件file = fopen(path, "r");if (file == NULL) {perror("fopen");exit(EXIT_FAILURE);}// 读取文件内容size = fread(cmdline, 1, PATH_MAX, file);if (size == 0) {perror("fread");exit(EXIT_FAILURE);}// 将中间间隔的'\0'变成' 'for (i = 0; i < size; i++) {if (cmdline[i] == '\0') {cmdline[i] = ' ';}}// 添加字符串终止符cmdline[size] = '\0';// 输出cmdline信息printf("The command line for PID %d is: %s\n", pid, cmdline);// 清理资源fclose(file);return 0;
}

这段代码首先定义了进程ID并构建了相应的文件路径。使用fopen打开cmdline文件。之后,使用fread读取文件内容,并在末尾添加空字符以形成一个标准的字符串。最后,输出命令行信息,释放内存,关闭文件。

请注意,cmdline文件的内容使用空字符分隔命令行参数,所以上面代码中进行了转换,使用空格进行输出。下面是实际运行情况:

ubuntu->cs-test:$ gcc -o read-cmdline read-cmdline.c 
ubuntu->cs-test:$ ./read-cmdline 1911678
The command line for PID 1911678 is: sshd: ubuntu@pts/0   
ubuntu->cs-test:$ ./read-cmdline 1949309
The command line for PID 1949309 is: /home/ubuntu/.vscode-server/extensions/ms-vscode.cpptools-1.18.5-linux-x64/bin/cpptools-srv 1949199 {EDAFE4D2-4BEC-4A09-9466-66C5A7850042} 
2.3 读取exe可执行程序路径

为了获取Linux系统中/proc目录下所有进程的可执行程序路径,我们可以编写一个C程序来遍历/proc目录,查找所有的数字命名子目录(这些通常代表进程ID),然后对每个进程读取其exe链接文件,该链接指向进程的可执行文件路径。

在Linux系统中,/proc/[pid]/exe是一个符号链接,指向启动该进程的可执行文件。

以下是实现这一功能的C语言代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <libgen.h>int main()
{DIR           *proc;struct dirent *entry;char           exe_path[PATH_MAX];char           filepath[PATH_MAX];ssize_t        len;// 打开/proc目录if ((proc = opendir("/proc")) == NULL) {perror("opendir");exit(EXIT_FAILURE);}// 遍历/proc目录while ((entry = readdir(proc)) != NULL) {// 检查目录名是否为数字(代表进程ID)if (entry->d_type == DT_DIR &&strspn(entry->d_name, "0123456789") == strlen(entry->d_name)) {// 构造链接路径snprintf(filepath, sizeof(filepath), "/proc/%s/exe", entry->d_name);// 读取符号链接(即可执行文件的路径)if ((len = readlink(filepath, exe_path, sizeof(exe_path) - 1)) != -1) {exe_path[len] = '\0';    // 确保字符串以NULL结尾// 获取basename和dirnamechar *base = basename(exe_path);char *dir  = dirname(exe_path);printf("PID: %s - EXE: %s, BASE: %s, DIR: %s\n", entry->d_name, exe_path, base, dir);} else {perror("readlink");}}}// 关闭/proc目录closedir(proc);return 0;
}

本段代码首先打开/proc目录,然后使用readdir函数遍历每个条目。对于每个条目,如果它是一个目录并且它的名字只包含数字,那么它很可能代表一个进程ID。对于这样的目录,代码构造了一个指向exe链接的路径,然后使用readlink函数尝试读取该链接的目标路径,即可执行文件的路径。成功读取后,将路径打印到标准输出。

需要注意的是,由于涉及到读取系统目录和链接,运行这个程序可能需要相应的权限,通常需要root权限才能读取所有进程的exe路径。此外,由于进程可能在任何时候结束,所以该程序可能无法捕获到系统中的瞬时进程。

下面是实际运行结果(可以看到很多权限报错,这是因为内核线程和一些root进程需要权限,可以用sudo运行):

ubuntu->cs-test:$ gcc -o read-exe read-exe.c 
ubuntu->cs-test:$ ./read-exe
......
readlink: Permission denied
readlink: Permission denied
readlink: Permission denied
PID: 1911597 - EXE: /usr/lib/systemd, BASE: systemd, DIR: /usr/lib/systemd
readlink: Permission denied
readlink: Permission denied
PID: 1911679 - EXE: /usr/bin, BASE: bash, DIR: /usr/bin
readlink: Permission denied
readlink: Permission denied
PID: 1948965 - EXE: /usr/bin, BASE: bash, DIR: /usr/bin
PID: 1949010 - EXE: /usr/bin, BASE: dash, DIR: /usr/bin
PID: 1949074 - EXE: /home/ubuntu/.vscode-server/bin/903b1e9d8990623e3d7da1df3d33db3e42d80eda, BASE: node, DIR: /home/ubuntu/.vscode-server/bin/903b1e9d8990623e3d7da1df3d33db3e42d80eda
PID: 1949131 - EXE: /home/ubuntu/.vscode-server/bin/903b1e9d8990623e3d7da1df3d33db3e42d80eda, BASE: node, DIR: /home/ubuntu/.vscode-server/bin/903b1e9d8990623e3d7da1df3d33db3e42d80eda
......
ubuntu->cs-test:$ sudo ./read-exe 
......
PID: 1961276 - EXE: /usr/bin, BASE: sleep, DIR: /usr/bin
readlink: No such file or directory
readlink: No such file or directory
readlink: No such file or directory
PID: 1962795 - EXE: /usr/bin, BASE: sudo, DIR: /usr/bin
PID: 1962796 - EXE: /usr/bin, BASE: sudo, DIR: /usr/bin
......

sudo运行很多报错No such file or directory, 其实这些进程时内核进程,所以符号链接文件指向是空的

2.4 如何判断内核进程

在Linux系统中,内核线程是一种特殊的进程,它们在内核空间运行,而不是在用户空间。内核线程可以通过检查它们的特征来区分,这些特征在/proc文件系统中可见。以下是一些区分用户进程和内核线程的方法:

  1. 命令行为空:对于内核线程,/proc/[pid]/cmdline文件通常为空,因为它们不是由外部命令启动的。

  2. 没有关联的终端:内核线程通常没有关联的终端。你可以通过查看/proc/[pid]/stat文件中的TTY(终端)字段来检查这一点。如果这个值是0,则表示没有关联的终端。

  3. 父进程:内核线程的父进程通常是kthreadd(PID为2)。

  4. 进程目录的task子目录:对于用户进程,/proc/[pid]/task目录包含该进程的所有线程。内核线程通常只有一个线程,所以这个目录下通常只有一个目录,其名字与PID相同。

  5. /proc/[pid]/exe链接:对于用户进程,/proc/[pid]/exe是一个到可执行文件的符号链接。对于内核线程,exe通常不存在或者链接无效。

  6. 名字的形式:内核线程的名字通常在/proc/[pid]/comm文件中以方括号包围,例如[kthreadd]

2.5 修改comm和cmdline信息

在Linux系统中,进程的名称可以通过两个属性来表示:commcmdlinecomm表示进程的简短名称,通常与可执行文件的名称相同,而cmdline则包含了启动该进程时传递给它的命令行参数。

  • comm:这个名称对应于/proc/[pid]/comm文件,包含了进程的名称。这个名称是进程的可执行文件名的最后16个字符(在Linux内核版本2.6.33之后,长度从15个字符扩展到了16个字符,包括终止的null字符)。

  • cmdline:这个文件/proc/[pid]/cmdline包含了进程启动时的命令行参数,参数之间由null字符分隔。

要修改进程的commcmdline,可以编写C语言程序直接写入到相应的/proc/self/目录下的文件。对于comm,可以调用prctl函数,对于cmdline,需要直接修改argv[]数组的数据。以下是一个示例:

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>int main(int argc, char **argv)
{char        old_comm[16], comm[16], cmdline[64];const char *new_comm, *new_cmdline;size_t      read_bytes, cmdline_len;FILE       *cmdline_file;// 获取旧的commif (prctl(PR_GET_NAME, old_comm) < 0) {perror("prctl get name");return 1;}// 修改commnew_comm = "my-new-comm";if (prctl(PR_SET_NAME, new_comm) < 0) {perror("prctl set name");return 1;}// 验证修改if (prctl(PR_GET_NAME, comm) < 0) {perror("prctl get name");return 1;}printf("Comm change: %s => %s\n", old_comm, comm);// 修改cmdlinenew_cmdline = "fake-name";cmdline_len = strlen(new_cmdline) + 1;// 直接写入到arv[0]的内存空间strncpy(argv[0], new_cmdline, cmdline_len);// 读取验证修改cmdline_file = fopen("/proc/self/cmdline", "r");if (!cmdline_file) {perror("fopen cmdline");return 1;}read_bytes  = fread(cmdline, 1, sizeof(cmdline) - 1, cmdline_file);fclose(cmdline_file);if (read_bytes > 0) {for (size_t i = 0; i < read_bytes; ++i) {if (cmdline[i] == '\0') {cmdline[i] = ' ';    // 替换null字符以打印}}printf("New cmdline: %s\n", cmdline);}return 0;
}

请注意以下几点:

  1. 修改comm通过prctl系统调用完成,参数PR_SET_NAME用于设置新的comm值。
  2. 出于安全和稳定性的考虑,不是所有的环境都允许进程修改自己的cmdline。另外,cmdline的修改通常在进程启动初期完成,一旦进程开始执行,就不推荐修改,因为这可能会迷惑正在监视进程状态的系统管理工具和用户。
  3. 修改这些信息通常用于使进程在系统监视工具中更易于识别,但应谨慎使用,以避免误导或带来安全风险。
  4. 进程可以通过修改其内存空间中的参数来影响显示在cmdline中的内容。这通常是通过修改argv参数实现的,因为/proc/[pid]/cmdline是根据argv的内容生成的。但是这种做法并不常见,也不推荐使用,因为它可能会导致与其他程序的不兼容性,甚至可能会违反安全最佳实践。

下面是实际编译运行的结果:

ubuntu->cs-test:$ gcc -o change-self change-self.c 
ubuntu->cs-test:$ ./change-self 
Comm change: change-self => my-new-comm
New cmdline: fake-name elf 

可以看到,成功改变了commcmdline信息,从ps和top等命令看到的进程信息也会有变化

2.6 实现一个简易的killall工具

下面是一个简单的killall工具,可以kill掉指定可执行文件的进程,通过exe确定(类似pidof -x)进程是否属于目标进程。

/** SPDX-License-Identifier: BSD-3-Clause** Copyright (c) 2024 Once Day <once_day@qq.com>, All rights reserved.** @FilePath: /cs-test/my-killall.c* @Author: Once Day <once_day@qq.com>.* @Date: 2024-02-22 10:16* @info: Encoder=utf-8,Tabsize=4,Eol=\n.** @Description:*  简单的进程查找程序** @History:***/#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>
#include <dirent.h>
#include <ctype.h>
#include <errno.h>
#include <libgen.h>/* 判断一个目录名是否是纯数字 */
static bool is_pid_directory(const char *dir_name)
{while (*dir_name) {if (!isdigit(*dir_name)) {return false;}dir_name++;}return true;
}/* 在检索进程对象时遇到错误, 输出对应的信息用于定位 */
static void print_proc_error(const char *dir_name, const char *msg, int32_t err_code)
{FILE *comm_file;char *newline;char  path[256], comm[256];snprintf(path, sizeof(path), "/proc/%s/comm", dir_name);comm[0]   = '\0';comm_file = fopen(path, "r");if (comm_file) {if (fgets(comm, sizeof(comm), comm_file) != NULL) {/* Remove newline if present */newline = strchr(comm, '\n');if (newline) {*newline = '\0';}}fclose(comm_file);}/* 如果没有读到任何字符, 则提示未知进程名 */if (comm[0] == '\0') {snprintf(comm, sizeof(comm), "(unknown process name)");}/* 打印错误日志信息 */printf("%s [%s(%s)] [%s(%d)].\n", msg, comm, dir_name, strerror(err_code), err_code);return;
}/* 读取exe可执行文件信息, 判断是否为目标进程名, 全匹配 */
bool check_proc_exe(const char *dir_name, const char *proc_name)
{size_t  bytes_read, i, seg;ssize_t link_len;FILE   *cmdline_file;char   *exe_name;char    proc_path[256], exe_path[PATH_MAX + 1];/* /proc/[pid]/exe是一个符号链接文件, 内核进程exe链接文件不存在 */snprintf(proc_path, sizeof(proc_path), "/proc/%s/exe", dir_name);link_len = readlink(proc_path, exe_path, sizeof(exe_path) - 1);if (link_len == -1) {/* note: 内核进程无法读取exe link文件, 只考虑用户空间进程, 即排除ENOENT的错误 */if (errno != ENOENT) {/* 如果非root用户, 可以排除权限错误警告EACCES */print_proc_error(dir_name, "Error reading executable link file", errno);}return false;}/* 确保字符串以 null 字符结尾 */exe_path[link_len] = '\0';/* printf("Executable path: %s\n", exe_path); *//* 处理路径字符串, 获取basename */exe_name = basename(exe_path);/* printf("Executable name: %s\n", exe_name); */if (strncmp(exe_name, proc_name, strlen(proc_name)) == 0) {printf("Process found: %s(%s) %ld.\n", proc_name, dir_name, strlen(proc_name));kill(atoi(dir_name), SIGKILL);return true;}return false;
}/* 通过/proc文件系统查询当前设备上的进程信息 */
bool find_process_by_name(const char *proc_name)
{uint64_t       pid, count;DIR           *proc_dir;struct dirent *entry;/* 读取/proc内核信息虚拟目录信息 */proc_dir = opendir("/proc");if (proc_dir == NULL) {printf("Failed to open /proc directory, %s(%d).\n", strerror(errno), errno);return false;}/* 遍历整个/proc目录, 找到进程子目录, 逐个判断是否为目标进程 */count = 0;while ((entry = readdir(proc_dir)) != NULL) {if (entry->d_type == DT_DIR && is_pid_directory(entry->d_name)) {if (check_proc_exe(entry->d_name, proc_name) == true) {count++;}}}closedir(proc_dir);printf("Total %lu process(es) found.\n", count);
}int main(int argc, char *argv[])
{if (argc != 2) {printf("Usage: %s <process name>\n", argv[0]);return EXIT_FAILURE;}find_process_by_name(argv[1]);return EXIT_SUCCESS;
}

测试时,将2.5节的程序change-self加一个sleep函数,延迟1000s,change-self会改变自身的cmdline和comm内容,但是我们的killall仍然可以识别全部的change-self进程。

编译运行,执行如下:

ubuntu->cs-test:$ gcc -o change-self change-self.c 
ubuntu->cs-test:$ ./change-self & # 重复运行5次
[1] 1971781
Comm change: change-self => my-new-comm
New cmdline: fake-name elf 
ubuntu->cs-test:$ gcc -o my-killall my-killall.c 
ubuntu->cs-test:$ ./my-killall 
Usage: ./my-killall <process name>
ubuntu->cs-test:$ ./my-killall change-self
Error reading executable link file [kworker/u8:0-events_unbound(1971317)] [Permission denied(13)].
Process found: change-self(1971781) 11.
Process found: change-self(1971795) 11.
Process found: change-self(1971803) 11.
Process found: change-self(1971811) 11.
Process found: change-self(1971814) 11.
Error reading executable link file [sh(1973550)] [Permission denied(13)].
Error reading executable link file [tat_agent(2029982)] [Permission denied(13)].
Total 5 process(es) found.
[1]   Killed                  ./change-self
[2]   Killed                  ./change-self
[3]   Killed                  ./change-self
[4]-  Killed                  ./change-self
[5]+  Killed                  ./change-self

可以看到,my-killall成功找到5个change-self,并且kill了它们,实现了我们想要的功能

附录
附录1: opendir和readdir函数简介

在C语言中,opendirreaddir是两个用于操作目录的函数,它们定义在<dirent.h>头文件中。这两个函数通常用来遍历文件系统中的目录结构。

opendir函数用于打开一个目录,使其成为读取目录项的候选项。它的原型如下:

DIR *opendir(const char *name);

这里DIR是一个表示目录流的类型,opendir函数接受一个参数,即要打开目录的路径字符串,并返回一个指向DIR类型的指针。如果打开目录失败,它会返回NULL,并且errno变量会被设置为相应的错误代码,以便可以进一步检查错误发生的原因。

readdir函数用于读取由opendir打开的目录流中的目录项。它的原型如下:

struct dirent *readdir(DIR *dirp);

readdir接受一个指向DIR类型的指针,返回一个指向struct dirent结构的指针。这个结构包含了目录项的详细信息,最重要的字段包括:

  • d_name:一个字符数组,包含了目录项的名字。
  • d_type:一个字符,代表目录项的类型(如普通文件、目录等)。

readdir读取到目录流的末尾或发生错误时,将返回NULL。在使用readdir遍历目录时,它会记住当前的位置,所以每次调用时都会获取下一个目录项。需要注意的是,readdir返回的目录项顺序是不确定的,且可能会包含特殊的...目录项,分别表示当前目录和父目录。

使用opendirreaddir时,一旦目录项读取完成,应该使用closedir函数关闭目录流,释放资源。例如:

DIR *dir = opendir("/path/to/directory");
if (dir) {struct dirent *entry;while ((entry = readdir(dir)) != NULL) {printf("%s\n", entry->d_name);}closedir(dir);
}

在这个例子中,我们打开了指定路径的目录,然后循环读取每个目录项并打印其名称,最后关闭了目录流。opendirreaddir函数是目录操作中非常基础且重要的工具,它们使得在C语言中处理文件系统变得更加容易。

附录2: 介绍fread函数

fread函数用于从文件流中读取数据块,定义在<stdio.h>头文件中,它的原型如下:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数解释:

  • ptr:指向一个缓冲区的指针,用于存储读取的数据。
  • size:每个数据块的大小,以字节为单位。
  • nmemb:要读取的数据块的数量。
  • stream:指向FILE对象的指针,该FILE对象代表了一个打开的文件流。

fread函数从stream指定的文件流中读取nmemb项数据,每项数据的大小为size字节,存储在ptr指向的缓冲区中。函数返回成功读取的数据块数目,如果此数目与nmemb不相符,可能是因为发生了错误或达到了文件末尾。

fgets不同,fread是以二进制形式读取文件,不会在读取的数据后添加空终止符,也不会因为换行符或文件末尾而停止读取。它通常用于读取结构体数据或二进制文件。

一个简单的fread使用示例可能如下:

FILE *file = fopen("example.bin", "rb");
if (file) {char buffer[1024];size_t bytesRead = fread(buffer, sizeof(char), sizeof(buffer), file);fclose(file);// 处理读取的数据...
}

在这个例子中,我们打开了一个名为"example.bin"的二进制文件,然后尝试读取到缓冲区buffer中,并记录实际读取的字节数。

fread非常适合读取二进制数据和大块数据

附录3: 介绍fgets函数

fgets函数用于从文件中读取字符串,定义在<stdio.h>头文件中,它的原型如下:

char *fgets(char *s, int size, FILE *stream);

参数解释:

  • s:指向一个字符数组的指针,用于存储读取的字符串。
  • size:指定最多读取的字符数,包括空终止字符\0
  • stream:指向FILE对象的指针,该FILE对象代表了一个打开的文件流。

fgets函数会从指定的文件流stream中读取字符,直到发生以下三种情况之一:

  1. 读取了size-1个字符;
  2. 遇到换行符'\n'
  3. 到达了文件末尾EOF。

读取停止后,fgets会在字符串的末尾添加一个空终止符\0。如果读取成功,fgets返回s;如果遇到文件末尾或发生错误,fgets返回NULL

fgets是一个安全的读取字符串的函数,因为它允许指定缓冲区的大小,防止缓冲区溢出。

附录4: 介绍readlink函数

readlink函数是一个在类UNIX操作系统中用于读取符号链接(symbolic link)内容的系统调用。符号链接是一种特殊类型的文件,它包含的是指向另一个文件路径的引用。不同于硬链接,符号链接可以跨文件系统,并且链接的目标可以是任何类型的文件,包括目录。

函数的原型定义在 <unistd.h> 头文件中,其用途主要是获取符号链接所指向的原始路径。当你想要知道一个符号链接指向何处时,就可以使用 readlink 函数。

函数原型如下:

#include <unistd.h>
ssize_t readlink(const char *restrict path, char *restrict buf, size_t bufsize);
  • path 参数是指向符号链接的路径字符串。
  • buf 参数是一个字符数组,用来存储符号链接指向的路径。
  • bufsize 参数指定了缓冲区 buf 的大小。

调用 readlink 时,它会将 path 指向的符号链接所引用的路径读取到 buf 中,并返回读取的字节数。如果成功,返回值是写入 buf 的字节数(不包含空终止字符);如果失败,返回 -1 并设置 errno 来表示错误。

readlink 函数不会在 buf 后追加空字符,所以在处理返回的数据时,你可能需要根据返回值在 buf 后手动添加空字符,以确保它是一个标准的字符串。

以下是 readlink 函数可能遇到的一些错误情况:

  • EACCES:路径中的目录不可搜索。
  • EINVALpath 不是一个符号链接。
  • EIO:输入输出错误,无法读取链接。
  • ELOOP:解析 path 中的符号链接时遇到太多层的符号链接。
  • ENAMETOOLONGpath 太长。
  • ENOENTpath 指向的符号链接不存在。
  • ENOTDIRpath 的前缀不是目录。
  • EFAULTpathbuf 指向了不可访问的内存区域。

readlink 是处理符号链接的常用函数,它可以帮助程序了解文件的真实路径,尤其是在处理文件链接时,它提供了一种有效的方法来避免进入符号链接引起的循环。在文件系统操作中,readlink 函数是实现某些特定功能(比如寻找程序的实际安装位置)时不可或缺的工具。

附录5: 介绍kill函数

kill函数是一种在UNIX-like操作系统中用于向进程发送信号的系统调用。它的作用是向指定的进程或进程组发送一个信号,信号可以用来中断、终止甚至改变进程的行为。kill函数的原型定义在头文件 <signal.h> 中,它的使用在C语言编程中相当常见,尤其是在需要进行进程控制和管理的场景下。

函数原型如下:

#include <signal.h>
int kill(pid_t pid, int sig);

这里的pid_t是一个用于进程ID的数据类型,而sig是希望发送的信号的编号。如果调用成功,kill函数返回0;如果失败,返回-1,并设置errno以指示错误原因。

kill函数的参数pid可以是以下几种情况之一:

  • pid > 0:信号被发送到进程ID为pid的进程。
  • pid == 0:信号被发送到与发送进程属于同一个进程组的所有进程。
  • pid < -1:信号被发送到进程组ID为-pid的所有进程。
  • pid == -1:信号被发送到所有发送进程有权限发信号的进程,除了系统进程和调用进程。

sig参数则指定了要发送的信号类型,这些信号包括但不限于:

  • SIGINT:终止进程(可中断)。
  • SIGKILL:立即终止进程(不可中断)。
  • SIGTERM:请求终止进程(可中断和处理)。
  • SIGSTOP:停止(暂停)进程的执行(不可中断)。
  • SIGCONT:如果进程被停止,让它继续执行。

kill函数的强大之处在于它提供了一个简洁的方式来控制进程的行为。例如,当用户在命令行中按下Ctrl+C时,通常会发送SIGINT信号给前台进程组中的所有进程,这通常会导致进程的终止。而在编写守护进程或者需要清理资源的场景中,可能会捕捉SIGTERM信号来执行清理操作后再退出进程。

值得注意的是,有些信号是不能被进程捕捉或忽略的,比如SIGKILLSIGSTOP,这是为了确保系统管理员能够控制系统中的进程。而其他信号,比如SIGTERM,则可以被进程捕捉,以便进行适当的清理工作。

使用kill函数时,需要具有相应的权限,通常是需要进程的拥有者或者超级用户权限才能向进程发送信号。如果权限不足,kill函数会失败,并且errno会被设置为EPERM

总的来说,kill函数是进程间通信和控制的重要手段,它允许进程间以及操作系统与进程之间进行有效的信号传递。程序员在使用kill函数时应当小心谨慎,以确保正确、合理地管理进程。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【软件测试】定位前后端bug总结+Web/APP测试分析

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Web测试中简单…

抖音视频评论数据提取软件|抖音数据抓取工具

一、开发背景&#xff1a; 在业务需求中&#xff0c;我们经常需要下载抖音视频。然而&#xff0c;在网上找到的视频通常只能通过逐个复制链接的方式进行抓取和下载&#xff0c;这种操作非常耗时。我们希望能够通过关键词自动批量抓取并选择性地下载抖音视频。因此&#xff0c;为…

Matlab/simulink基于vsg的风光储调频系统建模仿真(持续更新)

​ 1.Matlab/simulink基于vsg的风光储调频系统建模仿真&#xff08;持续更新&#xff09;

宝塔面板安装了mysql5.7和phpMyadmin,但是访问phpMyadmin时提示502 Bad Gateway

操作流程截图如下&#xff1a; 原因是没有选择php版本 选择php版本 下一页找到phpMyAdmin&#xff0c;选择设置 目前只有纯净态&#xff0c;说明没有php环境&#xff0c;前去安装php环境 点击安装&#xff0c;选择版本&#xff0c;这里选择的是7.4版本&#xff0c;编译安…

利用R语言进行典型相关分析实战

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

SpringMVC 学习(三)之 @RequestMapping 注解

目录 1 RequestMapping 注解介绍 2 RequestMapping 注解的位置 3 RequestMapping 注解的 value 属性 4 RequestMapping 注解的 method 属性 5 RequestMapping 注解的 params 属性&#xff08;了解&#xff09; 6 RequestMapping 注解的 headers 属性&#xff08;了解&…

Opencv中的RNG-随机绘图

在OpenCV中&#xff0c;RNG是一个随机数生成器类&#xff0c;用于生成各种类型的随机数&#xff0c;包括均匀分布或高斯分布的整数和浮点数。RNG类的实例化时可以接受一个无符号整数作为种子值&#xff0c;这个种子值决定了随机数生成序列的起点&#xff0c;相同的种子值将产生…

应用回归分析:非参数回归

非参数回归是一种统计方法&#xff0c;它在建模和分析数据时不假设固定的模型形式。与传统的参数回归模型不同&#xff0c;如线性回归和多项式回归&#xff0c;非参数回归不需要预先定义模型的结构&#xff08;例如&#xff0c;模型是否为线性或多项式&#xff09;。这使得非参…

ASP.NET-实现图形验证码

ASP.NET 实现图形验证码能够增强网站安全性&#xff0c;防止机器人攻击。通过生成随机验证码并将其绘制成图像&#xff0c;用户在输入验证码时增加了人机交互的难度。本文介绍了如何使用 C# 和 ASP.NET 创建一个简单而有效的图形验证码系统&#xff0c;包括生成随机验证码、绘制…

Stable Diffusion 绘画入门教程(webui)-ControlNet(Tile/Blur)

上篇文章介绍了y语义分割Seg&#xff0c;这篇文章介绍下Tile/Blur&#xff08;增加/减少细节&#xff09; Tile用于增加图片细节&#xff0c;一般用于高清修复&#xff0c;Blur用于减少图片细节&#xff08;图片模糊&#xff09;&#xff0c;如下图&#xff0c;用Tile做修复&a…

C语言-数组指针与指针数组

一、简介 对于使用C语言开发的人来说&#xff0c;指针&#xff0c;大家都是非常熟悉的。数组&#xff0c;大家也同样熟悉。但是这两个组合到一起的话&#xff0c;很多人就开始蒙圈了。这篇文章&#xff0c;就详细的介绍一下这两个概念。 指针数组和数组指针&#xff0c;听起来非…

c语言经典测试题1

1.题1 int x5,y7; void swap() { int z; zx; xy; yz; } int main() { int x3,y8; swap(); printf("%d,%d\n"&#xff0c;x, y); return 0; } A: 5,7 B: 7,5 C: 3,8 D: 8,3 大家思考一下选哪一个呢&#xff1f; 我们来分析一下&#xff1a;上述代码中我们创建了4…

sql注入 [极客大挑战 2019]FinalSQL1

打开题目 点击1到5号的结果 1号 2号 3号 4号 5号 这里直接令传入的id6 传入id1^1^1 逻辑符号|会被检测到&#xff0c;而&感觉成了注释符&#xff0c;&之后的内容都被替换掉了。 传入id1|1 直接盲注比较慢&#xff0c;还需要利用二分法来编写脚本 这里利用到大佬的脚…

QT Widget自定义菜单

此文以设置QListWidget的自定义菜单为例&#xff0c;其他继承于QWidget的类也都可以按类似的方法去实现。 1、ui文件设置contextMenuPolicy属性为CustomContextMenu 2、添加槽函数 /*** brief onCustomContextMenuRequested 右键弹出菜单* param pos 右键的坐标*/void onCusto…

Mac OS 下载安装与破解Typora

文章目录 下载Typora破解Typora1. 进入安装目录2. 找到并打开Lincense文件3. 修改激活状态4. 重新打开Typora 下载Typora 官网地址&#xff1a;typora官网 下载最新Mac版&#xff0c;正常安装即可 破解Typora 打开typora,可以看到由于未激活&#xff0c;提示使用期限还剩下15…

day11-项目集成SpringSecurity-今日指数

项目集成SpringSecurity 学习目标 理解自定义认证和授权过滤器流程&#xff1b;理解项目集成SprignSecurity流程&#xff1b; 第一章 自定义认证授权过滤器 1、SpringSecurity内置认证流程 通过研究SpringSecurity内置基于form表单认证的UsernamePasswordAuthenticationFi…

代码随想录刷题第41天

首先是01背包的基础理论&#xff0c;背包问题&#xff0c;即如何在有限数量的货物中选取使具有一定容量的背包中所装货物价值最大。使用动规五步曲进行分析&#xff0c;使用二维数组do[i][j]表示下标从0到i货物装在容量为j背包中的最大价值&#xff0c;dp[i][j]可由不放物品i&a…

图片Base64编码解码的优缺点及应用场景分析

title: 图片Base64编码解码的优缺点及应用场景分析 date: 2024/2/24 14:24:37 updated: 2024/2/24 14:24:37 tags: 图片Base64编码解码HTTP请求优化网页性能加载速度安全性缓存机制 随着互联网的迅猛发展&#xff0c;图片在网页和移动应用中的使用越来越广泛。而图片的传输和加…

halcon中的一维测量

一维测量 像点到点的距离&#xff0c;边缘对的距离等沿着一维方向的测量都属于1D测量范畴。Halocn的一维测量首先构建矩形或者扇形的ROI测量对象&#xff0c;然后在ROI内画出等距离的、长度与ROI宽度一致的、垂直于ROI的轮廓线&#xff08;profile line&#xff09;的等距线。…

抖音数据挖掘软件|视频内容提取

针对用户获取抖音视频的需求&#xff0c;我们开发了一款功能强大的工具&#xff0c;旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们希望用户能够通过简单的关键词搜索&#xff0c;实现自动批量抓取视频&#xff0c;并根据需要进行选择性批量下载。因此&a…