linux系统编程-2、进程

前言:Linux系统编程的基础系列文章,随着不断学习会将一些知识点进行更新,前期主要是简单了解和学习。

进程

进程运行状态

进程原语

fork

1、进程原语-fork

读时共享写时复制
只有进程空间的各段的内容要发生变化时(子进程或父进程进行写操作时,都会引起复制),才会将父进程的内容复制一份给子进程。

  • 在fork之后两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。

  • 即父子进程在逻辑上仍然是严格相互独立的两个进程,各自维护各自的参数,只是在物理上实现了读时共享,写时复制。

  • fork函数创建子进程后,内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间。

  • 直到父子进程中有更改相应段(用户空间中)的行为发生时,再为子进程相应的段分配物理空间。

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

int main(void) {
    pid_t pid;///< 调用一次返回两次,在父进程返回子进程的PID,在子进程返回0

    pid = fork();

    if (pid > 0) {
        printf("I am parent\n");
        while(1);
    }
    else if (pid ==0 ) {
        printf("I am child\n");
        while(1);
    }
    else {
        perror("fork");
        exit(1);
    }
}

调用命令查看当前运行进程

ps aux
kudio     10073  100  0.0   4352   652 pts/19   R+   18:06   0:13 ./fork-test
kudio     10074  100  0.0   4352    76 pts/19   R+   18:06   0:13 ./fork-test

可以发现两个进程均处于运行态,并且PID号相邻

进程相关函数
#include <unistd.h>
#include <sys/types.h>

pid_t getpid(void);///< 返回调用进程的 PID号
pid_t getppid(void);///< 返回调用进程父进程的 PID号

uid_t getuid(void);///< 返回实际用户 ID
uid_t getuid(void);///< 返回有效用户 ID

gid_t getgid(void);///< 返回实际用户组 ID
gid_t getgid(void);///< 返回有效用户组 ID

exec族

2、进程原语-exec
execl
#include <stdio.h>
#include <unistd.h>

int main(void) {
    printf("hello\n");
    execl("/bin/ls", "ls", "-l", NULL);///< 进程代码段已经被 ls覆盖,并且是从 ls退出的
    printf("world\n");

    return 0;
}

通过以下代码进行测试:

#include <stdio.h>

int main(int argc, char *argv[]) {
    int i = 0;
    while (i < argc) {
        printf("%s\n", argv[i++]);
    }
    return 2;
}
gcc -g -Wall exec-child.c -o exec-child
#include <stdio.h>
#include <unistd.h>

int main(void) {
    printf("hello\n");
    execl("./exec-child", "./exec-child", "zhouyi", "kudio",  NULL);
    printf("world\n");

    return 0;
}
gcc -g -Wall exec-test.c -o exec-test

运行 ./exec-test, 得到结果如下:

hello
./exec-child
zhouyi
kudio

通过以下命令查看退出值

echo $?

可发现退出值为 2

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

int main(void) {
    pid_t pid;

    pid = fork();
    if (pid == 0) {
        execl("/usr/bin/firefox", "firefox", "www.baidu.com", NULL);
    }
    else if (pid > 0) {
        while (1) {
            printf("I am parent.\n");
            sleep(1);
        }
    }
    else {
        perror("fork");
        exit(1);
    }
    return 0;
}
3、进程原语-exec-1

exec族区别

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
  • 带有字母l(表示list)的exec函数

    • 要求将新程序的每个命令行参数都当作一个参数传给它
    • 命令行参数的个数是可变的,因此函数原型中有…,…中的最后一个可变参数应该是NULL,起sentinel的作用。
  • 带字母p(表示path)的exec函数

    • 第一个参数必须是程序的相对路径或绝对路径
  • 对于以e(表示environment)结尾的exec函数

    • 可以把一份新的环境变量表传给它。
  • 对于带有字母v(表示vector)的函数

    • 应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。

事实上,只有execve是真正的系统调用,其他五个函数都调用execv。

4、进程原语-exec族之间的关系

wait/waitpid

#include <sys/types.h> 
#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options); 
```C

一个进程释放需要释放两部分
* 用户空间代码释放
* PCB需要父进程回收释放

```C{.line-numbers}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    int n = 10;
    pid_t pid;///< 调用一次返回两次,在父进程返回子进程的PID,在子进程返回0

    pid = fork();

    if (pid > 0) {

        while (1) {
            printf("I am parent: %d.\n", n++);
            printf("my pid = %d, my parent pid = %d.\n", getpid(), getppid());
            sleep(2);
        }
    }
    else if (pid == 0 ) {
            printf("I am child: %d.\n", n++);
            printf("my pid = %d, my parent pid = %d.\n", getpid(), getppid());
            sleep(4);
            return 0;
        }
    }
    else {
        perror("fork");
        exit(1);
    }
    return 0;
}
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
kudio      3424  0.0  0.0   4352   672 pts/2    S+   20:11   0:00 ./zombie-process
kudio      3425  0.0  0.0      0     0 pts/2    Z+   20:11   0:00 [zombie-process] <defunct>

可以发现父进程处于S+(Sleep)睡眠状态,而子进程处于Z+(Zombie)僵尸状态

  • return 0 的时候只是释放了用户空间代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t pid, pid_child;
    pid = fork();

    if (pid > 0) {
        while (1) {
            printf("I am parent, my id is %d.\n", getpid());
            pid_child = wait(NULL);///< 回收僵尸子进程的 PCB
            printf("wait for child %d\n", pid_child);        
            sleep(2);
        }
    }
    else if (pid == 0 ) {
            printf("I am child, my id is %d.\n", getpid());
            sleep(4);
            return 0;
    }
    else {
        perror("fork");
        exit(1);
    }
    return 0;
}

通过 ps aux进行查看

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
kudio      4040  0.0  0.0   4352   784 pts/2    S+   23:44   0:00 ./wait-test
kudio      4041  0.0  0.0   4352    76 pts/2    S+   23:44   0:00 ./wait-test

再次通过 ps aux进行查看

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
kudio      4040  0.0  0.0   4352   784 pts/2    S+   23:44   0:00 ./wait-test

运行后得到如下:

I am parent, my id is 4040.
I am child, my id is 4041.
wait for child 4041
I am parent, my id is 4040.
wait for child -1

可以发现wait函数是个阻塞函数,父进程一直在等待回收子进程
如果没有子进程,则立即返回 -1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值