LinuxC语言并发程序笔记(第二十天)

C语言进程打开其他程序:

在qt中,我们会用到一个叫QProcess的类,它可以打开一个外部的程序,但和c的不太一样,其无法创建进程,并用该进程打开程序,而是直接打开外部程序。

exec函数族

C语言进程可以通过在进程中调用exec函数族执行某些程序,但那个子进程的所以从父进程继承的内容都会被替换掉。它能让父子进程执行不一样的程序,并且父进程不受到影响。

最大的使用exec的程序就是我们的Shell(命令行)。

execl/execlp:

exec族的函数:

int execl(const char *path,const char *arg,...);

int execlp(const char *file,const char *arg,...);

两个函数功能相同,成功执行的时候执行指定的程序,失败返回EOF。

其中path是程序的地址(绝对或相对都可以),而file直接写程序名就可以了,系统会在PATH自己找对应的路径。

arg就是main函数里的argv[],是程序调用的参数,第一个为程序名,后面为调用的参数名。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid;

    pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        printf("子进程PID: %d\n", getpid());

        // 使用execl执行ls命令
        execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL);
        perror("execl");
        exit(1);
    } else {
        printf("父进程PID: %d, 子进程PID: %d\n", getpid(), pid);
    }

    return 0;
}

除了execl和execlp还有两个常用的。

execv/execvp:

int execv(const char *path,const char *argv[]);

int execvp(const char *file,const char *argv[]);

和上面的一样,只是后面的一长串的东西变成了指针数组,如下:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid;

    pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        printf("子进程PID: %d\n", getpid());

        // 使用execv执行ls命令
        char *args[] = {"/bin/ls", "-a", "-l", "/etc", NULL};
        execv("/bin/ls", args);
        perror("execv");
        exit(1);
    } else {
        printf("父进程PID: %d, 子进程PID: %d\n", getpid(), pid);
    }

    return 0;
}

system:

如果觉得上面的创建过程太麻烦,还有个最简单的函数:

int system(const char *command);

command是你要执行的程序,成功就会返回程序执行的结果(包括输出等),失败返回EOF。

主进程会等commond结束在继续。

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>

int main() {
    int ret = system("ls -l"); // 列出当前目录文件(Linux命令)
    if (ret == -1) {
        printf("无法执行命令\n");
    }
    return 0;
}

进程回收:

前面说过进程可以通过exit函数结束,,对于子进程,其结束后由父进程负责回收,如果父进程先一步结束,那子进程就会变成孤儿进程,被shell的init进程收养,那如果子进程结束后父进程没有马上回收呢?

那它就会变成僵尸进程,pcb等无法释放,直到父进程结束后,被init释放,所以我们父进程要主动去回收子进程。

wait:

在unistd.h头文件的wait函数就是用于回收子进程的:

(!!!!在新的c语言标准中,wait函数改了头文件,在<sys/wait.h>中)

pid_t wait(int *status);

成功会返回子进程的进程号,失败返回EOF。

为了确保子进程能成功回收,父进程会一直在wait函数等待(阻塞)直至子进程结束回收。

若有多个子进程,哪个先结束就会先回收那个。

函数的参数会保存子进程的返回值和结束方式的地址,所以要先创建好一个整型变量,然后传地址给函数。如果不希望接收返回值,就i传递一个NULL,父进程会直接释放子进程。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
        int status;
        pid_t pid;
        if((pid = fork()) < 0){
                perror("fork");
                exit(-1);
        }else if(pid == 0){
                sleep(5);
                exit(2);
        }else{
                wait(&status);
                printf("%x\n",status);
        }
        return 0;
}

进程可通过exit/return等方式结束,属于正常结束,都会返回某个值(0~255),除此之外还有通过信号结束。对于status返回值,c语言也定义了很多的宏,可对其数据进行提取,分析等:

含义
WIFEXITED(status) 为真表示子进程正常调用 exit/_exit 终止
WEXITSTATUS(status) 仅在 WIFEXITED 为真时有效,返回低 8 位退出码
WIFSIGNALED(status) 为真表示子进程被信号杀死(如 kill -9)
WTERMSIG(status) 返回杀它的信号编号

waitpid:

和wait差不多的函数

pid_t waitpid(pid_ pid,int *status,int option);

第一个参数是指定回收的对象,第二个是返回值,第三个是父进程的状态。

成功返回子进程的pid,如果进程还没,还结束就会返回0,失败返回EOF。

option有两个选择,一、0,表示以阻塞态的方式等待到子进程结束,二、WNOHANG,表示不会阻塞,即当前单次查看子进程的状态。

waitpid(-1,&status,0) == wait(&status)。

-1表示父进程的任意子进程

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }

    if (pid == 0) {
        printf("child: my pid=%d, going to sleep 2s\n", getpid());
        sleep(2);
        exit(42);
    }

    int status;
    printf("parent: waiting for child %d ...\n", pid);

    if (waitpid(pid, &status, 0) == -1) {
        perror("waitpid");
        exit(EXIT_FAILURE);
    }

    if (WIFEXITED(status))
        printf("parent: child exited with code %d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("parent: child killed by signal %d\n", WTERMSIG(status));
    else
        printf("parent: child stopped/continued\n");

    return 0;
}

守护进程:

第一课的时候,进程分为三种,守护进程是一种和终端无关,一直在后台执行的进程。在系统启动时运行,系统关闭时结束,不受任何程序运行。Linux中含有大量的守护进程,如ssh,打印服务等,若想写后台服务程序,守护进程就是不二之选。

会话、进程组:

Linux以会话、进程组的方式管理进程。

每个进程属于一个进程组,子进程和父进程在一个进程组里。

会话是一个或多个进程的集合,在平常使用中,当我们打开终端的时候,就会创建一个会话。所有在这个终端的进程都属于这个会话。而终端打开的第一个程序:shell程序,那么他就是这个会话的首进程,也就是会话的组长。

守护进程的创建:

我们用子进程来创建守护进程:

先创建一个子进程,然后关闭父进程,使子进程变成孤儿进程,被init收养(拜托父进程的控制)

通过setsid()函数创建一个新的会话。(摆脱当前的终端,使其成为新的会话的首进程,成为独立的进程)

更改当前的工作目录,使其在一个不会被修改的目录工作。(保护守护进程)

重设文件掩码(保证守护进程创建的文件不会受掩码影响,不让系统影响自身工作)

关闭打开的所有从父进程继承的文件描述符。(使用getdtablesize()函数得到其继承的文件数,用for循环全部关闭)

(因为脱离了终端,使用标准输入输出流都将无法使用,所以使用文件io)

将当前系统时间每隔 1 秒写入 time.log 文件

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    pid_t pid;
    FILE *fp;
    time_t t;
    int i;

    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(-1);
    } else if (pid > 0) {
        exit(0); // 父进程退出
    }

    // 子进程成为守护进程
    setsid();           // 创建新会话
    umask(0);           // 清除文件权限掩码
    chdir("/tmp");      // 更改工作目录

    // 关闭所有打开的文件描述符
    for (i = 0; i < getdtablesize(); i++) {
        close(i);
    }

    // 打开日志文件
    if ((fp = fopen("time.log", "a")) == NULL) {
        perror("fopen");
        exit(-1);
    }

    // 守护进程主循环
    while (1) {
        time(&t);
        fprintf(fp, "%s", ctime(&t));
        fflush(fp);
        sleep(1);
    }

    fclose(fp);
    return 0;
}
相关推荐
会飞的土拨鼠呀1 小时前
运维工程师需要具备哪些技能
linux·运维·ubuntu
立志成为大牛的小牛1 小时前
数据结构——四十九、B树的删除与插入
数据结构·学习·程序人生·考研·算法
IT方大同2 小时前
C语言的组成部分
c语言·开发语言
用户043543771952 小时前
C语言:数组入门及其基础算法详解
c语言
say_fall2 小时前
WinAPI 极简教程:超简单的 Windows 接口入门
c语言·windows
北顾南栀倾寒2 小时前
[杂学笔记]C++编译过程、静态链接库与动态链接库的区别、动态多态的实现机制、虚拟地址空间分布与C++内存分布、volatile的作用以及使用场景
笔记
虎头金猫3 小时前
随时随地处理图片文档!Reubah 加cpolar的实用体验
linux·运维·人工智能·python·docker·开源·visual studio
NiKo_W3 小时前
Linux 数据链路层
linux·服务器·网络·内网穿透·nat·数据链路层
dessler4 小时前
MYSQL-物理备份(xtrabackup)使用指南
linux·数据库·mysql