文章目录
1.程序和进程:
1.1 程序:
磁盘中存储的代码文件,是静态的。
1.2 进程:
程序一次执行的过程,是动态的。(程序执行和资源管理的最小单位)
主要进程标识
进程号 (PID)
父进程号(PPID)
PID唯一的标识一个进程
Linux中的进程包含三个段
数据段:存放全局变量,常数以及动态数据分配的数据空间
正文段:存放的是程序中的代码
堆栈段:存放的是函数的返回地址,函数的参数以及程序中的局部变量
进程类型:
交互进程:由shell控制和运行的。可前台可后台
批处理进程:不属于某个终端,它被提交到一个队列中以便顺序执行
守护进程:在后台运行。一般在LINUX启动时开始执行,系统关闭时才结束
2.linux调度进程相关命令:
at 在指定时刻执行相关进程
cron 周期性执行相关进程
ps 查看系统中的进程
ps -ef 显示系统下所有进程
top 动态显示系统中的进程
nice 按用户指定的优先级运行进程
./a.out -n 19 (用户指定最高优先级0 最低优先级19)
renice 改变正在运行进程的优先级
renice -n 3 3632(PID)
kill 向进程发信号
kill -9 强制杀死进程
bg 将挂起的进程在后台执行
bg 1 (1为进程挂起序号,Ctrl+z后中括号内的数字)
fg 将挂起的进程放到前台运行
fg 1 (1为进程挂起序号,Ctrl+z后中括号内的数字)
& 让程序后台运行
./a.out & 直接后台运行a.out
3.常用函数
3.1 进程创建:fork()
pid_t fork(void);
功能:创建子进程
头文件:
#include<sys/types.h>
#include<unistd.h>
返回值: -1: 出错
0 : 成功,代表子进程区域
子进程PID: 成功,代表父进程区域
例子:
int main()
{
printf("main:%d",getpid());
pid_t pid = fork();//创建子进程
if(pid < 0)
{
perror("fork");
return -1;
}else if(pid == 0){//子进程
printf("child process:%d\n",getpid());
}else{ //父进程
printf("pid = %d\n",pid);
printf("farther process:%d\n",getpid());
}
}
运行结果:
main:3812
pid = 3813
farther process:3812
child process:3813
【注】:
①孤儿进程:父进程 先于 子进程结束,子进程就成为孤儿进程。
孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
②僵尸进程:子进程 先于 父进程结束,父进程没有回收子进程的资源。
子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
③fork创建子进程时,完整的拷贝父进程的资源,父子进程间的同名数据互不干扰
fork之后父进程内定义的变量在子进程中没有定义。(fork只拷贝fork之前父进程的资源)
④现代unix版的fork也创建新进程,只有子进程需要改变内存中数据时才拷贝父进程。这叫“写操作时拷贝”
3.2 exec函数族:
exec函数提供了一种在进程中启动另一个程序执行的方法,可以根据指定的文件名或目录名找到可执行文件,并用它取代原调用进程的数据段,代码段和堆栈段。除了进程号,其他全部都被替换(相当于夺舍了,哪怕这个夺舍程序结束了,也不会在执行被夺舍的子进程后面的代码)
头文件:
#include<unistd.h>
返回值: -1 :出错
成功不会返回
函数原型: 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 execve(const char *path,char *const argv[],char *const envp[]);
int execlp(const char *file,const char *arg,...);
int execvp(const char *file,const char *argv[]);
例子:
int main()
{
/*调用execlp函数,相当于调用了"ps -ef" 命令*/
if(execlp("ps","ps","-ef",NULL) < 0)
{
perror("execlp error!");
}
return 0;
}
【注】
execl/execv:必须完整地给出 可执行文件或脚本文件的路径
execlp/execvp:自动搜索 可执行文件或脚本文件的路径
3.3 exit结束进程:
函数原型: void exit(int status); //c库函数,结束时会刷新缓冲区
头文件:
#include<stdlib.h>
函数原型: void _exit(int status);//系统调用,不会刷新缓冲区
头文件:
#include<unistd.h>
参数:
status: 是一个整型参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束,其他数值表示出现了错误,进程非正常结束。
可以用wait系统调用接收子进程的返回值,进行相应的处理。
(1)WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)
例如:
int status;
pid_t cid = wait(&status);
printf("status = %d\n",WIFEXITED(status)); //子进程正常退出为非零 非正常退出为零
(2)WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值
如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;
如果子进程调用exit(7)退出,WEXITSTATUS(status)就会返回7。
请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。
3.4 wait和waitpid回收进程:
wait()
函数原型: pid_t wait(int *status)
功能: 调用该函数使进程阻塞,直到任一个子进程结束或者是该进程收到了
一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait
函数会立刻返回
头文件:
#include<sys/types.h>
#include<sys/wait.h>
函数参数:
status是一个整型指针,指向的对象用来保存子进程退出时的状态。
1)status若为空,表示忽略子进程退出时的状态
2)status若不为空,表示保存子进程退出时的状态
返回值: 成功:子进程的进程号
失败:-1
waitpid()
函数原型: pid_t waitpid(pid_t pid,int *status,int options);
功能: 功能和wait函数类似。可以指定等待某个子进程的结束以及等待方式
(阻塞或非阻塞)
头文件:
#include<sys/types.h>
#include<sys/wait.h>
函数参数:
pid: pid>0: 只等待进程ID等于pid的子进程。
pid=-1: 等待任何一个子进程退出,此时和wait作用一样
pid=0: 等待其组ID等于调用进程的组ID的任一子进程
pid<-1: 等待其组ID等于pid绝对值的任一子进程
status: 同wait
options:WNOHANG:若由pid指定的子进程不立即可用,则waitpid不阻塞,此时返回值为0
0: 阻塞父进程,等待子进程退出
返回值: 正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
出错:-1
4.守护进程
ps -axj 可以查看守护进程
步骤
1.创建子进程,父进程退出
2.在子进程中创建新会话
setsid函数:创建一个新的会话,并使当前进程成为新会话组的组长
3.改变当前目录为根目录
通常做法是让“/”或“tmp”作为守护进程的当前工作目录。
在进程运行过程中,当前目录所在的文件系统是不能卸载的。
chdir函数可以改变进程当前工作目录
4.重设文件权限掩码
文件权限掩码是指文件权限中被屏蔽掉的对应位。把文件权限掩码设置为0,
可以增加该守护进程的灵活性。
设置文件权限掩码的函数使umask
通常的使用方法为umask(0)
5.关闭文件描述符
fdtablesize = getdtablesize();
for(fd = 0;fd<fdtablesize;fd++)
{
close(fd);
}
范例:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>
int main()
{
//1.创建孤儿进程
pid_t pid = fork();
if(pid<0)
{
perror("fork");
return -1;
}else if(pid > 0)
{
exit(0);
}else{
//2.创建新的会话
if(setsid()<0)
{
perror("setsid");
}
//3.更改工作目录
chdir("/tmp");
//4.设置文件权限掩码
umask(0);
//5.关闭多余文件描述符
int fdtablesize = getdtablesize();
int fd;
for(fd = 0;fd<fdtablesize;fd++)
{
close(fd);
}
}
}
5.进程间通信方式
5.1传统的进程间通信方式
无名管道(pipe),有名管道(fifo),信号(signal)
5.1.1 无名管道(pipe)
- 只能用于具有亲缘关系的进程之间的通信,半双工的通讯模式,具有固定的读端和写端
- 管道可以看成一种特殊的文件,对于它的读写可以使用文件IO如read,write函数。
- 当一个管道创建时,会创建两个文件描述符fd [0] (读管道),fd [1] (写管道)
- 创建无名管道 pipe
函数原型 | int pipe(int fd[2]); |
---|---|
头文件 | #include<unistd.h> |
函数参数 | fd:包含两个元素的整型数组 |
函数返回 | 成功:0 失败:-1 |
【注】 :
- 管道中无数据时,读操作会阻塞
- 管道缓冲区有空闲区域,写进程会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,写操作会一直阻
塞 - 管道读端存在时,向管道写入数据才有意义,否则向管道写入数据的进程将受到内核传来的SIGPIPE信号(管道破裂
信号)
- 代码示例
/*子进程向管道写入数据,主进程从管道读取数据*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
int main()
{
int fd[2];//fd[0]为写端,fd[1]为读端
if(pipe(fd)<0)//在内核中创建 无名管道
{
perror("pipe");
return -1;
}
pid_t pid;
if((pid = fork())<0)
{
perror("fork");
return -1;
}else if(pid == 0){
char buf[32];
close(fd[0]);//关闭读端
while(1){
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
write(fd[1],buf,strlen(buf));
}
}else{
char buf[32];
close(fd[1]);//关闭写端
while(1){
memset(buf,0,sizeof(buf));
read(fd[0],buf,sizeof(buf));
printf("buf = %s\n",buf);
if(strstr(buf,"quit") != NULL){
//字符串查找函数,在buf地址上查找"quit"字符串,如果找到,返回子串首字母地址,否则返回NULL
//exit(0);
printf("are you okey!?\n");
}
}
}
return 0;
}
5.1.2 有名管道(FIFO)
- 可以使互不相关的两个进程互相通信。可以通过路径名来指出,并且在 文件系统中可见。
- 进程通过文件IO来操作有名管道。
- 有名管道遵循先进先出规则。
- 不支持如lseek的操作。
- 创建有名管道
函数原型 | int mkfifo(const char *filename,mode_t mode); |
---|---|
头文件 | #include<unistd.h> #include<fcntl.h> #include<sys/types.h> |
函数参数 | filename:要创建的管道 mode:指定创建的管道访问权限,8进制表示 |
函数返回 | 成功:0 失败:-1 |
- 代码示例
/*创建管道*/
int res = mkfifo("./5.fifo.txt", 0666);
if(res == -1)
{
perror("mkfifo");
exit(1);
}
/*write进程*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
int fd = open("./5.fifo.txt",O_WRONLY); //只写打开管道文件
if(fd<0){
perror("open");
return -1;
}
pid_t pid;
if((pid = fork())<0){
perror("fork");
return -1;
}else if(pid == 0){
char buf[32];
while(1)
{
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
write(fd,buf,sizeof(buf)); //向管道文件写数据
if(strstr(buf,"quit")!=NULL){
exit(0);
}
}
}else{
wait(NULL);
close(fd);
printf("over\n");
}
return 0;
}
/*read进程*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
int fd = open("./5.fifo.txt",O_RDONLY);//只读打开管道文件
if(fd<0){
perror("open");
return -1;
}
pid_t pid;
if((pid = fork())<0){
perror("fork");
return -1;
}else if(pid == 0){
char buf[32];
memset(buf,0,sizeof(buf));
while(1)
{
if(read(fd,buf,sizeof(buf))>0) //从管道读取数据
{
printf("buf = %s\n",buf);
if(strstr(buf,"quit")!=NULL){
exit(0);
}
memset(buf,0,sizeof(buf));
}
}
}else{
wait(NULL);
close(fd);
printf("over\n");
}
return 0;
}
5.1.3 信号(SIGNAL)
- SIGNAL特点
- 软件层次上对中断机制的一种模拟,是一种异步通信方式
- 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了那些系统事件
- 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它
- 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,至到其阻塞被取消时才被传递给进程
-
信号的生存周期
信号产生(内核进程)—>信号注册(用户进程)—>信号注销(用户进程) -
用户进程对信号的响应方式:
忽略信号:对信号不做任何处理,但SIGKILL和SIGSTOP不能忽略
捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数(也叫注册信号)
执行缺省操作:LINUX对每种信号都规定了默认操作 -
使用信号的场合:
后台进程需要使用信号,如xinetd
如果两个进程没有亲缘关系,无法使用无名管道
如果两个通讯进程之一只能使用标准输入和标准输出,则无法使用FIFO -
kill()和raise()信号发送函数
kill函数同kill系统命令一样,可以发送信号给进程或进程组(kill系统命令指示kill函数的一个用户接口)
kill -l命令查看系统支持的信号列表
数原型 int kill(pid_t pid,int sig);
文件 #include<signal.h>
#include<sys/types.h>数功能 向某个进程发送信号 数参数 pid:正数:要接受信号的进程的进程号
0: 信号被发送到所有和pid进程在同一进程组的进程
-1: 信号发送给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号数返回 成功:0
失败:-1#include<stdio.h> #include<signal.h> #include<sys/types.h> #include<unistd.h> int main() { //向某个进程发送一个信号 kill(getpid(),SIGKILL);//等价于raise(SIGKILL); printf("hello world\n"); return 0; }
raise函数是进程向自己发送信号
数原型 int raise(int sig);
文件 #include<signal.h>
#include<sys/types.h>数功能 进程向自己发送信号 数参数 sig:信号 数返回 成功:0
失败:-1 -
alarm()闹钟函数
数原型 unsigned int alarm(unsigned int seconds);
文件 #include<unistd.h> 数功能 在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALARM信号 数参数 seconds:指定秒数 数返回 成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟剩余时间,否则返回0
出错:-1 -
pause()挂起函数
数原型 int pause(void);
文件 #include<unistd.h> 数功能 用于将调用进程挂起直到受到信号为止 数返回 -1,并且吧error设置为EINTR -
signal()信号处理函数
数原型 void (*signal(int signum,void(*handler)(int)))(int);
文件 #include<signal.h> 数功能 指定要处理的信号和处理函数,对信号接受并处理 数参数 signum: 指定信号
handler: SIG_IGN:忽略该信号
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针数返回 成功:设置之前的信号处理方式(返回值是一个void(*)(int)的函数指针)
出错:-1#include<stdio.h> #include<stdlib.h> #include<string.h> #include<strings.h> #include<signal.h> #include<unistd.h> void myfunc(int signo) { if(signo == SIGINT){ printf("you pressed ctrl+c\n"); }else if(signo == SIGTSTP){ kill(getpid(),SIGKILL); } } int main() { signal(SIGINT,myfunc);//注册信号和信号处理 signal(SIGTSTP,myfunc); while(1); return 0; }