一、虚拟内存与物理映射


二、PCB
PCB进程控制块:
进程id
文件描述符表
进程状态:初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码
信号相关信息资源。
用户id和组id
三、fork、getpid、getppid

//子进程创建成功返回0,父进程返回子进程pid
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
printf("before fork-1-\n");
printf("before fork-2-\n");
printf("before fork-3-\n");
printf("before fork-4-\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
}
else if (pid == 0) {
printf("---child is created\n");
}
else if (pid > 0)
{
printf("---parent process: my child is %d\n", pid);
}
printf("-----------------end of file\n");
return 0;
}
//运行结果如下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
printf("before fork-1-\n");
printf("before fork-2-\n");
printf("before fork-3-\n");
printf("before fork-4-\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
}
else if (pid == 0) {
printf("---child is created, pid = %d, parent-pid:%d\n", getpid(), getppid());
}
else if (pid > 0)
{
printf("---parent process: my child is %d, my pid:%d, my parent pid:%d\n", pid, getpid(), getppid());
}
printf("-----------------end of file\n");
return 0;
}
//运行结果如下
//7876是bash进程

四、循环创建多个子进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
for (i = 0; i < 5; i++) {
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
}
else if (pid == 0) {
printf("---child is created, pid = %d, parent-pid:%d\n", getpid(), getppid());
}
else if (pid > 0)
{
printf("---parent process: my child is %d, my pid:%d, my parent pid:%d\n", pid, getpid(), getppid());
}
}
return 0;
}
//i=2子进程如下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
pid_t pid;
for (i = 0; i < 5; i++) {
if (fork() == 0) {
break;
}
}
if (i == 5) {
printf("I'm parent\n");
}
else {
printf("I'm %dth child\n", i + 1);
}
return 0;
}
//出现下面这种情况是因为bash进程优先抢到CPU,子进程没抢到

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
pid_t pid;
for (i = 0; i < 5; i++) {
if (fork() == 0) {
break;
}
}
if (i == 5) {
sleep(5);
printf("I'm parent\n");
}
else {
sleep(i);
printf("I'm %dth child\n", i + 1);
}
return 0;
}
//这样保证了子进程按顺序执行,父进程也是放到最后一个

fork函数:
父进程返回子进程pid。子进程返回0.
父子进程相同:
刚fork后。data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
父子进程不同:
进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集
父子进程共享:
读时共享、写时复制。----------- 全局变量。
1.文件描述符 2.mmap映射区。
五、父子进程gdb调试
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
pid_t pid;
for (i = 0; i < 5; i++) {
if (fork() == 0) {
break;
}
}
if (i == 5) {
sleep(5);
printf("I'm parent\n");
}
else {
sleep(i);
printf("I'm %dth child\n", i + 1);
}
return 0;
}
//这样保证了子进程按顺序执行,父进程也是放到最后一个
//调试子进程
gdb test(上述代码文件名)
list 1
l
b 12(在for循环处创建断点)
r(运行)
n(next)
set follow-fork-mode child(切换到子进程)
n
n

//调试父进程
gdb test
l 1
l
b 12
r
set follow-fork-mode parent
n
n
n
n
n
//多个n创建5个子进程

六、exec

exec所干的事就是将exec函数里的内容覆盖子进程原来fork的内容。
七、execlp和execl
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int main(int argc, char *argv[]){
pid_t pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}
else if(pid == 0){ //子进程
execlp("ls", "ls", "-l", "-h", NULL); //第一个逗号之后的ls是argv[0]
execlp("date", "date", NULL);
execl("./a.out", "./a.out", NULL); //执行相对路径下的a.out文件
perror("exec error"); //如果execl或execlp出错就会执行以下内容
exit(1); //因为execl或execlp执行成功没有返回值,不返回
}
else if(pid > 0){
sleep(1);
printf("I'm parent: %d\n", getpid());
}
return 0;
}
//子进程会做出ls、date命令和执行a.out文件,下图只展示一个功能

八、孤儿进程和僵尸进程
//孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程被init进程领养。
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void){
pid_t pid;
pid = fork();
if(pid == 0){
while(1){
printf("I'm child, my parent pid = %d\n", getppid());
sleep(1);
}
}
else if(pid > 0){
printf("I'm parent, my pid = %d\n", getpid());
sleep(9);
printf("-----------parent is going to die------------\n");
}
else{
perror("fork");
return 1;
}
return 0;
}
//如下图,当父进程执行完退出后子进程变为孤儿进程,他的父进程变为pid为1721 init父进程
//用两次ps ajx命令可以查看子进程前后父进程pid的变化

//僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void){
pid_t pid;
pid = fork();
if(pid == 0){
printf("child, my parent = %d, go to sleep 10s\n", getppid());
sleep(10);
printf("----child die----\n");
}
else if(pid > 0){
while(1){
printf("I'm parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
}
else{
perror("fork");
return 1;
}
return 0;
}
//当子进程结束后,子进程变成僵尸进程,只需将父进程kill就解决这个问题
//kill -9 3464(父进程pid)

九、wait回收子进程
wait函数:回收子进程退出资源
作用1:阻塞等待子进程退出
作用2:清理子进程残留在内核的pcb资源
作用3:通过传出参数,得到子进程结束状态
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void){
pid_t pid, wpid;
int status;
pid = fork();
if(pid == 0){
printf("child, my id = %d, go to sleep 10s\n", getppid());
sleep(10);
printf("---------child die---------\n");
}
else if(pid > 0){
wpid = wait(&status); //status是个传出参数,将子进程状态传出来
if(wpid == -1){
perror("wait error");
exit(1);
}
printf("---------parent wait finish: %d\n", wpid);
}
else{
perror("fork");
return 1;
}
return 0;
}

十、子进程退出和终止
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void){
pid_t pid, wpid;
int status;
pid = fork();
if(pid == 0){
printf("---child, my id = %d, go to sleep 10s\n", getpid());
sleep(10);
printf("---child die---\n");
return 73;
}
else if(pid > 0){
//wpid = wait(NULL) //不关心子进程结束原因
wpid = wait(&status); //如果子进程未终止,父进程阻塞(等待)在这个函数上
if (wpid == -1){
perror("wait error");
exit(1);
}
if (WIFEXITED(status)){ //为真,说明子进程正常终止
printf("child exit with %d\n", WEXITSTATUS(status));
}
if (WIFSIGNALED(status)){ //为真,说明子进程是被信号终止
printf("child kill with signal %d\n", WTERMSIG(status));
}
printf("parent wait finish: %d\n", wpid);
}
else{
perror("fork");
return 1;
}
return 0;
}
//下图第一个和第三个案例是被信号(9,11)终止,第二个是正常超时退出(退出值73)

十一、waitpid回收子进程
waitpid函数:
pid_t waitpid(pid_t pid, int *status, int options)
参数:
pid:指定回收的子进程pid, > 0:待回收的子进程pid, -1:任意子进程, 0:同组的子进程。
status:(传出)回收进程的状态。
options:WNOHANG指定回收方式为非阻塞。
返回值:
> 0:表示成功回收的子进程pid
0:函数调用时,参数3指定了 WNOHANG,并且,没有子进程结束。
-1:失败。errno
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
if (fork() == 0) {
break;
}
}
if (i == 5) {
//sleep(5);
wait(NULL); //一次wait/waitpit函数调用,只能回收一个子进程
wpid = waitpid(-1, NULL, WNOHANG); //-1回收任意子进程,相当于wait
if(wpid == -1){
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish: %d\n", wpid);
}
else {
sleep(i);
printf("I'm %dth child\n", i + 1);
}
return 0;
}
//由于父进程没有sleep,父进程先运行,但此时没有子进程运行,所以没有回收子进程
//WNOHANG表示非阻塞,没回收就直接返回0

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { //是子进程,退出
break;
}
if(i == 2){ //没有被break就是父进程,pid是子进程的pid
tmpid = pid;
printf("pid = %d\n", tmpid);
}
}
if (i == 5) { //父进程
//sleep(5);
//wpid = waitpid(tmpid, NULL, WNOHANG); //指定一个进程回收,不阻塞
wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收,阻塞
if(wpid == -1){
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish: %d\n", wpid);
}
else { //子进程
sleep(i);
printf("I'm %dth child, pid = %d\n", i + 1, getpid());
}
return 0;
}
//有两种指定回收子进程方式,第一种是sleep+WNOHANG非阻塞,第二种是用参数0来阻塞
//第一张图是sleep的,等子进程全都退出父进程再回收
//第二张图是阻塞的,等待第三个子进程执行完立马回收


十二、waitpid回收多个子进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>
int main(int argc, char* argv[]) {
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { //是子进程,退出
break;
}
}
if (i == 5) { //父进程
//第二个参数NULL表示不关心子进程状态,0表示阻塞
/*while((wpid = waitpid(-1, NULL, 0))){
printf("wait child:%d\n", wpid);
}*/
//非阻塞方式回收
while((wpid = waitpid(-1, NULL, WNOHANG)) != -1){
if(wpid > 0){
printf("wait child:%d\n", wpid);
}
else if(wpid == 0){
sleep(1);
continue;
}
}
printf("I'm parent, wait a child finish: %d\n", wpid);
}
else { //子进程
sleep(i);
printf("I'm %dth child, pid = %d\n", i + 1, getpid());
}
return 0;
}
十三、进程间通信常见方式
英文简称:IPC,InterProcess Communication
四种进程间通信方式:
1.管道(使用最简单,得有血缘关系,如父子关系)
2.信号(开销最小)
3.共享映射区(可应用于无血缘关系的进程间)
4.本地套接字(最稳定,较复杂,一般用于网络)

十四、管道通信特性
管道:
实现原理:内核借助环形队列机制,使用内核缓冲区实现。
特质:
1.伪文件(文件占用磁盘空间,伪文件占用内存空间)
2.管道中的数据只能一次读取。
3.数据在管道中,只能单向流动。
局限性:
1.自己写,不能自己读。
2.数据不可以反复读。
3.半双工通信。
4.血缘关系进程间可用。
十五、管道函数pipe
pipe函数:创建,并打开管道。
int pipe(int fd[2]);
参数:
fd[0]:读端。
fd[1]:写端。
返回值:
成功:0
失败:-1 errno
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int ret;
int fd[2];
pid_t pid;
char *str ="hello pipe\n";
char buf[1024];
ret = pipe(fd);
if (ret ==-1){
sys_err("pipe error");
}
pid = fork();
if (pid> 0){
close(fd[0]); // 关闭读端
write(fd[1], str, strlen(str));
sleep(1); //保证父进程不先退出
close(fd[1]);
}
else if (pid== 0){
close(fd[1]); //子进程关闭写端
ret = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret); //输出到屏幕
close(fd[0];
}
return 0;
}

十六、管道读写行为
读管道:
1.管道中有数据,read返回实际读到的字节数。
2.管道中无数据:
(1)管道写端被全部关闭,read返回0(类似读到文件结尾)。
(2)写端没有全部被关闭,read阻塞等待(不久的将来可能有数据到达)
写管道:
1.管道读端全部被关闭,进程异常终止(SIGPIPE信号导致的)
2.管道读端没有全部关闭:
(1)管道已满,write阻塞等待。
(2)管道未满,write将数据写入,并返回实际写入的字节数。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int ret;
int fd[2];
pid_t pid;
char *str ="hello pipe\n";
char buf[1024];
ret = pipe(fd);
if (ret ==-1){
sys_err("pipe error");
}
pid = fork();
if (pid> 0){
close(fd[0]); // 关闭读端
sleep(3);
write(fd[1], str, strlen(str));
close(fd[1]);
}
else if (pid== 0){
close(fd[1]); //子进程关闭写端
ret = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret); //输出到屏幕
close(fd[0];
}
return 0;
}
//等待3s父进程写数据
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int ret;
int fd[2];
pid_t pid;
char *str ="hello pipe\n";
char buf[1024];
ret = pipe(fd);
if (ret ==-1){
sys_err("pipe error");
}
pid = fork();
if (pid> 0){
close(fd[0]); // 关闭读端
sleep(3);
//write(fd[1], str, strlen(str));
close(fd[1]);
}
else if (pid== 0){
close(fd[1]); //子进程关闭写端
ret = read(fd[0], buf, sizeof(buf));
printf("child read ret = %d\n", ret);
write(STDOUT_FILENO, buf, ret); //输出到屏幕
close(fd[0];
}
return 0;
}
//父进程未写入数据,返回0

十七、用管道实现ls | wc -l
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
void sys_err(const char* str)
{
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd[2];
int ret;
pid_t pid;
ret = pipe(fd);
if (ret == -1) {
sys_err("pipe error");
}
pid = fork();
if (pid == -1) {
sys_err("fork error");
}
else if (pid > 0) {
//父进程来读,这样就不会先于子进程执行,因为会阻塞等待子进程写入
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL); //执行成功就不回来了
sys_err("execlp wc error");
}
else if (pid == 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", NULL);
sys_err("execlp ls error");
}
return 0;
}
//这个程序可以输出当前目录文件个数(ls | wc -l)
//注意 | 是管道符
十八、兄弟进程实现ls | wc -l
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
void sys_err(const char* str)
{
perror(str);
exit(1);
}
int main(int argc, char* argv[]) {
int fd[2];
int ret, i;
pid_t pid;
ret = pipe(fd);
if (ret == -1) {
sys_err("pipe error");
}
for (int i = 0; i < 2; i++) {
pid = fork();
if (pid == -1) {
sys_err("fork error");
}
if (pid == 0) {
break;
}
}
if (i == 2) {
//父子进程都公用一个管道,必须保证管道单向流动,关闭父进程读写端
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);
}
else if (i == 0) { //兄长进程
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", NULL);
sys_err("execlp ls error");
}
else if (i == 1) { //小弟进程
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
sys_err("execlp wc error");
}
return 0;
}
十九、管道的一写端多读端
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
#include<stdlib.h>
int main(void) {
pid_t pid;
int fd[2], i, n;
char buf[1024];
int ret = pipe(fd);
if (ret == -1) {
perror("pipe error");
exit(1);
}
for (i = 0; i < 2; i++) {
if ((pid = fork()) == 0) {
break;
}
else if (pid == -1) {
perror("pipe error");
exit(1);
}
}
if (i == 0) {
close(fd[0]);
write(fd[1], "1.hello\n", strlen("1.hello\n"));
}
else if (i == 1) {
close(fd[0]);
write(fd[1], "2.world\n", strlen("2.world\n"));
}
else {
close(fd[1]); //父进程关闭写端,留读端读取数据
sleep(1); //保证两个子进程都写完再回收
n = read(fd[0], buf, 1024); //从管道中读数据
write(STDOUT_FILENO, buf, n);
for (i = 0; i < 2; i++) {
wait(NULL); //两个儿子wait两次
}
}
return 0;
}
//这个程序父进程读,两个子进程写,由于按循环顺序i==0的子进程概率先执行,所以打印hello world
//如果想保证顺序,两个子进程用sleep来控制先后

二十、命名管道fifo创建
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>
void sys_err(const char* str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int ret = mkfifo("myfifo", 0644); //创建管道myfifo rw-rw-r--
if (ret == -1) {
sys_err("mkfifo error");
}
return 0;
}