linux下进程的控制
进程的创建
- 首先,我们知道进程的创建需要调用fork()函数。
- fork()一次调用两次返回,子进程返回0,父进程返回子进程的pid。
- 同样也可以调用vfork来创建子进程,但此时父子进程共享地址空间,因此我们一般不建议使用。
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid=fork();
if(pid<0)
{
perror("fork");
return -1;
}
if(pid==0)
{
//chilid
while(1)
{
printf("I am Child\n");
sleep(1);
}
}
while(1)
{
printf("I am Father\n");
sleep(1);
}
return 0;
}
这样就完成了进程的创建。
进程的等待
我们知道,一个进程的退出,它的父进程必须知道它的退出信息,因此会保持僵尸状态,而如果僵尸进程没有人去回收的话,就会造成内存的泄露。
父进程需要通过等待的方式,来获取子进程退出的信息,并且回收掉子进程的资源。
函数1:pid_t wait(int*status),成功返回等待进程id,失败返回-1,并且带出等待进程的退出信息。不关心退出信息的话就可以设置成NULL;
函数2:pid_t waitpid(pid_t pid,int *status,int options);正常返回收集到的子进程的ID,如何设置了WNOHANG,调用发现已经没有已退出的子进程时候,返回0,pid=-1时,表示回收任一个子进程,pid>0表示等待其进程的ID和pid相等的子进程。
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 int status=0;
6 int count=5;
7 pid_t pid=fork();
8 if(pid<0)
9 {
10 perror("fork");
11 return -1;
12 }
13 else if(pid==0)
14 {
15 //child
16 while(count--)
17 {
18 printf("Child process running\n");
19
20 }
21 exit(1);
22
23
24 }
25 else
26 {
27 //father
28 wait(&status);
29 printf("status is %d\n",status);
30 printf("Father process running\n");
31 }
32
33 return 0;
34 }
其中不管是wait还是waitpid,都有一个status标志位标志着子进程的退出消息,因此很有必要研究一下里面到底存放的是什么。
- 首先,这个status不能简单的当,作一个整形来看待,更应该把它比喻成一个位图。
- 而我们只需要研究它的低16位就足够了
- 其中,当一个进程代码完成,正常结束掉的时候,它所告诉父进程的退出状态存放在高8位。第八位全0
- 而它如果是由信号杀死的话,那么它的低7位就存放的是被那个终止信号干掉的,第8为是core dump 标志位,表示它是否生成了core dump文件。我们称之为核心转储
进程终止
- 一个进程的结束有三种情况:1.代码运行完,结果正确,2.代码运行完,结果不正确,3.代码
运行异常终止 - 而正常退出又分为man函数return返回和调用exit或者_exit来。
- 异常终止比如接收到ctrl+c 这样的信号终止。
调用exit
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6
7 printf("hello world\n");
8 exit(1);
9 }
调用_exit
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6
7 printf("hello world");
8 _exit(1);
9 }
- 为什么同样是正常退出,但是结果却不一样呢,这里我们要讲一下exit和_exit的区别。
- 首先,exit是c库函数,而_exit是系统调用函数,exit封装了_exit。
- 当我们调用exit 的时候,首先执行用户自定义的清理函数,再刷新缓冲区,关闭流等操作,最后才回去调用_exit。
- 其中,还有一种退出方式就是return ,其实return还是将后面的参数传递给了exit。
进程替换
- 我们创建子进程的最主要原因,就是想让子进程来代替父进程做一部分工作。而fork出来的子进程和父进程拥有相同的代码和数据,因此就需要对子进程的代码和数据进行替换,从而完成别的任务,这就是进程替换的基本思想。
- 下面我们来看看6个进程替换的函数:
如何更好记忆
- 首先,execve才是真正的系统调用接口,这些函数都是在它的基础上进行了封装.
- l表示参数使用列表,即可变参数参数列表注意要以NULL结尾,而v表示参数采用数组,需要用户自己定义。
- 带p表示自动搜索当前环境变量PATH,不带P就需要手动的来添加路径。
- 带e表示需要自己维护环境变量。
自己实现一个shall
综合了之前所学的知识,我们大概能明白shall是如何运行命令的,当我们用户输入一条命令时,shall获取这条命令,并且对它进行解析,创建一个子进程,然后用当前命令替换子进程,父进程等待子进程的退出。
1 #include<stdio.h>
2 #include<sys/wait.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<unistd.h>
6 char *argv[8];
7 int argc=0;
8 void do_pause(char*buf)
9 {
10 int i;
11 int status=0;
12 for(argc=i=0;buf[i];i++)
13 {
14 if(!isspace(buf[i])&&status==0)
15 {
16 argv[argc++]=buf+i;
17 status=1;
18
19 }
20 else if(isspace(buf[i]))
21 {
22 status=0;
23 buf[i]=0;
24 }
25 }
26 argv[argc]=NULL;
27 }
28 void do_execute(void)
29 {
30 pid_t pid=fork();
31 switch(pid)
32 {
33 case -1:
34 perror("fork");
35 exit(EXIT_FAILURE);
36 break;
37 case 0:
38 execvp(argv[0],argv);
39 perror("execvp");
40 exit(EXIT_FAILURE);
41 default:
42 {
43 int st;
44 while(wait(&st)!=pid)
45 ;
46 }
47 }
48 }
49 int main()
50 {
51 char buf[1024]={};
52 while(1)
53 {
54 printf("myshell>>");
55 scanf("%[^\n]%*c",buf);
56 do_pause(buf);
57 do_execute();
58
59 }
60 }
popen/system
- FILE *popen(const char *command, const char *type);
- 这个函数创建一个管道,然后通过fork或者inovke一个子进程,然后在子进程里面执行command的命令,然后把结果返回到标准I/O当中,由于管道只能单向同通信的原因,所以command只能从管道中读取r或者把结果w到管道里面。在调用完成后我们要调用fclose来回收创建的子进程,不然就可能产生僵尸进程。
int system(const char *command); - 这个函数创建一个进程,然后进程替换去执行我们指定的command命令。然后shell阻塞直到该命令执行完成,再返回到调用函数,
- 函数 process_create(pid_t* pid, void* func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数
int process_create(int(*func)(),const char* file,char* argv[])
{
int ret = 0;
pid_t pid = fork();
if(pid == 0)
{
//child
ret = func(file,argv);
if(ret == -1)
{
printf("调用execvp失败\n");
perror("func");
_exit(-1);
}
}
else
{
int st;
pid_t ret = wait(&st);
if(ret == -1)
{
perror("wait");
exit(-1);
}
}
return 0;
}
int main()
{
char* argv1[] = {"ls"};
char* argv2[] = {"ls","-al","/etc/passwd",0};
process_create(execvp,*argv1,argv2);
return 0;
}