目录
1.获取进程信息:
pid_t getpid(void) //返回当前进程的 PID
pid_t getppid(void) //返回当前进程的父进程的 PID
2.创建和终止进程
终止进程:
- 接收到一个终止信号
- 返回到
main
- 调用了
exit
函数
exit
函数:
// 以 status 状态终止进程
void exit(int status)
- _exit() :退出程序;
- exit(0):运行正常退出程序;
- exit(1):运行异常退出程序;
- return():返回函数,若在主函数中,则会退出函数并返回值。
创建进程:
调用 fork
来创造新进程。这个函数很有趣,执行一次,但是会返回两次,具体的函数原型为
// 对于子进程,返回 0
// 对于父进程,返回子进程的 PID
int fork(void)
返回值:
RETURN VALUE
On success,
the PID of the child process is returned in the parent,
and 0 is returned in the child.
On failure, -1 is returned in the parent, no child process is created,
and errno is set appropriately.
子进程几乎和父进程一模一样,会有相同且独立的虚拟地址空间,也会得到父进程已经打开的文件描述符(file descriptor)。比较明显的不同之处就是进程 PID 了。按照手册说法:
The child process and the parent process run in separate memory spaces.
At the time of fork() both memory spaces have the same content.
Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.
也就是说:父进程和子进程在不同的内存中运行,但是却有相同的内容 ,如下示例中 int x 就是两者共用的数据。
程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
/* $begin fork */
/* $begin wasidefork */
int main()
{
pid_t pid;
int x = 1;
//在父进程中创建一个子进程
pid = fork();
if(pid == 0)
{ /* Child */
printf("child : x=%d\n", ++x); //line:ecf:childprint
exit(0);
}
/* Parent */
printf("parent: x=%d\n", --x); //line:ecf:parentprint
exit(0);
}
/* $end fork */
/* $end wasidefork */
输出:
parent: x=0
child : x=2
运行结果说明:
- 调用一次,但是会有两个返回值
- 并行执行,不能预计父进程和子进程的执行顺序(在本系统中,父进程先完成printf语句,然后是子进程)
- 拥有自己独立的地址空间(也就是变量都是独立的),除此之外其他都相同
- 在父进程和子进程中
stdout
是一样的(都会发送到标准输出)
进程图:
3.回收子进程
wait函数
pid_t wait(int *status);
wait的参数status。status用来返回子进程结束时的状态,父进程通过wait得到status后就可以知道子进程的一些结束状态信息。wait的返回值pid_t,这个返回值就是本次wait回收的子进程的PID。当前进程有可能有多个子进程,wait函数阻塞直到其中一个子进程结束wait就会返回,wait的返回值就可以用来判断到底是哪一个子进程本次被回收了。总结:wait主要是用来回收子进程资源,回收同时还可以得知被回收子进程的pid和退出状态。
WIFEXITED、WIFSIGNALED、WEXITSTATUS 这几个宏用来获取子进程的退出状态。
WIFEXITED 宏用来判断子进程是否正常终止(return、exit、_exit退出)
WIFSIGNALED 宏用来判断子进程是否非正常终止(被信号所终止)
WEXITSTATUS 宏用来得到正常终止情况下的进程返回值的。
使用waitpid实现wait的效果
ret = waitpid(-1, &status, 0);
-1 表示不等待某个特定PID的子进程而是回收任意一个子进程,0表示用默认的方式(阻塞式)来进行等待,返回值ret是本次回收的子进程的PID。当调用进程没有子进程,那么waitpid返回-1,并设置reeno为EINTR。
ret = waitpid(pid, &status, 0);
等待回收PID为pid的这个子进程,如果当前进程并没有一个ID号为pid的子进程,则返回值为-1;如果成功回收了pid这个子进程则返回值为回收的进程的PID
ret = waitpid(pid, &status, WNOHANG);
这种表示父进程要非阻塞式的回收子进程。
如果父进程执行waitpid时子进程已经先结束等待回收则waitpid直接回收成功,返回值是回收的子进程的PID;如果父进程waitpid时子进程尚未结束则父进程立刻返回(非阻塞),但是返回值为0(表示回收不成功)。
程序:
/* $begin waitpid1 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#define N 10
int main()
{
int status, i;
pid_t pid;
//在父进程中创建N个子进程
for (i = 0; i < N; i++)
if ((pid = fork()) == 0) /* Child */
exit(100+i); //每个子进程分别以唯一的退出状态退出
//父进程中用while循环,调用 waitpid 等待所有进程终止
//当回收了所有进程后,再调用waitpid返回就是-1,并且设置 errno = ECHILD
while ((pid = waitpid(-1, &status, 0)) > 0)
{
if (WIFEXITED(status)) //判断子进程是否正常终止,exit
printf("child %d terminated normally with exit status=%d\n",
pid, WEXITSTATUS(status));
else
printf("child %d terminated abnormally\n", pid);
}
/* The only normal termination is if there are no more children */
if (errno != ECHILD) //line:ecf:waitpid1:errno
printf("waitpid error");
exit(0);
}
/* $end waitpid1 */
4.让进程休眠
//将一个进程挂起一段指定的时间
unsigned int sleep(unsigned int secs);
//让调用函数休眠,直到该进程收到一个信号
int pause(void);
5.加载并运行程序 、利用fork和execve运行程序
这里只举个简单的:
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
execl() 其中后缀 " l " 代表 list 也就是参数列表的意思
第一参数 path 字符指针所指向要执行的文件路径
之后的 arg 参数代表执行该文件时传递的参数列表 :argv[0] , argv[1] ...
最后一个参数须用空指针NULL作结束
为什么需要exec函数?
(1) fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)
(2) 可以直接在子进程的 if 中写入新程序的代码。这样可以,但是不够灵活,因为我们只能把子进程程序的源代码贴过来执行(必须知道源代码,而且源代码太长了也不好控制),譬如说我们希望子进程来执行ls -la 命令就不行了(没有源代码,只有编译好的可执行程序)
(3) 使用exec族运行新的可执行程序(exec族函数可以直接把一个编译好的可执行程序直接加载运行)
(4) 我们有了exec族函数后,我们典型的父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译连接成一个可执行程序(叫hello),(项目是一个多进程项目)主程序为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。
下面举一个简单的例子:
在子进程中执行 :
ls -l -a
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
pid_t pid = -1;
pid_t ret = -1;
int status = -1;
pid = fork();
if (pid > 0)
{
// 父进程
printf("parent, 子进程id = %d.\n", pid);
}
else if (pid == 0)
{
// 子进程
execl("/bin/ls", "ls", "-l", "-a", NULL); // ls -l -a
return 0;
}
else
{
perror("fork");
return -1;
}
return 0;
}
输出:
总用量 28
drwxrwxr-x 2 wdd wdd 4096 9月 14 00:20 .
drwxr-xr-x 34 wdd wdd 4096 9月 13 17:05 ..
-rwxrwxr-x 1 wdd wdd 8760 9月 14 00:16 a.out
-rwxrw-rw- 1 wdd wdd 486 9月 14 00:15 execl.c
-rwxrw-rw- 1 wdd wdd 163 9月 14 00:20 hello.c
如果我们先编译好一个可执行文件,程序如下:
#include <stdio.h>
int main(int argc, char **argv)
{
int i = 0;
printf("argc = %d.\n", argc);
while (NULL != argv[i])
{
printf("argv[%d] = %s\n", i, argv[i]);
i++;
}
return 0;
}
然后用gcc生成可执行文件:
把上面的: execl("/bin/ls", "ls", "-l", "-a", NULL); 换成:
execl("hello", "aaa", "bbb", NULL);
结果如下输出如下:
argc = 2.
argv[0] = aaa
argv[1] = bbb