前言:Linux系统编程的基础系列文章,随着不断学习会将一些知识点进行更新,前期主要是简单了解和学习。
进程
进程运行状态
进程原语
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族
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;
}
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[]);
事实上,只有execve是真正的系统调用,其他五个函数都调用execv。
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)僵尸状态
#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