一、简述
记--进程的概念,简单的创建并使用进程。进程是资源分配的最小单位。所有的进程都是1号进程的子进程,一个进程至少有一个线程,也可以有多个线程。
每个进程一般都拥有4G的虚拟内存(真实并没有)。创建一个子进程,子进程也拥有独立的4G虚拟内存,并且复制父进程的运行状态(代码都是一样的,并且代码运行到哪都一样)
子进程退出后一般进入“僵死态”,由其父进程回收资源,若是其父进程已经结束,则由父进程的父进程回收,以此类推。
二、创建进程
2.1 fork()函数
功能 | 创建子进程 |
头文件 | #include #include |
函数原型 | pid_t fork(void); |
返回值 | 成功时,子进程的PID将在父进程中返回,并在子进程中返回0。 失败时,在父项中返回-1,不创建子进程,并正确设置errno。 |
备注 | 子进程资源、代码、内存状况都与父进程的一样,互不干扰。子进程就从fork()之后的代码执行。 |
子进程资源、代码、内存状况都与父进程的一样,子进程从fork()函数之后的代码执行。
虽然父进程与子进程代码相同,但是fork()的返回值不同,在父进程中返回值是子进程的PID,而在子进程中返回值是0。
测试代码:
#include
#include
#include
int main(int argc, char *argv[])
{
printf("main\n");
pid_t pid = fork();
printf("pid=%d\n", pid);
return 0;
}
通过fork的返回值来区分父进程和子进程,从而执行不同的动作
三、退出进程
3.1 exit()函数
功能 | 终止一个进程 |
头文件 | #include |
函数原型 | void exit(int status); |
参数 | 退出状态,常用宏EXIT_SUCCESS表示成功, EXIT_FAILURE表示失败 |
返回值 | 无 |
备注 | 1、status&0377的值(其实就是status的低8位)返回给父进程,在父进程可用wait()来获取。(0377-->011 111 111) 2、在退出进程前会先执行atexit或者是on_exit所登记的退出处理函数 |
父进程等待子进程结束,并获取子进程的结束状态。使用宏WEXITSTATUS来获取。
在退出进程前会先执行atexit或者是on_exit所登记的退出处理函数,
atexit()函数
功能 | 注册一个函数,在程序退出前调用 |
头文件 | #include |
函数原型 | int atexit(void (*function)(void)); |
参数 | 一个无返回值,无参数的函数指针 |
返回值 | 成功返回0,失败返回非0值 |
on_exit()函数
功能 | 注册一个函数,在程序退出前调用 |
头文件 | #include |
函数原型 | int on_exit(void (*function)(int , void *), void *arg); |
参数 | function是一个待参数的函数指针 arg是function指向函数的参数,以字符串形式 |
返回值 | 成功返回0,失败返回非0值 |
备注 | func(int status, void *);一个是退出状态,一个是参数地址 |
测试代码
#include
#include
#include
#include
void func(int status, void *arg)
{
printf("status=%d, arg=%s", status, (char *)arg);
}
int main(int argc, char *argv[])
{
printf("main\n");
pid_t pid = fork();
on_exit(func," this is on_exit call\n");
if(pid==0)
{
printf("this is child process!\n");
exit(5);
}
printf("this is parent process!\n");
return 0;
}
exit退出会清理IO缓冲区,_exit()直接退出,不清理缓冲区
三、等待进程
wait()函数
功能 | 等待进程改变状态 | |
头文件 | #include #include |
|
函数原型 | pid_t wait(int *wstatus); pid_t waitpid(pid_t pid, int *wstatus, int options); |
|
参数 | pid:是要等待的子进程的PID | >0:等待进程号为pid的子进程 =0:等待调用者所在进程组的 任一子进程 -1:等待任一子进程退出(跟wait一样),可以不同进程组 小于-1:等待组ID的绝对值为pid的进程组 中的任一子进程 |
wstatus 是子进程的状态 | 比如子进程的退出状态 | |
options:附加选项 | 0:正常引用该函数 WCONTINUED:报告任一从暂停态出来且从未报告过的子进程的状态 WNOHANG:非阻塞等待 WUNTRACED:报告任一当前处于暂停态且从未报告过的子进程的状态 |
|
返回值 | 成功返回退出的子进程的pid,失败返回-1,返回0则是设置了非阻塞等待,其没有出错但是又没有子进程退出 | |
备注 | 如果不需要获取子进程的退出状态,stat_loc 可以设置为 NULL WEXITSTATUS(status);返回子进程的退出状态。 这包括子项在exit(3)或_exit(2)的调用中指定的status参数的最低有效8位,或者作为main()中return语句的参数。 仅当WIFEXITED返回true时才应使用此宏。 |
四、vfork()与exec()
功能 | 创建子进程并阻塞父进程 |
头文件 | #include #include |
函数原型 | pid_t vfork(void); |
返回值 | 成功时,子进程的PID将在父进程中返回,并在子进程中返回0。 失败时,在父项中返回-1,不创建子进程,并正确设置errno。 |
备注 | 相对比于fork有以下特点: 1、共享父进程的资源,也就是共同操作一份虚拟内存,数据相互影响。 2、父进程进入睡眠,一直等到子进程结束或者是调用exec系列函数。 3、专门用来于exec系列函数配合使用。 |
资源共享,内存空间一样,共同操作一份数据。父进程进入睡眠,一直等到子进程结束。
vfork()与exec()系列函数进行配合使用
功能 | 在进程中加载新的程序文件或者脚本,覆盖原有代码,重新运行。(调用第三方程序,成功调用原来程序就不继续执行了) | |
头文件 | #include |
|
函数原型 | int execl(const char *path, const char *arg, ...); int execv(const char *path, char *const argv[ ]); int execle(const char *path, const char *arg, ..., char * const envp[ ]); int execlp(const char *file, const char *arg, ...); int execvp(const char *file, char *const argv[ ]); int execvpe(const char *file, char *const argv[ ],char *const envp[ ]); |
|
path | 即将被加载执行的 ELF 文件或脚本的路径 | |
file | 即将被加载执行的 ELF 文件或脚本的名字 | |
arg | 以列表方式罗列的 ELF 文件或脚本的参数 | |
argv | 以数组方式组织的 ELF 文件或脚本的参数 | |
envp | 用户自定义的环境变量数组 | |
返回值 | 调用第三方程序,成功调用原来程序就不继续执行了,返回值就没用了。如果调用失败或出错就返回-1。 | |
备注 | 1,函数名带字母 l 意味着其参数以列表(list)的方式提供。 2,函数名带字母 v 意味着其参数以矢量(vector)数组的方式提供。 3,函数名带字母 p 意味着会利用环境变量 PATH 来找寻指定的执行文件。 4,函数名带字母 e 意味着用户提供自定义的环境变量。 *在给execl()函数传参时,最后一个参数为NULL,以标识参数结尾。 |
子进程中成功调用了第三方程序,子进程后面的代码就不会执行了。如例子中的printf("in child i=%d\n", i);就不会执行。
可以使用execlp()函数调用系统命令
五、守护进程
1、守护进程的概念
类似于服务。通常创建的子进程受限于父进程,受限于组等。比如当某个信号是发送的该组的话,该组所有的进程都将收到影响。比如说在终端运行一个程序a,a程序会占用终端的控制资源,控制终端等程序a结束后才继续。而在控制终端下按下Ctrl+c(向子进程发出挂断信号),或者直接关掉终端,程序a都会被终端并退出。而守护进程就相当于独立出来,不再受组、父进程等影响的一个进程。例如在终端运行的一个程序,关闭终端后,守护进程不受影响,然可继续运行。
2、将一个普通进程变为守护进程
代码:
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
pid_t pid;
int max_fd, i;
//1 忽略挂断信号
signal(SIGHUP, SIG_IGN);
//2 创建子进程,并结束父进程
pid = fork();
if(pid>0)
exit(0);
//3 设置新的会话
setsid();
//4 创建子进程,并结束父进程
pid = fork();
if(pid>0)
exit(0);
//5 设置新的组
setpgrp();
//6 关闭所有描述符,(输出信息可以记录在系统日志,/var/log/syslog)
max_fd = sysconf(_SC_OPEN_MAX);
for(i=0;i
结果:
可以使用命令:kill -s 9 进程ID 或 killall 进程名称 来结束进程
由于关闭了所有打开的文件描述符(包括标准输入0,标准输出1、标准出错2),所以需要输出信息的的话,可以使用系统日志文件。
void openlog(const char *ident, int option, int facility);//打开日志
void syslog(int priority, const char *format, ...);//写入日志
void closelog(void);//关闭日志
功能 | 为程序打开到系统记录器的连接。(打开系统日志文件) |
头文件 | #include |
原型 | void openlog(const char *ident, int option, int facility); |
参数 | ident:添加到每条消息的前面,用来标识消息,通常设置为程序的名称。 option:指定控制openlog()操作和后续syslog()调用的标志。可以是下面一个或多个值的“或” LOG_CONS 如果系统日志服务器不能用,写入控制台 LOG_NDELAY 立即打开连接,正常情况下,直到发送第一条消息才打开连接 LOG_PERROR 打印输出到stderr(标准出错,通常是显示屏,不缓冲输出) LOG_PID 每条消息中包含进程 PID facility:指定程序发送消息的类型。 LOG_AUTHPRIV 安全授权消息 LOG_CRON 时钟守护进程:cron和at LOG_DAEMON 其他系统守护进程 LOG_KERN 内核消息 LOG_LPR 打印机子系统 LOG_MAIL 邮件子系统 LOG_USER 默认 |
返回值 | 无 |
备注 |
功能 | 生成一条日志消息(将信息写入日志文件) |
头文件 | #include |
原型 | void syslog(int priority, const char *format, ...); |
参数 | priority:priority指定消息的重要性。 LOG_EMERG 系统不能使用 LOG_ALERT 立即采取措施 LOG_CRIT 紧急事件 LOG_ERR 出错条件 LOG_WARNING 警告条件 LOG_NOTICE 正常但重大事件 LOG_INFO 信息消息 LOG_DEBUG 调试信息 format:类似于printf函数,指定格式的字符串 |
返回值 | 无 |
备注 | syslog在首次使用的时候自动打开日志文件,也就是说可以不调用openlog()函数。 |
功能 | 关闭系统日志文件 |
头文件 | #include |
原型 | void closelog(void); |
linux系统上日志文件通常是/var/log/syslog
测试代码:将信息写到系统日志文件中
liang@ubuntu:~$ ./system
liang@ubuntu:~$ vi /var/log/syslog
liang@ubuntu:~$ cat system.c
#include
#include
#include
#include
#include
int daemon_init(void);
int main(void)
{
int i = 10;
//1、创建守护进程
daemon_init();
//2、打开系统日志
openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);
//3、将信息写入系统日志
while(i--)
{
syslog(LOG_DAEMON, "this is syslog test! %d\n", i);
printf("printf test\n");//因为在创建守护进程时关闭了所有打开的描述符(包括标准输入输出、标准出错),所以这句话是没有打印的
sleep(2);
}
//4、关闭系统日志
closelog();
return 0;
}
int daemon_init(void)
{
int i, max_fd;
pid_t pid;
// 1 创建子进程
pid = fork();
if(pid<0)//创建子进程a失败
{
perror("fork faild!");
exit(1);
}
else if(pid != 0)//退出a的父进程
{
exit(0);
}
//以下是a子进程部分
// 2 忽略挂断信号
signal(SIGHUP, SIG_IGN);
// 3 设置会话id
if(setsid() < 0)
{
exit(1);
}
// 4 子进程a再创建子进程aa
if((pid = fork()) < 0)
{
perror("fork faild!");
exit(1);
}
else if(pid != 0)//退出aa的父进程a
{
exit(0);
}
// 5 设置组id
setpgrp();
// 6 关闭所有打开的文件描述符
max_fd = sysconf(_SC_OPEN_MAX);//获取到打开中最大的文件描述符
for (i = max_fd; i>=0; i--)//逐个关闭
{
close(i);
}
// 7 设置掩码
umask(0);
// 8 改变工作路径
chdir("/");
return 0;
}
运行结果:
六、进程间通信
1、管道
2、信号
3、套接字
4、system V-IPC
等等。。。。。