进程

文章目录

    • 进程
      • I 进程基本概念
        • 1、进程和程序
        • 2、进程号和父进程号
        • 3、进程内存布局
        • 4、虚拟内存管理
          • (1)程序的两种局部性
          • (2)虚拟内存的规划
          • (3)虚拟内存的优点
        • 5、栈和栈帧
        • 6、命令行参数argc和argv
        • 7、环境变量
          • (1)通过全局变量environ访问环境变量
          • (2)通过getenv()获取环境变量
          • (3)通过putenv()修改环境变量
          • (4)通过setenv()修改环境变量
          • (5)通过unsetenv()移除环境变量
          • (6)shell中的环境变量
      • II 进程的创建
        • 1、fork()创建进程
        • 2、父子进程间的文件共享
        • 3、fork()的内存语义
        • 4、同步信号以规避竞争条件
      • III 进程的终止
        • 1、main函数return
        • 2、系统调用_exit()
        • 3、库函数exit()
        • 4、函数atexit()
        • 5、函数on_exit()
        • 6、进程终止的细节
        • 7、fork()、stdio缓冲区以及_exit()之间的交互
      • IV 监控子进程
        • 1、系统调用wait()
        • 2、系统调用`waitpid()`
        • 3、等待状态值`status`
        • 4、孤儿进程与僵尸进程
      • V 进程的执行
        • 1、系统调用`execve()`
        • 2、exec系库函数
        • 3、通过system()执行shell指令

I 进程基本概念

1、进程和程序
  • 进程是一个可执行程序的运行时实例

  • 进程是由内核定义的抽象实体,并为该实体分配用于执行程序的各种资源
2、进程号和父进程号
  • 每个进程都有一个进程号(PID),进程号是一个正数,用于唯一标识系统中的某个进程

  • 可以通过系统调用getpid()来获取当前进程的PID
    #include <unistd.h>pid_t getpid(void);
  • 可以通过系统调用getppid()来获取当前进程的父进程PID
    #include <unistd.h>pid_t getppid(void);
  • 如果子进程的父进程终止,那么该子进程就会变为孤儿进程,init进程(PID为1)将收养该进程,子进程后续对getppid()的调用将返回进程号1
3、进程内存布局

每个进程所分配的内存由程序段、数据段、栈和堆组成:

图片描述

  • 程序段(Text) 包含了进程运行的程序机器语言指令,具有只读属性,程序段可共享给多个进程同时运行

  • 未初始化数据段(bss) 包含了未显示初始化的全局变量和静态变量,程序启动前系统会将该段内所有内存初始化为0
    int a[2];/*未显示初始化的变量*/
  • 初始化数据段(InitializedData) 包含了显示初始化的全局变量和静态变量
    int a[2] = {0,1};/*显示初始化的变量*/
  • 栈(Stack) 是一个动态增长和收缩的段,由栈帧组成,系统会为每个当前调用的函数分配一个栈帧,用于存储函数的局部变量、实参、返回值

  • 堆(Heap) 是可在运行时动态分配的一块区域(例如malloc())
    char *p = (char *)malloc(sizeof(char)*1024);/*p指向堆内存*/
  • 命令行参数和环境变量单独存放于栈之上的一片连续的内存区域
4、虚拟内存管理
(1)程序的两种局部性

   空间局部性:由于指令是顺序执行的,程序倾向于访问当前(或最近)访问过的内存附近的内存

   时间局部性:由于循环,程序倾向于在不久的将来再次访问最近刚访问过的内存

(2)虚拟内存的规划

虚拟内存映射关系

  • 虚拟内存将每个程序使用的内存分割成固定大小的页(通常4KB);相应地,物理内存被划分为相同大小的页帧;每个程序仅有部分页驻留在物理内存中,其余页保留在磁盘交换区(swap area)

  • 内核为每个进程维护页表,实现虚拟地址到物理地址的映射。页表项记录虚拟页对应的物理页帧位置(若不在内存则标记为无效并包含磁盘位置信息)

  • 当进程访问的虚拟地址对应的页不在物理内存时,会触发缺页异常(page fault),此时操作系统负责从磁盘加载所需页到内存,并更新页表

  • 虚拟内存的实现需要硬件中分页管理单元的支持,它负责将么个虚拟内存地址转换为相应的物理内存地址
(3)虚拟内存的优点
  • 扩展可用内存:通过交换技术使程序可使用超过物理内存容量的地址空间

  • 进程隔离保护每个进程拥有独立地址空间,防止非法内存访问

  • 简化编程模型程序员无需考虑物理内存限制和布局

  • 高效内存利用通过按需分页和共享机制减少物理内存占用

  • 支持高级特性:为内存映射文件、写时复制等技术提供基础
5、栈和栈帧

  函数调用使栈的收缩和增长呈线性;每次函数调用时,会在栈上分配一个栈帧,函数返回时再从栈上将此帧去除

  每个栈帧包含以下信息:

  (1)函数实参和局部变量

    存储函数调用时传入的参数以及在函数内部定义的局部变量

  (2)函数调用的链接信息:

    包括返回地址(调用结束后继续执行的指令地址)和调用者的栈帧指针(用于恢复调用者的栈帧)

6、命令行参数argc和argv

  每个C语言程序都是从main()开始执行的,命令行的参数通过argcargv传递给main()

    int main(int argc, char *argv[]){/*function main*/}
  • argc表示参数的个数

  • argv是一个指向命令行参数的指针数组第一个指针argv[0]指向的字符串通常是该程序的名称

  • argv中的每个指针指向的参数都是一个以空字符结尾的字符串,argv指针列表以NULL结尾(argv[argc]==NULL)

7、环境变量

每个进程都有与其相关的称为环境变量的字符串数组,他们是“名称-值”的成对集合,可以存储任何信息。新进程在创建时会继承其父进程的环境副本。

使用putenv、setenv、unsetenv设置的环境变量作用域为当前进程及其子进程

(1)通过全局变量environ访问环境变量
  • 在C语言中可以通过全局变量char **environ访问环境变量
    extern char **environ;
  • environ变量结构与argv类似:

在这里插入图片描述

  • 打印所有环境变量
    extern char **environ;int main(){char **ep;for(ep = environ; *ep != NULL; ep++){printf("%s\n",*ep);}return 0;}
(2)通过getenv()获取环境变量
  • getenv()函数能够从环境变量中检索某个值:
    #include <stdlib.h>char *getenv(const char *name);/*return pointer to stiring,or NULL if no such environment variable*/
  • 参数name是需要检索的环境变量的名称

  • 获取环境变量SHELL的值的示例:
    // Get and print specific environment variable// argv[0] is the function name// argv[1] is the name of envint main(int argc, char *argv[]){// Check if argument is providedif(argc < 2){printf("usage:%s <name>\n", argv[0]);return -1;}// Get environment variablechar *env = getenv(argv[1]);if(strcmp(env,"NULL") == 0)printf("no such valuable %s\n", argv[1]);elseprintf("the value of $%s is %s\n", argv[1], env);return 0;}
(3)通过putenv()修改环境变量
  • putenv()函数用于添加或修改环境变量:
    #include <stdlib.h>int putenv(char *string);/*returns 0 on success, or nonzero on error*/
  • 参数string应为NAME=value格式的字符串

  • 不会复制传入的字符串,而是直接将其指针添加到环境变量表中。因此,如果后续修改或释放传入的字符串,环境变量的值也会受到影响

  • 修改环境变量PATH的示例:
    // Put environment variable in form "name=value" and print it if successfulint main(int argc, char *argv[]){// Check if argument is providedif(argc < 2){printf("usage:%s <name=value>\n",argv[0]);return -1;}// Set environment variableif(putenv(argv[1]) != 0){perror("putenv");return -1;}// Search and print the newly set environment variablefor(char **ep = environ; *ep != NULL; ep++){if(strcmp(argv[1],*ep) == 0){printf("%s\n", *ep);break;}}return 0;}
(4)通过setenv()修改环境变量
  • setenv()函数更安全地设置环境变量:
    #include <stdlib.h>int setenv(const char *name, const char *value, int overwrite);/*returns 0 on success, or -1 on error*/
  • 参数name是需要修改的环境变量的名称,参数value是需修改为的值

  • 若环境变量已存在,参数overwrite为0时,不修改其值,非0时将修改值

  • 示例:
    // Set environment variable with name and value, overwrite if existsint main(int argc, char *argv[]){// Check if arguments are providedif(argc < 3){printf("Usage:%s <name> <value>\n", argv[0]);return -1;}// Set environment variable with overwrite flagif(setenv(argv[1], argv[2], 1) != 0){perror("setenv");return -1;}// Print the newly set environment variableprintf("$%s = %s\n", argv[1], getenv(argv[1]));return 0;}
(5)通过unsetenv()移除环境变量
  • unsetenv()函数用于移除环境变量:
    #include <stdlib.h>int unsetenv(const char *name);/*returns 0 on success, or -1 on error*/
  • name是需要移除的环境变量的名称

  • 若需要清除所有环境变量可将全局变量environ赋值为NULL,或使用cleanenv()

  • 示例:

    // Remove environment variable and verify removalint main(int argc, char *argv[]){// Check if argument is providedif(argc < 2){printf("Usage:%s <name>", argv[0]);return -1;}// Remove environment variableif(unsetenv(argv[1]) != 0){perror("unsetenv");return -1;}// Verify and print removal statuschar *get = getenv(argv[1]);if(get == NULL)printf("$%s has been cleaned!\n", argv[1]);elseprintf("$%s = %s", argv[1], get);return 0;}
(6)shell中的环境变量
  • 通过在自身环境列表中放置变量值,shell就可以把这些值传递给其所创建的进程,并以此来执行用户的命令
  • Linux下shell环境变量常见操作:
    • export VAR=value 设置环境变量(对当前shell及子进程有效)
    • unset VAR 删除环境变量
    • env 查看所有环境变量
    • echo $VAR 查看特定环境变量值
  • 特殊环境变量:
    • PATH 可执行文件搜索路径
    • HOME 用户主目录路径
    • SHELL 当前shell程序路径
    • USER 当前用户名
  • 持久化配置:
    • 全局配置:/etc/profile/etc/environment
    • 用户配置:~/.bashrc~/.profile

II 进程的创建

通过fork() exit() wait() execve()等系统调用对进程进行创建、退出、等待和执行其他进程等操作,他们之间的协作关系如下(shell执行一条命令的过程):

在这里插入图片描述

1、fork()创建进程
  • 系统调用fork()用于创建一个新的进程,创建的新进程几乎是对调用进程的翻版
    #include <unistd.h>pid_t fork(void);/*In parent,return pid of child process on success or -1 on error;In successfully created child: always return 0 */
  • 完成对fork()的调用后将存在两个进程,且两个进程都会从fork()开始执行

  • 两个进程虽然执行相同的程序文本段,但却各自拥有不同的栈段、数据段以及堆段拷贝

  • fork()创建进程后父子进程都可以修改各自的栈数据以及堆中的变量

  • 程序代码中可通过fork()的返回值来区分父子进程,父进程中fork()返回子进程的pid,子进程中fork()返回0

  • 调用fork()之后,系统将调度哪个进程先试用CPU是无法确定的,这会导致竞态条件

  • 示例:

    int main(){int a = 100;char b[1024] = "hello world!";pid_t proc = fork();if(proc == -1){perror("fork");return -1;}else if(proc == 0){a = 3;strcpy(b, "I am child process!");printf("a = %d,\n b = %s\n", a, b);return 0;}else{printf("I am parent process!\n");printf("a = %d,\n b = %s\n", a, b);} return 0;}
2、父子进程间的文件共享
  • 执行fork()时,子进程会获得父进程所有的文件描述符的副本,父子进程中对应的描述符均指向相同的打开文件句柄;

  • fork()使子进程继承了父进程的文件属性,包括文件偏移量、文件属性等,如果子进程更新了文件属性,那么父进程中的文件属性也将随之更新

  • 示例:在父进程中打开文件,在子进程中验证是否继承属性并写入内容,在父进程中读取子进程写入的内容
    int main(int argc, char *argv[]){if(argc < 2){printf("Usage:%s <file_name>\n", argv[0]);return -1;}// Open file with read-write permissions, create if doesn't existint fd = open(argv[1], O_RDWR | O_CREAT, 0644);if (fd == -1){perror("open");return -1;}// Create child processpid_t pid = fork();if (pid == -1) {perror("fork");close(fd);return -1;} if(pid == 0) {// Verify child process inherits file attributes from parent process if(fcntl(fd, F_GETFL) & O_RDWR) {printf("File is opened with O_RDWR\n");}// Child process writes to filechar str[] = "<this is child>";if(write(fd, str, strlen(str)) == -1){perror("Child write");};}if(pid > 0){sleep(1);  // Wait for child to complete writinglseek(fd, 0, SEEK_SET);  // Reset file pointer to beginning// Parent process reads from file after child writeschar buf[1024];    if(read(fd, buf, 1024) == -1){perror("read");close(fd);return -1;}printf("parent read from file %s :\n %s\n", argv[1], buf);}close(fd);  // Close file descriptorreturn 0;}
3、fork()的内存语义
  • 内核将每一进程的代码段标记为只读,父子进程可共享同一段代码;系统调用fork()在为子进程创建代码段时,其所构建的一系列进程级页表项均指向与父进程相同的物理内存页帧;

  • 对于数据段、堆段和栈段,内核采用写时复制(CoW)技术:初始时,它们的页表项指向与父进程相同的物理页。当任一进程尝试修改共享页时,内核会捕获写操作,为修改的进程分配新的物理页,并更新页表,这时父子进程便可以互不干扰的分别修改各自的页帧。

在这里插入图片描述

4、同步信号以规避竞争条件
  • 竞争条件:调用fork()后,父子进程的执行顺序由内核调度器决定,这种不确定性可能导致对共享资源(如文件描述符、内存等)的访问出现竞态条件,从而影响程序行为的正确性

  • 使用信号同步:父进程可以通过信号(如SIGUSR1)来通知子进程开始执行,从而避免竞争

  • 示例:父进程等待子进程准备就绪后发送信号,子进程收到信号后再执行关键代码
    sig_atomic_t p_sync = 0;  // Atomic flag for process synchronizationvoid handler(){p_sync = 1;  // Signal handler sets sync flag}int main(){signal(SIGUSR1, handler);  // Register signal handlerpid_t pid = fork();if(pid == -1){perror("fork");return -1;}if(pid == 0){// Child waits for signalwhile(!p_sync){pause();  // Sleep until signal received}printf("child process executing!\n");}if(pid > 0){printf("parent process preparing!\n");sleep(1);  // Give child time to setupkill(pid, SIGUSR1);  // Send signal to child}return 0;}

III 进程的终止

1、main函数return

  进程终止的一种方法是在进程的main函数中通过return,或者执行到main函数结尾,调用main的运行时函数会将main函数的返回值作为exit()的参数

2、系统调用_exit()
  • 进程可以调用_exit()系统调用正常终止
    #include <unistd.h>void _exit(int status);
  • 参数status定义了进程的终止状态,父进程可以调用wait()来获取该状态,终止状态为0表示成功退出,终止状态为非0值则表示异常退出
3、库函数exit()
  • exit()对系统调用_exit()进行了封装,在执行_exit()之前会执行一系列操作
    #include <stdlib.h>void exit(int status);
  • exit执行的操作包括:

  (1)、调用退出处理函数(由atexit()on_exit()注册的函数),执行顺序与注册顺序相反

  (2)、刷新stdio流缓冲

  (3)、使用由status提供的值执行系统调用_exit()

4、函数atexit()
  • atexit函数用于注册退出处理函数
    #include <stdlib.h>int atexit(void (*func)(void));/*return 0 on success or nozero on error*/
  • 参数func指向需注册的函数,atexit()会将其添加到一个函数列表中,进程终止时会调用该函数列表的所有函数,func()函数应为无参数,无返回值的形式:
    void func(void){/*perform some actions*/}
  • atexi()注册的退出处理函数会有两种限制:无法获知传递给exit()的状态,无法给给退出处理程序指定参数

  • 示例:处理函数的执行顺序与注册顺序相反
    // Function to be called at exitvoid func_atexit_01(){printf("func_atexit 01 is called\n");}// Function to be called at exitvoid func_atexit_02(){printf("func_atexit 02 is called\n");}// Register exit handler and demonstrate execution orderint main(){// Register exit handler functionif(atexit(func_atexit_01) != 0){printf("atexit error\n");};if(atexit(func_atexit_02) != 0){printf("atexit error\n");};// Main function executionprintf("hello, this is the main function\n");return 0;}
5、函数on_exit()
  • 函数on_exit()同样用于注册退出处理函数
    #define _BSD_SOURCE#inlcude <stdio.h>int on_exit(void (*func)(int, void*), void *arg)/*return 0 on success or nozero on error*/
  • func是指向退出处理函数的指针,可带参数:
    void func(int status, void *arg){/*perform some actions*/}

  status为提供给exit()的参数,argv为提供给on_exit()的一份参数拷贝

  • 示例:
    void func_onexit_01(int status, void *arg){printf("onexit func 01 is called\n");printf("status = %d, arg = %s\n", status, (char *)arg);}void func_onexit_02(int status, void *arg){printf("onexit func 02 is called\n");printf("status = %d, arg = %s\n", status, (char *)arg);}int main(){if(on_exit(func_onexit_01, "first") != 0)printf("onexit error\n");if(on_exit(func_onexit_02, "second") != 0)printf("onexit error\n");printf("hello, this is the main function\n");return 0;}
6、进程终止的细节
  • 进程终止时内核会执行以下操作

      (1)、关闭所有打开的文件描述符、目录流、消息队列描述符等

      (2)、释放进程持有的文件锁、共享内存等资源

      (3)、将子进程的父进程改为init进程(PID=1)

      (4)、向父进程发送SIGCHLD信号

      (5)、将进程状态置为僵尸状态(Zombie),直到父进程调用wait()获取终止状态

  • 异常终止的情况

      (1)、进程收到未处理的信号(如SIGSEGV、SIGKILL)

      (2)、进程调用abort()触发SIGABRT信号

      (3)、最后一个线程执行pthread_exit()

7、fork()、stdio缓冲区以及_exit()之间的交互
  • 调用fork()时,子进程会继承父进程的stdio缓冲区副本

  • 如果父进程在调用fork()前有未刷新的缓冲区数据(例如printf输出),这些数据会被复制到子进程

  • 示例:
    int main(){printf("hello world\n");write(STDOUT_FILENO, "Fison\n", 6);if(fork() == -1){perror("fork");return -1;}return 0;}

  输出为:

    $ ./mainhello worldFison$ ./main > ./tmp$ cat tmpFisonhello worldhello world

  两个输出不一样的原因
  (1)、write()是系统调用,直接写入文件描述符,不经过缓冲区

  (2)、printf()使用stdio缓冲区,默认行缓冲模式(终端)会立即刷新,全缓冲模式(重定向到文件)不会立即刷新

  (3)、fork()时未刷新的stdio缓冲区会被复制到子进程,导致父子进程各刷新一次缓冲区

  • 为防止以上情况,可以

    • Call fflush(NULL) before fork() to flush all stdio buffers.
    • Use _exit() in child processes to avoid flushing buffers twice.

IV 监控子进程

1、系统调用wait()
  • 系统调用wait()等待调用进程的任一子进程终止,同时在status所指的缓冲区返回该子进程的终止状态
    #include <sys/wait.h>pid_t wait(int *status);/*return process id of terminated child or -1 on error*/
  • wait()将一直阻塞,直达某个子进程终止,如果调用时已经有子进程终止,wait()会立即返回

  • status非空时,子进程的终止信息将通过其指向的指针返回

  • wait()会返回终止子进程的ID

  • wait()的缺点:

  (1)、无法等待某个特定的子进程完成

  (2)、没有子进程退出,则一直保持阻塞

  (3)、只能发现已经终止的进程,无法发现因某个信号终止的进程或恢复执行的进程

  • 示例:
    int process_wait(int argc, char *argv[]){// Check if correct number of arguments is providedif(argc < 2){printf("Usage:%s <number to fork>\n", argv[0]);return -1;}pid_t pid;// Fork multiple child processes based on input argumentfor(int i = 0; i < atoi(argv[1]); i++){pid = fork();if(pid == -1){perror("fork");return -1;}// Child process executionif(pid == 0){printf("child %ld is created\n", (long int)getpid());sleep(1);  // Simulate workexit(EXIT_SUCCESS);  // Exit successfully}}// Parent process waits for all children to terminatepid_t child;while(1){child = wait(NULL);  // Wait for any child to terminateif(child == -1){if(errno == ECHILD){  // No more children leftprintf("all child terminated\n");break;} else{  // Other error occurredprintf("wait error\n");continue;}}printf("child %ld terminated successfully\n", (long int)child);}return 0;}
2、系统调用waitpid()
  • 系统调用waitpid()用于等待IDpid的子进程终止,并通过参数status返回状态信息
    #include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int optinos);/*return process id of child, 0(...), or -1 on error*/ 
  • 参数pid可以为任意整数:

    pid大于0,表示等待ID为pid的进程终止

    pid等于0,表示等待与父进程同一个进程组的所有子进程

    pid小于-1,表示等待进程组标识符与pid绝对值相同的所有子进程

    pid等于-1,表示等待任意进程,与wait()等价

  • 参数option是一个位掩码,可以按位或,其值可以是:

    WUNTRACED-除了返回终止子进程信息外,还返回因信号而停止的子进程信息

    WCONTINUED-返回那些因收到SIGCONT

    WNOHANG如果pid所指定的子进程并未发生状态变化,则立即返回,而不会阻塞

3、等待状态值status
  • WIFEXITED(status)–若子进程真诚结束则返回真

  • WIFSIGNALED(status)–若通过信号杀掉子进程则返回真

  • WIFCONTINUED(status)–若子进程收到SIGCONT而恢复执行则返回真

  • WIFSTOPPED(status)–若子进程因信号而终止则返回真

  • 示例:waitpid()status
    int main(){// Create child processpid_t pid = fork();if(pid == -1){perror("fork");return -1;}// Child process executionif(pid == 0){printf("this is child\n");exit(EXIT_SUCCESS);}// Parent process waits for child with status checkingint status;pid_t w_child = waitpid(pid, &status, WUNTRACED); if( w_child == -1){perror("waitpid");return -1;}else{printf("child %ld terminted\n", (long int)w_child);}// Check termination statusif(WIFEXITED(status))printf("child termined normally\n");if(WIFSIGNALED(status))printf("child terminated by signal\n");return 0;}
4、孤儿进程与僵尸进程
  • 孤儿进程(Orphan Process)

    父进程先于子进程终止时,子进程成为孤儿进程

    孤儿进程会被init进程(PID=1)收养

    不会对系统造成危害

  • 僵尸进程(Zombie Process)

    子进程终止后,父进程尚未调用wait()/waitpid()获取其终止状态

    进程表中仍保留子进程的退出状态等信息

    会占用系统资源(进程表项)

    大量僵尸进程会导致系统无法创建新进程

  • 处理方法

    父进程调用wait()/waitpid()主动回收

    父进程忽略SIGCHLD信号(可能导致无法获取子进程状态)

    杀死父进程(孤儿进程会被init接管并自动回收)

V 进程的执行

1、系统调用execve()
  • 系统调用execve()可以将程序加载到某一进程的内存空间,进程的栈、数据段以及堆将会被新程序的相应部分所替换
    #include <unistd.h>int execve(const char *pathname, char *const argv[], char *const envp[]);/*never return on success, return -1 on error*/
  • 参数pathname是要执行的可执行文件的路径名,可以是绝对路径也可以是相对路径

  • 参数argv是指向传递给新程序的参数列表的指针数组,以NULL结尾

  • 参数envp是指向新程序环境变量的指针数组,以NULL结尾

  • execve()的调用将永不反悔,而且也无需检查其返回值,一旦函数返回,就表明发生了错误,可以通过errno来判断出错的原因

  • 示例:
    int process_exec(int argc,char *argv[]){if(argc < 3){printf("Usage: %s <executablefile> <arguements>", argv[0]);return -1;}char *envp[] = {"PATH=/bin:/usr/bin:/usr/local/bin", NULL};if (execve(argv[1], &argv[1], envp) == -1){perror("execve");return -1;}// This line will never be reached if execve succeedsprintf("After execve\n");return 0;}
2、exec系库函数
  • 基于execve()系统调用,标准C库提供了多个exec函数变体:
    #include <unistd.h>int execl(const char *path, const char *arg, ..., NULL);int execlp(const char *file, const char *arg, ..., NULL);int execle(const char *path, const char *arg, ..., NULL, char *const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 函数命名规则:

    l-参数以列表形式传递

    v-参数以数组形式传递

    p-使用PATH环境变量查找可执行文件

    e-可自定义环境变量

  • 示例:不同exec函数的使用
    // execl示例execl("/bin/ls", "ls", "-l", NULL);// execv示例char *argv[] = {"ls", "-l", NULL};execv("/bin/ls", argv);// execlp示例execlp("ls", "ls", "-l", NULL);// execle示例char *envp[] = {"PATH=/bin", NULL};execle("/bin/ls", "ls", "-l", NULL, envp);// execvp示例char *argv_vp[] = {"ls", "-l", NULL};execvp("ls", argv_vp);// execvpe示例char *argv_vpe[] = {"ls", "-l", NULL};char *envp_vpe[] = {"PATH=/bin", NULL};execvpe("ls", argv_vpe, envp_vpe);
3、通过system()执行shell指令
  • system()函数提供了一种执行shell命令的简单方式
  • 该函数会创建一个子进程来执行指定的命令,并等待其完成
    #include <stdlib.h>int system(const char *command);/* Returns termination status of shell or -1 on error */
  • 参数command是要执行的shell命令字符串

  • 返回值:

    • 成功时返回命令的终止状态
    • 如果无法创建子进程或无法获取状态则返回-1
    • 如果command为NULL,返回非零值表示shell可用
  • 实现原理:

    • 调用fork()创建子进程
    • 子进程调用execl("/bin/sh", "sh", "-c", command, NULL)执行命令
    • 父进程调用waitpid()等待子进程结束
  • 示例:

    int ret = system("ls -l");if (ret == -1) {perror("system");return -1;}else if (WIFEXITED(ret) && WEXITSTATUS(ret) != 0) {printf("Command failed with exit status %d\n", WEXITSTATUS(ret));}

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

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

相关文章

0基础学Python系列【25】 单元测试入门教程

大家好,欢迎来到Python学习的第三站!🎉 这部分会涉及一些Python的进阶技术,虽然不一定是必需的,但学会这些,你会觉得编程更得心应手。 本章要学什么? Python调试器(pdb)装饰器lambda函数代码性能分析单元测试入门 —— 今天讲这里听起来有点多?别担心,我们慢慢来,…

iOS常见内存错误码

一、经典十六进制错误码0xDEADBEEF&#xff08;EXC_BAD_ACCESS&#xff09; 含义&#xff1a;野指针访问&#xff08;访问已释放的内存地址&#xff09;。 记忆点&#xff1a;“DEAD BEEF” 可理解为 “死亡牛肉”&#xff0c;象征指针指向的内存已 “死亡”。 触发场景&#x…

CSS01:CSS的快速入门及优势

CSS快速入门 style 练习格式&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS</title><!-- 规范,<style>可以编写css代码,每一个声明最好用分号结尾语法&#xff1a;…

springsecurity5配置之后启动项目报错:authenticationManager cannot be null

目录 配置代码 报错信息 解决办法 配置代码 下面的配置为响应式的配置方式 //这个配置只是配置springboot admin的一个例子,具体的配置可能比较复杂 @EnableWebFluxSecurity public class SecurityConfig {private final AdminServerProperties adminServer;public Securi…

攻防世界-Rerverse-game

知识点 1.ida逆向 2.函数分析逆向 步骤 用Exeinfo打开&#xff0c;为32位exe文件。 方法一&#xff1a; 玩游戏通关&#xff0c;根据游戏规则&#xff0c;m1&#xff0c;n依次为1到8即可得到flag。 方法二&#xff1a; 用32位IDA打开 ctrlF搜索main&#xff0c;点击_main,…

openEuler 24.03 全流程实战:用 Ansible 5 分钟部署分布式 MinIO 高可用集群

目录 0 | 为什么要写这篇教程&#xff1f; 1 | 准备工作 1.1 控制节点手工下载 MinIO 1.2 SSH 互信&#xff08;可跳过&#xff0c;本教程已有互信&#xff09; 1.3 安装 Ansible & SELinux 依赖 2 | 项目目录 3 | Inventory hosts.ini 4 | 变量文件 group_vars/al…

最左匹配原则

导读&#xff1a; 首先创建一张 test 表&#xff0c;并插入一些数据&#xff1a; CREATE TABLE test ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键, a int(11) NOT NULL, b int(11) NOT NULL, c int(11) NOT NULL, d int(11) NOT NULL, PRIMARY KEY (id), KEY idx_abc …

MySQL 8.0 OCP 1Z0-908 题目解析(17)

题目65 Choose two. Which two are characteristics of snapshot-based backups? □ A) The frozen file system can be cloned to another virtual machine immediately into active service. □ B) There is no need for InnoDB tables to perform its own recovery when re…

Level2_12小球与挡板(移动+反弹)

一、前引 #已经学习完了: #1.数据结构&#xff1a;集合、元组、字典 #2.函数 #3.类和对象 #4.继承与多态 #1.规划编程项目: #&#xff08;1&#xff09;你想做什么什么样功能的项目&#xff1f; # 接小球游戏,碰到挡板时自动反弹 #&#xff08;2&#xff09;功能有哪些&#x…

win11 2025开机禁用微软账号登录,改本地用户登录,品牌预装机福音

今天开箱了品牌商出厂系统一台华为笔记本&#xff0c;开机提示连接wifi并需要登录微软账号&#xff0c;其中过程实在缓慢&#xff0c;而且老是提示自动更新&#xff0c;速度太慢了&#xff0c;等的花都谢了&#xff0c;进到桌面大概得要30-40分钟&#xff0c;还不如本地用户登录…

【嵌入式ARM汇编基础】-ELF文件格式内部结构详解(三)

ELF文件格式内部结构详解(三) 文章目录 ELF文件格式内部结构详解(三)12、动态部分和动态加载13、依赖加载(需要)14、程序重定位14.1 静态重定位14.2 动态重定位14.3 全局偏移表 (GOT)14.4 过程链接表 (PLT)12、动态部分和动态加载 ELF 文件格式中的 .dynamic 部分用于指…

HTML知识复习2

文章目录 HTML5简介什么是HTML5HTML5优势 新增语义化标签新增布局标签新增状态标签新增列表标签新增文本标签 新增表单功能表单控件新增属性input新增属性值 新增多媒体标签视频标签音频标签 HTML5兼容性处理 HTML5简介 什么是HTML5 HTML5 是新一代的 HTML 标准&#xff0c;2…

栈(Stack)和队列(Queue)

文章目录 前言1. 栈(Stack)1.1 什么是栈1.2 栈的常用操作1.3 栈的模拟实现1.4 栈的应用场景1.4.1 元素序列处理1.4.2 字符串反转1.4.3 括号匹配1.4.4 逆波兰表达式求值1.4.5 栈的压入、弹出序列1.4.6 最小栈1.4.7 递归转循环 1.5 概念区分1.5.1 数据结构中的栈1.5.2 JVM中的虚拟…

5G MEC四大核心挑战技术解析报告

一、MEC园区部署挑战:数据本地化与低时延接入 痛点深度解析 数据不出园区:工业质检、医疗影像等敏感业务需数据在本地闭环处理。但运营商基站与企业MEC间若经公网绕行,时延超50ms且存在泄露风险。L2网络局限:传统L2接入网无法实现基站→UPF的智能路由,导致业务流绕行城域…

【硬核拆解】英伟达Blackwell芯片架构如何重构AI算力边界?

前言 前些天发现了一个巨牛的人工智能免费学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 一、Blackwell诞生的算力危机&#xff08;2025现状&#xff09; graph TD A[2025年AI算力需求] --> B[千亿参数模型训练能耗…

【深度学习模块】图像的相对位置编码

这个是一个常用的模块&#xff0c;就是我们可以对输入的特征嵌入位置编码。 位置编码&#xff08;Positional Encoding&#xff09;是一种将空间位置信息嵌入到特征中的方法&#xff0c;通常用于帮助模型更好地理解特征的空间关系。 这里介绍的这个是相对位置编码&#xff0c;…

osg加入实时光照SilverLining 天空和3D 云

OSG系列文章目录 文章目录 OSG系列文章目录一、前言官网的介绍&#xff1a; 二、编译官网例子 一、前言 osg本身也可以加入动态云&#xff0c;但是效果有点差强人意&#xff0c;这里我们使用sundog公司的动态云&#xff1a;SilverLining 天空和 3D 云。 官网的介绍&#xff1…

spring-ai-alibaba 1.0.0.2 学习(十二)——聊天记忆扩展包

学习spring-ai时提到过&#xff0c;spring-ai除了内置的InMemoryChatMemoryRepository&#xff0c;还提供jdbc、cassandra、neo4j三个扩展包。 而spring-ai-alibaba则提供了jdbc、redis、elasticsearch三个扩展包。 两者都提供了jdbc扩展包&#xff0c;有什么区别呢&#xff…

c语言-指针(数组)练习2

题目&#xff1a;将数组中n个元素按逆序存放并打印出来&#xff0c;使用函数封装与指针 思路&#xff1a; 1.定义一个数组arr[5]和用于存放数组大小&#xff08;数组大小通过sizeof关键字来进行计算&#xff09;的变量len&#xff1b; 2.创建三个函数initArr、printArr、rev…

Redis服务器

Redis&#xff0c;一款Key-Value型内存数据库 常用于网站开发场景 Redis服务器只发布了Linux版本 Redis服务器安装&#xff0c;2种办法 自动安装 apt install redis-server手动编译安装 从官网下载源码&#xff0c;编译&#xff0c;部署 1 安装redis apt install redis-s…