进程
进程是程序的一次执行过程,并且是动态的,包括创建、调度、执行和消亡。
进程的类型
1.交互进程 2.批处理进程 3.守护进程(生命期很长)
进程状态
1.运行态 2.等待态(可中断/不可中断) 3.停止态 4.死亡态(僵尸态)(pcb没有释放)
查看进程信息
ps
ps -ef/aux|grep 关键字 // 查看系统进程快照
top
ls /proc
jobs//查看系统后台进程
bg 作业号 //将挂起的进程在后台运行(ctrl +z)
fg 作业号 //把后台的进程放到前台运行
改变进程优先级
nice -n m 可执行程序 //(m = -20 - 19):优先级 按用户指定的优先级运行进程
renice -n m pid 改变正在运行进程的优先级
//这里要注意普通用户能改变的优先级只是正数
进程创建和结束
#include <unistd.h>
pid_t fork(void);
//成功时父进程返回子进程的进程号,子进程返回0
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
//结束当前的进程并将status的低8位的值返回
//exit()结束进程时会刷新流缓冲区_exit()不会
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);//获取子进程号
pid_t getppid(void);//获取父进程号
父子关系:
子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束 子进程变为孤儿进程和后台进程,被init进程收养
若子进程先结束 如果没有进行回收,子进程变为僵尸进程
进程创建好后,父进程从main函数运行,子进程从fork的下一条语句开始运行
父子进程不确定谁先执行,要看系统调度
使用示例1:
//执行的结果会分别返回父进程和子进程
#include <stdio.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
if((pid = fork())<0){
perror("fork");
return -1;
}
else if(pid == 0)
printf("child process pid=%d\n",getpid());
else
printf("parents process pid = %d\n",getpid());
printf("%d\n",pid);
}
进程回收
pid_t wait(int *status);
//成功时返回子进程的进程号,失败返回EOF
//若子进程没有结束,父进程一直阻塞
//若有多个子进程,那个先结束就先回收
//status:指定保存子进程返回值和结束方式的地址
//status为NULL表示直接释放子进程PCB,不接收返回值
pid_t waitpid(pid_t pid,int *status,int option);
//成功时返回子进程pid表示成功,0表示未结束,失败EOF
//pid可用于指定回收那个子进程或任意子进程(-1)
//option指定回收方式,0或WNOHANG 阻塞或非阻塞
WIFEXITED(status);//判断子进程是否正常结束,返回逻辑真假
WEXITSTATUS(status);//获取子进程返回值
WIFSIGNALED(status);//判断子进程是否被信号结束
WTERMSIG(status);//获取结束子进程的信号类型
使用示例1:
//通过此示例可以看到status的值传入过程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
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("%x\n",status);//以16进制打印
}
}
exec函数族和system
system:不用创建子进程去执行另外一个程序
#include <stdlib.h>
int system(const char *command);
//成功时返回命令command的返回值,失败时返回EOF
//当前进程等待command执行结束后才会继续执行
exec函数族:
1.进程调用exec函数执行某个程序
2.进程当前内容被指定的程序替换
3.实现让父子进程执行不同的程序
int execl(const char *path,const char *arg,..);
int execlp(const char *file,const char *arg,..);
//成功时执行指定的程序,失败时返回EOF
//path执行的程序名称,包含路径
//arg..传递给执行的程序的参数列表 最后一个NULL
//file执行的程序的名称,在PATH中查找
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
//成功时执行指定的程序,失败时返回EOF
//arg..封装成指针数组的形式
使用示例1:
//显示/etc下所有文件的详细信息
if(execl("/bin/ls","ls","-a","-l","/etc",NULL)<0){
perror("execl");
}
if(execlp("ls","-a","-l","/etc",NULL)<0){
perror("execlp");
}
使用示例2:
char *arg[] = {"ls","-a","-l","/etc",NULL};
if(execv("/bin/ls",arg)<0){
perror("execv");
}
if(execvp("ls",arg)<0){
perror("execvp");
}
守护进程(Daemon)
特点:始终在后台运行、独立于任何终端、周期性的执行某种任务或等待处理特定事件,linux通常以会话(session)、进程组的方式管理守护进程。通常在系统启动时运行,系统关闭时结束。
守护进程创建(1):
创建子进程,父进程退出
if(fork()>0){
exit(0);
}
子进程变为孤儿进程,被init进程收养
子进程在后台运行(但此时还依赖于终端)
守护进程创建(2):
子进程创建新会话
if(setsid()<0){
exit(-1);
}
子进程成为新的会话组长,子进程脱离原来的终端
守护进程创建(3):
更改当前的工作目录
chdir("/");//可读
chdir("/tmp");//可读可写可执行
守护进程一直在后台运行,其工作目录不能被卸载,重新设定当前 的工作目录
守护进程创建(4):
重设文件权限掩码
if(umask(0)<0){//不会屏蔽任何权限位
exit(-1);
}
掩码的设置只会影响当前进程
守护进程创建(5):
关闭打开的文件描述符(父进程打开的文件子进程所继承过来的)
int i;
for(i = 0;i<getdtablesize();i++){
close(i);
}
已经脱离终端,stdin/stdout/stderr无法再使用
代码实现:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
int main(){
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);
}
}
线程
线程就是轻量级进程LWP,同一个进程中的线程共享相同的地址空间,Linux不区分进程、线程,都认为是一个任务。这样大大提高了任务切换的效率,避免了额外的TLB & cache的刷新。
共享资源&私有资源
共享资源:可执行的指令、静态数据、进程中打开的文件描述符、当前工作目录、用户ID、用户组ID
私有资源:线程id(TID),PC(程序计数器)和相关寄存器、堆栈、错误号(errno)、优先级、执行状态和属性
创建线程
以下都是利用线程库来实现的:
#include <pthread.h>
int pthread_creat(pthread_t *thread,const pthread_attr_t *attr,void *(*routine)(void *),void *arg);
//成功返回0,失败返回错误码
//thread:线程对象
//attr:线程属性,NULL代表默认属性
//routine:线程执行的函数
//arg:传递给routine的参数,没有的话NULL
结束线程
#include <pthread.h>
void pthread_exit(void *retval);/return ;
//retval可被其他线程通过pthread_join获取
//线程私有资源被释放
//主进程结束,所有的线程也会结束。
回收线程
#include <pthread.h>
int pthread_join(pthread_t thread,void **retval);
//成功返回0,失败返回错误码
//thread要回收的线程对象
//调用线程阻塞直到thread结束
//*retval接收线程thread的返回值
用法示例1:
//线程创建和回收
//gcc -o pthread1 pthread1.c -lpthread 编译的时候要链接库
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char message[32] = "hello world";
void *thread_func(void *arg);
int main(void)
{
pthread_t a_thread;
void *result;
if(pthread_create(&a_thread,NULL,thread_func,NULL)!=0){
printf("failed to create\n");exit(-1);
}
pthread_join(a_thread,&result);
printf("result if %s\n",result);
printf("message is %s\n",message);
return 0;
}
void *thread_func(void *arg)
{
sleep(1);
strcpy(message,"marked by thread");
pthread_exit("thank you for waiting for me");
}