文章目录
进程控制
1. fork后内核做什么?
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构拷贝子进程
- 将子进程添加到系统进程列表中
- fork返回开始调度器调度
2. fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过了上限
3. 进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
4. 查看进程退出码
echo $? 只会保留最近一次执行的进程的退出码
5. 进程退出常用方法
- main函数中return返回
- 调用exit()函数
- 调用_exit()函数
6. exit()函数和_exit()函数的区别
- exit()函数执行用户定义的清理函数
- 关闭所有打开的流,所有缓存器均被写入
- 调用_exit()函数
7. 进程的等待必要性
- 僵尸进程可能会造成内存泄漏
- 回收子进程资源,获取子进程退出信息
8. 进程等待方法
- wait()函数方法:成功返回等待进程PID,失败则返回-1;参数不关心则可以设置NULL
- waitpid()函数方法:成功返回子进程PID,如果设置了WNOHANG,而调用中waitpid已经收集返回0,调用出错返回-1;参数:PID=-1等待任一一个子进程,PID>0等待其进程PID与子进程相等的PID;status: WIFEXITED:查看进程是否退出,WEXITSTATUS:查看进程退出码:options:若指定PID的子进程没结束,返回0;结束返回子进程PID
- status:是int*类型,指向的是一个int大小的空间,32个比特位,其中从右向左数,第7-15这八个比特位是退出状态,第0-6这7个比特位是终止信号,第7这个比特位是core dump标志位
怎么获取进程的退出码和终止信号呢?
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 0;
while(1)
{
if(count == 5)
{
break;
}
printf("我是子进程,PID:%d,PPPID:%d\n", getpid(), getppid());
count++;
sleep(1);
}
exit(2);
}
//父进程
int status = 0;
pid_t father_id = waitpid(id, &status, 0);
printf("我是父进程,PID:%d,PPID:%d,father_id:%d,子进程退出码:%d,子进程终止信号:%d\n", \
getpid(), getppid(), father_id, (status >> 8) & 0xFF, status & 0x7F);
return 0;
}
父进程在wait的时候,如果子进程没有退出,父进程是在干什么呢?
在子进程没有的时候,父进程只有一直在调用waitpid进程等待,这种等待就是阻塞等待。
如果父进程不想在waitpid处卡住呢,而是去做做别的事情呢,这时怎么来解决这个问题?
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 0;
while(1)
{
if(count == 5)
{
break;
}
printf("我是子进程,PID:%d,PPPID:%d\n", getpid(), getppid());
count++;
sleep(1);
}
exit(2);
}
//父进程
while(1)
{
int status = 0;
pid_t father_id = waitpid(id, &status, WNOHANG);
if(father_id < 0)
{
printf("err\n");
exit(-1);
}
else if(father_id == 0)
{
printf("子进程没有退出\n");
sleep(1);
continue;
}
else
{
printf("我是父进程,PID:%d,PPID:%d,father_id:%d,子进程退出码:%d,子进程终止信号:%d\n", \
getpid(), getppid(), father_id, (status >> 8) & 0xFF, status & 0x7F);
break;
}
return 0;
}
}
也可以用宏来获取退出码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 0;
while(1)
{
if(count == 5)
{
break;
}
printf("我是子进程,PID:%d,PPPID:%d\n", getpid(), getppid());
count++;
sleep(1);
}
exit(2);
}
//父进程
while(1)
{
int status = 0;
pid_t father_id = waitpid(id, &status, WNOHANG);
if(father_id < 0)
{
printf("err\n");
exit(-1);
}
else if(father_id == 0)
{
printf("子进程没有退出\n");
sleep(1);
continue;
}
else
{
if(WIFEXITED(status)) //收到信号
{
printf("等待成功,退出码是:%d\n", WEXITSTATUS(status));
}
else
{
printf("等待成功,信号是:%d\n", status & 0x7F);
}
break;
}
return 0;
}
}
9. 进程程序替换
9.1 现象
[jyh@VM-12-12-centos study8]$ ll
total 24
-rw-rw-r-- 1 jyh jyh 152 Mar 7 16:55 Makefile
-rw-rw-r-- 1 jyh jyh 2045 Mar 7 16:43 proc.c
-rwxrwxr-x 1 jyh jyh 8416 Mar 7 16:56 procReplace
-rw-rw-r-- 1 jyh jyh 271 Mar 7 16:56 procReplace.c
[jyh@VM-12-12-centos study8]$ cat procReplace.c
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("begin......\n");
printf("begin......\n");
printf("begin......\n");
execl("/bin/ls", "ls", "-a", "-l", NULL);
printf("end.....\n");
printf("end.....\n");
printf("end.....\n");
return 0;
}
[jyh@VM-12-12-centos study8]$ ./procReplace
begin......
begin......
begin......
total 32
drwxrwxr-x 2 jyh jyh 4096 Mar 7 16:56 .
drwxrwxr-x 15 jyh jyh 4096 Mar 7 15:42 ..
-rw-rw-r-- 1 jyh jyh 152 Mar 7 16:55 Makefile
-rw-rw-r-- 1 jyh jyh 2045 Mar 7 16:43 proc.c
-rwxrwxr-x 1 jyh jyh 8416 Mar 7 16:56 procReplace
-rw-rw-r-- 1 jyh jyh 271 Mar 7 16:56 procReplace.c
我们发现上述代码中执行procReplace.c这个进程的时候,然后调用execl来执行另外一个程序时,procRepalce.c进程被替换了,并没有执行完execl再来执行procReplace.c程序,这就是程序替换。
9.2 原理
进程有对应的PCB,最后数据和代码别加载到内存中,调用execl函数时,该进程的代码和数据完全被新程序替换,执行新程序。进程的程序替换并没有创建新的进程,调用execl函数前后该进程的PID并没有变化。
[jyh@VM-12-12-centos study8]$ ll
total 24
-rw-rw-r-- 1 jyh jyh 152 Mar 7 16:55 Makefile
-rw-rw-r-- 1 jyh jyh 2045 Mar 7 16:43 proc.c
-rwxrwxr-x 1 jyh jyh 8624 Mar 7 17:21 procReplace
-rw-rw-r-- 1 jyh jyh 578 Mar 7 17:21 procReplace.c
[jyh@VM-12-12-centos study8]$ cat procReplace.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("我是子进程,我的PID:%d\n", getpid());
execl("/bin/ls","ls", "-a", "-l");
}
sleep(5);
printf("我是父进程,我的PID:%d\n", getpid());
waitpid(id, NULL, 0);
return 0;
}
[jyh@VM-12-12-centos study8]$ ./procReplace
我是子进程,我的PID:8085
total 32
drwxrwxr-x 2 jyh jyh 4096 Mar 7 17:21 .
drwxrwxr-x 15 jyh jyh 4096 Mar 7 15:42 ..
-rw-rw-r-- 1 jyh jyh 152 Mar 7 16:55 Makefile
-rw-rw-r-- 1 jyh jyh 2045 Mar 7 16:43 proc.c
-rwxrwxr-x 1 jyh jyh 8624 Mar 7 17:21 procReplace
-rw-rw-r-- 1 jyh jyh 578 Mar 7 17:21 procReplace.c
我是父进程,我的PID:8084
[jyh@VM-12-12-centos study8]$
上述观察到:子进程中调用execl函数并不会影响父进程,说明操作系统在调用execl时对代码和数据完成了写时拷贝。这里的写时拷贝是拷贝的代码,说明写时拷贝可以发生在代码区。
9.3 接口
- execl
int execl(const char* path, const char* arg, ...);
//path:路径
//arg:参数
//最后参数列表必须以NULL结尾
实例
#include <stdio.h>
#include <unistd.h>
int main()
{
execl("/bin/pwd", "pwd", NULL);
return 0;
}
- execv
int execv(const char* path, char* const argv[]);
//path:路径
//argv:字符串数组
//argv[]中必须以NULL结尾
实例
#include <stdio.h>
#include <unistd.h>
int main()
{
char* const argv[] = {"ls", "-a", "-l", NULL};
execv("/bin/ls", argv);
return 0;
}
- execlp
int execlp(const char* file, const char* arg, ...);
//file:文件
//arg:参数
//参数列表必须以NULL结尾
//作用:系统自动在环境变量PATH中进行查找file按照参数执行
实例
#include <stdio.h>
#include <unistd.h>
int main()
{
execlp("ls", "ls", "-a", "-l", NULL);
return 0;
}
//这里的两个ls不一样,第一个ls是执行文件名,第二个ls及以后是怎样执行的参数
- execvp
int execvp(const char* file, char* const argv[], ...);
实例
#include <stdio.h>
#include <unistd.h>
int main()
{
char* const argv[] = {"ls", "-a", "-l", NULL};
execvp("ls", argv);
return 0;
}
- execle
int execle(const char* path, const char* arg, ..., char* const envp[]);
//path:绝对路径
//arg:参数
//envp:自定义环境变量
实例
[jyh@VM-12-12-centos study9]$ tree
.
|-- exec
| |-- Makefile
| |-- otherTest
| `-- otherTest.cc
|-- Makefile
|-- mytest
`-- test.c
1 directory, 6 files
[jyh@VM-12-12-centos study9]$ cat test.c
#include <stdio.h>
#include <unistd.h>
int main()
{
char* const myenv[] = {"MYENV=study9/test.c", NULL};
execle("./exec/otherTest", "otherTest", NULL, myenv);
return 0;
}
[jyh@VM-12-12-centos study9]$ cat ./exec/otherTest.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main()
{
for(int i = 0; i < 3; ++i)
{
cout << "我是exec路径下的程序,我的PID是: " << getpid() << " " << "MYENV:"<< (getenv("MYENV") == NULL ? "NULL" : getenv("MYENV")) << endl;
sleep(1);
}
return 0;
}
[jyh@VM-12-12-centos study9]$ cat Makefile
mytest:test.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f mytest
[jyh@VM-12-12-centos study9]$ cat ./exec/Makefile
otherTest:otherTest.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f otherTest
[jyh@VM-12-12-centos study9]$ ./mytest
我是exec路径下的程序,我的PID是: 6359 MYENV:study9/test.c
我是exec路径下的程序,我的PID是: 6359 MYENV:study9/test.c
我是exec路径下的程序,我的PID是: 6359 MYENV:study9/test.c
[jyh@VM-12-12-centos study9]$
这里就可以掌握使用了。但是看下面的问题:
[jyh@VM-12-12-centos study9]$ clear
[jyh@VM-12-12-centos study9]$ tree
.
|-- exec
| |-- Makefile
| |-- otherTest
| `-- otherTest.cc
|-- Makefile
|-- mytest
`-- test.c
1 directory, 6 files
[jyh@VM-12-12-centos study9]$ cd exec/
[jyh@VM-12-12-centos exec]$ cat otherTest.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main()
{
for(int i = 0; i < 3; ++i)
{
cout << "------------------------------------------------------" << endl;
cout << "我是exec路径下的程序,我的PID是: " << getpid() << endl;
cout << "MYENV:"<< (getenv("MYENV") == NULL ? "NULL" : getenv("MYENV")) << endl;
cout << "PATH:"<< (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl;
cout << "------------------------------------------------------" << endl;
sleep(1);
}
return 0;
}
[jyh@VM-12-12-centos exec]$ ./otherTest
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8867
MYENV:NULL
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8867
MYENV:NULL
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8867
MYENV:NULL
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
[jyh@VM-12-12-centos exec]$ cd ..
[jyh@VM-12-12-centos study9]$ pwd
/home/jyh/linux_-stu/study9
[jyh@VM-12-12-centos study9]$ cat test.c
#include <stdio.h>
#include <unistd.h>
int main()
{
char* const myenv[] = {"MYENV=study9/test.c", NULL};
execle("./exec/otherTest", "otherTest", NULL, myenv);
return 0;
}
[jyh@VM-12-12-centos study9]$ ./mytest
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8910
MYENV:study9/test.c
PATH:NULL
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8910
MYENV:study9/test.c
PATH:NULL
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8910
MYENV:study9/test.c
PATH:NULL
------------------------------------------------------
[jyh@VM-12-12-centos study9]$
现象:当本程序中设定自定义环境变量,另外的程序中有系统环境变量,此时运行本程序时,另外的程序中的系统环境变量为NULL。说明了:自定义环境变量是覆盖式传入,覆盖了系统的环境变量,所以导致这里系统环境变量为NULL。这里怎么解决呢?
int putenv(char* string);
//作用:改变或者添加一个环境变量
[jyh@VM-12-12-centos study9]$ tree
.
|-- exec
| |-- Makefile
| |-- otherTest
| `-- otherTest.cc
|-- Makefile
|-- mytest
`-- test.c
1 directory, 6 files
[jyh@VM-12-12-centos study9]$ cat ./exec/otherTest.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main()
{
for(int i = 0; i < 3; ++i)
{
cout << "------------------------------------------------------" << endl;
cout << "我是exec路径下的程序,我的PID是: " << getpid() << endl;
cout << "MYENV:"<< (getenv("MYENV") == NULL ? "NULL" : getenv("MYENV")) << endl;
cout << "PATH:"<< (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl;
cout << "------------------------------------------------------" << endl;
sleep(1);
}
return 0;
}
[jyh@VM-12-12-centos study9]$ cat test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
extern char** environ;
char* const myenv[] = {"MYENV=study9/test.c", NULL};
putenv("MYENV=study9/test.c");
execle("./exec/otherTest", "otherTest", NULL, environ);
return 0;
}
[jyh@VM-12-12-centos study9]$ ./mytest
------------------------------------------------------
我是exec路径下的程序,我的PID是: 14363
MYENV:study9/test.c
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 14363
MYENV:study9/test.c
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 14363
MYENV:study9/test.c
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
[jyh@VM-12-12-centos study9]$
这里考虑一个问题:环境变量具有全局属性,可以被子进程继承下去,这里怎么做到的呢?
因为所有的指令都是bash的子进程,而bash执行指令都可以使用execl来执行,我们要把bash的环境变量交给子进程,子进程只是需要调用execle,再把对应的bash的环境变量作为参数即可被子进程继承下去。
- execvpe
int execvpe(const char* file, char* const argv[], char* const envp[]);
- execve
int execve(const char* filename, char* const argv[], char* const envp[]);
10. 编写极简shell
[jyh@VM-12-12-centos demo]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 74 Mar 8 17:10 Makefile
-rw-rw-r-- 1 jyh jyh 1716 Mar 8 17:31 mybash.c
[jyh@VM-12-12-centos demo]$ cat mybash.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/types.h>
//模拟实现极简bash命令行
#define COMMANDMAXSIZE 1024
#define ARGVMAXSIZE 64
int split(char* command_string, char* argv[])
{
assert(command_string != NULL && argv != NULL);
argv[0] = strtok(command_string, " ");
if(argv[0] == NULL)
{
return -1;
}
int i = 1;
while((argv[i++] = strtok(NULL, " ")));
//while(1)
//{
// argv[i] = strtok(NULL, " ");
// if(argv[i] == NULL)
// {
// break;
// }
// ++i;
//}
return 0;
}
void debugOut(char* argv[])
{
int i = 0;
while(argv[i] != NULL)
{
printf("%d->%s\n", i, argv[i]);
++i;
}
//for(int i = 0; argv[i] != NULL; ++i)
//{
// printf("%d->%s\n", i, argv[i]);
//}
}
int main()
{
while(1)
{
char command_string[COMMANDMAXSIZE] = {0};
printf("[name@my_root currrent_path]# ");
fflush(stdout);
char* s = fgets(command_string, sizeof(command_string), stdin);
assert(s != NULL);
(void)s; //保证release版本时,因为去掉assert()导致ret未被使用,而带来的编译告警
command_string[strlen(command_string) - 1] = '\0'; //去掉回车导致的换行
char* argv[ARGVMAXSIZE] = {NULL};
int ret = split(command_string, argv); //命令字符串切割
if(ret != 0)
{
continue;
}
//debugOut(argv);
pid_t id = fork();
assert(id >= 0);
if(id == 0) //子进程
{
execvp(argv[0], argv);
exit(0);
}
//父进程
int status = 0;
waitpid(id, &status, 0);
//子进程去执行对应的命令,父进程等待子进程
}
return 0;
}
[jyh@VM-12-12-centos demo]$ cat Makefile
mybash:mybash.c
gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
rm -f mybash
[jyh@VM-12-12-centos demo]$