C语言并发程序设计
进程的概念
程序:
存放在磁盘上的指令和数据的有序集合(文件)
静态的
进程:
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
动态的,包括创建、调度、执行和消亡
进程包含的内容
进程包含:正文段(代码段)、用户数据段、系统数据段
程序包含:正文段(代码段)、用户数据段
系统数据包含:进程控制块、CPU寄存器值、堆栈
进程控制块(PCB)包含:
进程标识PID
进程用户
进程状态、优先级
文件描述符表
CPU寄存器值:
PC:program counter, 记录着下一条执行指令的地址
堆栈:所有的局部变量都是在栈中存在的
进程的类型
交互进程:在shell下启动。可以在前台运行,也可以在后台运行
批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
守护进程:和终端无关,一直在后台运行
进程的状态
运行态:进程正在运行,或者准备运行
等待态:进行在等待一个事件的发生或某种系统资源,又分为可中断和不可中断
停止态:进程被中止,收到信号后可继续运行
死亡态:已终止的进程,但pcb没有没有被释放
查看进程信息
ps:查看系统进程快照
ps -ef : 查看系统中所有的进程信息
ps aux: 比ps -ef 多一个当前的进程状态信息,进程优先级
top:查看进程动态信息,每个3秒刷新一次
/proc:查看进程详细信息,在proc目录下以进程PID为名的目录,其中status文件保存着进程的详细信息,fd目录保存这使用的文件
改变进程优先级
nice: 按用户指定的优先级运行进程,nice的区间[-20, 19],默认值为0,值越小优先级越高,普通用户能够设置的最小值为0。
nice -n 2 ./a.out
renice:改变正在运行进程的优先级,普通用户只能增加这个值。
renice -n 2 PID
前后台进程切换
jobs:查看后台进程
xdl@xdl-gj:~/C语言/thread$ ./a.out &
[1] 22441 //1表示作业号
xdl@xdl-gj:~/C语言/thread$ ./a.out &
[2] 22442
xdl@xdl-gj:~/C语言/thread$ jobs
[1]- 运行中 ./a.out &
[2]+ 运行中 ./a.out &
bg:降挂起的进程在后台运行
fg:把后台运行的进程放到前台运行
xdl@xdl-gj:~/C语言/thread$ fg 1
./a.out
^Z // ctrl +z 使进程挂起
[1]+ 已停止 ./a.out
xdl@xdl-gj:~/C语言/thread$ bg 1
[1]+ ./a.out &
创建进程
fork函数用于创建一个进程
#include <unistd.h>
pid_t fork(void);
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程和子进程
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid; pid = fork(); printf("pid=%d\n", pid); if (pid < 0) { perror("fork"); return -1; } else if (pid == 0) { printf("child process: my pid is %d\n", getpid()); } else { printf("parent process: my pid is %d\n", getpid()); } return 0; } /* pid=6018 parent process: my pid is 6017 pid=0 child process: my pid is 6018 */
父子进程
子进程继承父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束
子进程称为孤儿进程,被init进程收养(linux启动内核之后自动创建的用户态进程PID为1)
子进程变成后台进程
若子进程先结束
父进程如果没有及时回收,子进程变成僵尸进程
子进程从何处开始运行?
从fork下一句指令开始,并没有执行fork语句
父子进程谁先执行?
不一定,一般父进程创建子进程后时间片没有用完则继续执行
父进程能否多次调用fork?子进程呢?
可以
结束进程
exit/_exit
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
结束当前进程并将status(低八位)返回
exit结束结束进程时会刷新(流)缓冲区
#include <stdio.h> #include <stdlib.h> // for exit #include <unistd.h> //for _exit int main(int argc, char *argv[]) { printf("this process will exit"); //exit(0);//会打印上一句 _exit(0);//不会打印 printf("never be displayed"); return 0; }
exec函数族
进程调用exec函数执行某个程序
进程当前内容被指定的程序替换
实现让父子进程执行不同的程序
父进程创建子进程
子进程调用exec函数族
父进程不受影响
execl/execlp
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
成功执行指定的程序;失败时返回EOF
path 执行的程序的名称,包含路径
arg... 传递给执行的程序的参数列表
file执行的程序的名称,在PATH中查找
执行ls命令,显示/etc目录下所有文件的详细信息
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid; pid = fork(); printf("pid=%d\n", pid); if (pid < 0) { perror("fork"); return -1; } else if (pid == 0) { printf("child process: my pid is %d\n", getpid()); if (execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL) < 0) { perror("execl"); return -1; } } else { printf("parent process: my pid is %d\n", getpid()); if (execlp("ls", "ls", "-al", "/etc", NULL) < 0 ) { perror("execlp"); return -1; } } return 0; } /* pid=6018 parent process: my pid is 6017 pid=0 child process: my pid is 6018 */
execv/execvp
#include <unistd.h>
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
成功时执行指定的程序;失败时返回EOF
arg...封装成指针数组的形式
char *arg[] = {"ls", "-a", "-l", "/etc", NULL};
if(execv("/bin/ls", arg) < 0){
perror("execv");
}
if(execvp("ls", arg) < 0){
perror("execvp");
}
system
#include <stflib.h>
int system(const char *command);
成功时返回命令command的返回值;失败时返回EOF
当前进程等待command执行结束后才继续执行
进程回收
子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程
wait函数
#include <unistd.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
wait pid函数
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);
成功时返回回收的子进程的pid或0(表示子进程还没有结束);失败时返回EOF
pid可用于指定回收哪个子进程或任意子进程(-1表示任意子进程)
status指定用于保存子进程返回值和结束方式
option指定回收方式,0(阻塞方式)或WNOHANG(非阻塞)
进程返回值和结束方式
子进程通过exit/_exit/return 返回某个值(0-255)
父进程调用wait(&status)回收
WIFEXITED(status) : 判断子进程是否正常结束
WEXITSTATUS(status): 获取子进程返回值
WIFSIGALED(status): 判断子进程是否被信号结束
WTERMSIG(status): 获取结束子进程的信号类型
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int main(int argc, char *argv[]) { int status; pid_t pid; if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid == 0) { sleep(1); exit(2); } else { wait(&status); printf("%d\n", status); } return 0; } /* 200 521 0010 0000 0000 低7为(0~7)表示进程结束的类型,0表示正常结束,非零表示信号结束 高8为(8~15)表示子进程正常结束时的返回值 */
守护进程
守护进程(Daemon)是Linux三种进程类型之一
通常在系统启动时运行,系统关闭时结束
Linux系统中大量使用,很多服务程序以守护进程形式运行
守护进程的特点
始终在后台运行
独立于任何终端
中期性的执行某种任务或等待处理特定事件
会话、控制终端
Linux以会话(session)、进程组的方式管理进程
每一个进程属于一个进程组,父子进程属于同一个进程组
会话是一个或多个进程组的集合。通常用户打开一个终端时,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话
终端关闭时,所有相关进程会被结束
创建守护进程
1、创建子进程,父进程退出
if (fork() > 0){
exit(0);
}
子进程变成孤儿进程,被init进程收养
子进程在后台运行
2、子进程创建新会话
if (setsid() < 0 ){
exit(-1);
}
子进程成为新的会话组长
子进程脱离原先的终端
3、更改当前工作目录
chdir("/");
chdir("/tmp");
守护进程一直在后台运行,其工作目录不能被卸载
重新设定当前工作目录cwd
4、重设文件权限掩码
if (umask(0) < 0){
exit(-1);
}
文件权限掩码设置为0
值影响当前进程
5、关闭打开的文件描述符
int i;
for (i = 0; i < getdtablesize(); i++){
close(i);
}
关闭所有从父进程继承的打开文件
已脱离终端,stdio,stdout,stderr无法在使用
创建守护进程,每隔1秒将系统时间写入文件time.log
#include <stdio.h> #include <unistd.h> #include <time.h> #include <stdlib.h> #include <sys/stat.h> // for umask() int main(int argc, char *argv[]) { pid_t pid; FILE *fp; time_t t; int i; if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid > 0) { exit(0); } setsid(); umask(0); chdir("/tmp"); for (i = 0; i < getdtablesize(); i++) { close(i); } if ((fp = fopen("time.log", "a")) == NULL) { perror("fopen"); exit(-1); } while (1) { time(&t); fprintf(fp, "%s", ctime(&t)); fflush(fp); sleep(1); } return 0; }