2.linux系统编程之进程

linux系统编程之进程

1. 进程基础概念

进程是Linux系统中最基本的执行单元,它是程序的一次执行实例。每个进程都有自己的地址空间、文件描述符、环境变量和程序计数器等资源。

进程特点:

  • 独立性:每个进程有独立的地址空间
  • 动态性:进程有创建、运行、终止等生命周期
  • 并发性:多个进程可以并发执行
  • 结构性:进程由PCB(进程控制块)描述

2. 进程创建

fork()系统调用

#include <unistd.h>

pid_t fork(void);
  • 功能:创建子进程
  • 返回值:
    • 父进程中返回子进程的PID
    • 子进程中返回0
    • 出错返回-1

示例代码:

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        printf("This is child process (PID: %d)\n", getpid());
    } else {
        printf("This is parent process (PID: %d), child PID: %d\n", 
               getpid(), pid);
    }
    
    return 0;
}

vfork()系统调用

#include <unistd.h>

pid_t vfork(void);
  • 与fork()类似,但子进程与父进程共享地址空间
  • 子进程必须先调用exec()或_exit(),否则会导致未定义行为
  • 通常用于创建后立即执行exec的场景

3. 进程终止

正常终止方式:

  1. 从main函数返回
  2. 调用exit()
  3. 调用_exit()或_Exit()
  4. 最后一个线程从其启动例程返回
  5. 最后一个线程调用pthread_exit()

相关函数:

#include <stdlib.h>
void exit(int status);

#include <unistd.h>
void _exit(int status);

区别:

  • exit()会执行清理操作(如调用atexit注册的函数、刷新缓冲区等)
  • _exit()直接终止进程,不做清理

4. 进程等待

wait()系统调用

#include <sys/wait.h>

pid_t wait(int *status);
  • 功能:等待任意子进程终止
  • 参数:status保存子进程退出状态
  • 返回值:成功返回终止子进程的PID,失败返回-1

waitpid()系统调用

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • 功能:等待指定子进程终止

  • 参数:

    • pid:指定等待的进程ID

      • 0:等待特定子进程

      • -1:等待任意子进程(同wait)

      • 0:等待与调用进程同组ID的任何子进程

      • <-1:等待组ID等于pid绝对值的任何子进程

    • options:选项

      • WNOHANG:非阻塞模式
      • WUNTRACED:报告已停止的子进程
  • 返回值:同wait()

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        printf("Child process (PID: %d) running\n", getpid());
        sleep(2);
        printf("Child process exiting\n");
        exit(123);
    } else {
        printf("Parent process (PID: %d) waiting for child\n", getpid());
        int status;
        waitpid(pid, &status, 0);
        
        if (WIFEXITED(status)) {
            printf("Child exited with status: %d\n", WEXITSTATUS(status));
        } else {
            printf("Child terminated abnormally\n");
        }
    }
    
    return 0;
}

5. 进程替换

exec函数族

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) 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:自定义环境变量

示例代码:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Before exec\n");
    
    // 替换为ls命令
    execlp("ls", "ls", "-l", NULL);
    
    // 只有exec失败才会执行到这里
    perror("exec failed");
    return 1;
}

6. 进程间通信(IPC)

主要IPC机制:

  1. 管道(pipe)
  2. 命名管道(FIFO)
  3. 消息队列
  4. 共享内存
  5. 信号量
  6. 信号
  7. 套接字(socket)

6.1. 管道(Pipe)

无名管道示例
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    /* code */
    int fd[2];
    if(pipe(fd) == -1){
        perror("pipe failed");
        return -1;
    }
    pid_t pid = fork();

    if(pid < 0){
        perror("fork failed");
        return -1;
    }else if(pid == 0){
        //关闭写端
        close(fd[1]);
        char buf[100];
        int n = read(fd[0],buf,sizeof(buf));
        printf("Child received: %.*s \n",n,buf);
        close(fd[0]);
    }else{
        //关闭读端
        close(fd[0]);
        const char *msg = "Hello from parent";
        write(fd[1],msg, strlen(msg));
        close(fd[1]);
        wait(NULL);
    }
    return 0;
}
命名管道(FIFO)示例

写入端:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>


int main(int argc, char const *argv[])
{
    /* code */
    const char *fifo = "/tmp/myfifo";
    //创建FIFO
    mkfifo(fifo,0666);
    //打开FIFO
    int fd = open(fifo,O_WRONLY);
    if(fd == -1){
        perror("fifo open failed");
        return -1;
    }
    write(fd,"Hello FIFO",10);
    close(fd);
    return 0;
}

读取端:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    /* code */
    const char *fifo = "/tmp/myfifo";
    char buf[256];
    int fd = open(fifo,O_RDONLY);
    
    if(fd == -1){
        perror("fifo open failed");
        return -1;
    }
    int size = read(fd, buf, sizeof(buf));
    printf("FIFO Read : %.*s \n", size, buf);
    close(fd);
    return 0;
}

2. 消息队列(Message Queue)

#include <stdio.h>
#include <sys/msg.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

struct msgbuf
{
    long mtype;
    char mtext[100];
};

int main(int argc, char const *argv[])
{
    /* code */
    int msgid;
    struct msgbuf msg;
    msgid = msgget(IPC_PRIVATE, 0666 |IPC_CREAT);

    pid_t pid = fork();

    if(pid < 0){
        perror("fork failed");
        return -1;
    }else if( pid == 0){
        msg.mtype = 1;
        strcpy(msg.mtext,"hello my parent, i pass Message Queue");
        msgsnd(msgid,&msg,sizeof(msg),0);
    }else {
        msgrcv(msgid,&msg,sizeof(msg),1,0);

        printf("parent rev data: %s \n",msg.mtext);
        //删除消息队列
        msgctl(msgid,IPC_RMID,NULL);
        wait(NULL);
    }
    return 0;
}

3. 共享内存(Shared Memory)

#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    /* code */
    int shmid;
    char *shm;

    //创建共享内存
    shmid = shmget(IPC_PRIVATE, 1024, 0666| IPC_CREAT);
    shm = shmat(shmid, NULL, 0);

    pid_t pid = fork();
    if(pid < 0){
        perror("fork failed");
        return -1;
    }else if(pid == 0){
        strcpy(shm,"hello my share memory parent");
    }else {
        wait(NULL);

        printf("Parent read: %s \n",shm);

        shmdt(shm);
        shmctl(shmid , IPC_RMID , NULL);
    }

    return 0;
}

4. 信号量(Semaphore)

#include <stdio.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>

// 联合体用于semctl
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main(int argc, char const *argv[])
{
    
    int semid;
    union semun arg;
    struct sembuf sb = {0, -1, 0};//P操作

    //创建信号量
    semid = semget(IPC_PRIVATE,1, 0666|IPC_CREAT);
    arg.val= 1;//初始值为1
    semctl(semid,0,SETVAL,arg);

    pid_t pid = fork();

    if(pid < 0){
        perror("fork failed");
        return -1;
    }else if (pid == 0) { // 子进程
        printf("Child waiting for semaphore...\n");
        semop(semid, &sb, 1); // P操作
        printf("Child entered critical section\n");
        sleep(2);
        printf("Child leaving critical section\n");
        sb.sem_op = 1; // V操作
        semop(semid, &sb, 1);
    } else { // 父进程
        sleep(1); // 让子进程先运行
        printf("Parent waiting for semaphore...\n");
        semop(semid, &sb, 1); // P操作
        printf("Parent entered critical section\n");
        sleep(2);
        printf("Parent leaving critical section\n");
        sb.sem_op = 1; // V操作
        semop(semid, &sb, 1);
        
        wait(NULL);
        semctl(semid, 0, IPC_RMID); // 删除信号量
    }
    return 0;
}

5. 套接字(Socket)

本地套接字示例

服务器端:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>

#define SOCK_PATH "/tmp/mysocket"

int main() {
    int server_fd, client_fd;
    struct sockaddr_un server_addr, client_addr;
    socklen_t client_len;
    char buf[256];
    
    // 创建套接字
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    // 绑定地址
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCK_PATH);
    unlink(SOCK_PATH); // 确保路径可用
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    // 监听
    listen(server_fd, 5);
    
    // 接受连接
    client_len = sizeof(client_addr);
    client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    
    // 读取数据
    int n = read(client_fd, buf, sizeof(buf));
    printf("Server received: %.*s\n", n, buf);
    
    // 关闭
    close(client_fd);
    close(server_fd);
    unlink(SOCK_PATH);
    
    return 0;
}

客户端:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>

#define SOCK_PATH "/tmp/mysocket"

int main() {
    int sock_fd;
    struct sockaddr_un addr;
    
    // 创建套接字
    sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    // 连接服务器
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCK_PATH);
    connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
    
    // 发送数据
    write(sock_fd, "Hello Socket", 13);
    
    // 关闭
    close(sock_fd);
    
    return 0;
}

总结比较

IPC方式 特点 适用场景
管道 单向通信,有亲缘关系要求 父子进程简单通信
命名管道 可用于无亲缘关系进程 任意进程间通信
消息队列 消息边界保持,可指定消息类型 需要区分消息类型的场景
共享内存 最高效,但需要同步机制 大数据量频繁通信
信号量 主要用于进程同步 临界资源保护
套接字 最通用,支持跨网络通信 复杂通信需求,包括网络通信

7. 守护进程(Daemon)

守护进程是在后台运行的特殊进程,通常由系统启动时加载,独立于控制终端。

创建守护进程步骤:

  1. 调用fork()创建子进程,父进程退出
  2. 子进程调用setsid()创建新会话
  3. 改变当前工作目录到根目录
  4. 重设文件权限掩码
  5. 关闭不需要的文件描述符
  6. 处理SIGCHLD信号

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

void daemonize(){
    pid_t pid = fork();
    if(pid < 0){
        perror("fork failed");
        exit(-1);
    }else if(pid > 0){
        exit(0);
    }
    //子进程继续执行
    setsid();//创建新的会话
    chdir("/");//改变工作目录
    umask(0);//重设文件权限掩码

    //关闭标准文件描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    //忽略SIGCHLD信号
    signal(SIGCHLD, SIG_IGN);

}

int main(int argc, char const *argv[])
{
    daemonize();
    //守护进程实际的工作
    while (1)
    {
        //例如定期执行的某些任务
        printf("update \n");
        sleep(10);
    }
    return 0;
}

8. 进程状态监控

/proc文件系统

Linux通过/proc虚拟文件系统提供进程信息,每个进程对应/proc/[pid]目录。

常用信息:

  • /proc/[pid]/status:进程状态
  • /proc/[pid]/cmdline:启动命令
  • /proc/[pid]/fd/:打开的文件描述符
  • /proc/[pid]/stat:详细的进程状态信息

系统调用

  • getpid():获取当前进程ID
  • getppid():获取父进程ID
  • getpgid():获取进程组ID
  • getsid():获取会话ID

9. 进程资源限制

getrlimit/setrlimit

#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);

常用resource参数:

  • RLIMIT_CPU:CPU时间(秒)
  • RLIMIT_DATA:数据段大小
  • RLIMIT_STACK:栈大小
  • RLIMIT_NOFILE:文件描述符数量
  • RLIMIT_AS:虚拟内存大小

示例代码:

#include <stdio.h>
#include <sys/resource.h>

int main() {
    struct rlimit lim;
    
    // 获取文件描述符限制
    getrlimit(RLIMIT_NOFILE, &lim);
    printf("Current file descriptor limit: soft=%ld, hard=%ld\n",
           (long)lim.rlim_cur, (long)lim.rlim_max);
    
    // 设置新的限制
    lim.rlim_cur = 1024;
    setrlimit(RLIMIT_NOFILE, &lim);
    
    return 0;
}

10. 进程调度

nice()系统调用

#include <unistd.h>

int nice(int inc);
  • 功能:调整进程优先级
  • 参数:inc为优先级增量(正值降低优先级,负值提高优先级)
  • 返回值:新的nice值(-1可能表示错误)

sched_setscheduler()系统调用

#include <sched.h>

int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);

调度策略:

  • SCHED_FIFO:先进先出实时调度
  • SCHED_RR:轮转实时调度
  • SCHED_OTHER:普通分时调度

示例代码:

#include <stdio.h>
#include <sched.h>

int main() {
    struct sched_param param;
    param.sched_priority = 10;  // 设置优先级
    
    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler failed");
        return 1;
    }
    
    printf("Scheduler set to SCHED_FIFO with priority %d\n", param.sched_priority);
    return 0;
}

Linux进程管理是系统编程的核心内容,理解这些概念和系统调用对于开发高效、稳定的Linux应用程序至关重要。

posted @ 2025-06-27 20:08  站着说话不腰疼  阅读(25)  评论(0)    收藏  举报