C 程序和进程
程序:是一些二进制、数据的有序集合,没有被加载到内存。没有“生命”
进程:程序执行的一次的过程,资源分配的总成
程序运行状态
- 运行态:此时进程或者正在运行,或者准备运行
- 等待态:(挂起)此时进程在等待一个事件的发生或某种系统资源
- 可中断
- 不可中断
- 停止态:此时进程被中止
- 死亡态:(僵尸态)这是一个已终止的进程,但还在进程还在进程向量组中占有一个task_struct结构
进程状态图

进程相关命令
ps
- ps -ef
- ps -aux 有个状态STAT标识
top
- 用于实时显示 process 的动态
nice
- 以更改过的优先序来执行程序,如果未指定程序,则会印出目前的排程优先序,内定的 adjustment 为 10,范围为 -20(最高优先序)到 19(最低优先序)
renice
- 用于重新指定一个或多个行程(Process)的优先序(一个或多个将根据参数而定)
kill
- 用于删除执行中的程序或工作
bg
- 要先jobs获取一个序号,然后用bg 序号,把程序移入后台
fg
- 和bg用法一样
jobs
- 查看终端窗口放入后台的进程。参数-l:列出进程的PID;-p:只列出进程的PID;-r:只列出运行中的进程;-s:只列出已停止的进程。
系统调用
fork
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
//功能:创建一个子进程出来
//返回值:0表示子进程,大于0表示父进程,-1出错
注意:子进程拷贝父进程空间,
vfork
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
//功能:创建一个子进程
//返回值:0表示子进程,大于0表示父进程,-1出错
注意:一定是子进程先运行,并且子进程不结束exit()或者不调用exec函数簇,父进程不会正确运行(应该是同一片空间,子进程不释放,父进程就不能访问,故所以会出现段错误)
写实拷贝
exec函数族
#include <unistd.h>
int execl(const char *path, const char *arg, ...)
int execlp(const char *path, const char *arg, ...)
int execle(const char *path, const char *arg, ..., char *const envp[])
int execv(const char *path, char *const argv[])
int execvp(const char *file, char *const argv[])
int execve(const char *path, char *const argv[], char *const envp[])
-
简单来说,
exec函数族都是以exec开头,后面跟的不同字母表示不同的涵义:-
l表示list,指代的是命令行参数列表。 -
p表示path,指定搜索文件file时所使用的path变量。 -
v表示vector,指代的是命令行参数数组。 -
e表示environment,指代的是环境变量数组。
-
-
函数参数
-
path表示要执行的程序路径,可以是绝对路径或是相对路径。 -
file表示要执行的程序名称,如果该参数中包含/字符则视为路径名并直接执行,否则则视为单独的文件名,系统将会根据环境变量PATH中设置的路径顺序去搜索指定的文件。 -
argv表示命令行参数的矢量数组 -
envp表示带有该参数的exec函数可以在调用时指定一个环境变量数组,其他不带该参数的exec函数则使用调用进程的环境变量。 -
arg表示程序的第0个参数,也就是程序名本身,相当于argv[0]。 -
...表示命令行参数列表,调用相应程序时有多少个命令行参数就需要有多少个输入参数项。
-
-
返回值
- 函数执行成功不会返回,若执行失败则返回-1,失败原因会记录在
error中。
- 函数执行成功不会返回,若执行失败则返回-1,失败原因会记录在
事实上,这6个函数中真正的系统调用只有execve函数,其它5个都是库函数,它们最终都会调用execve这个系统调用。
子进程空间被exec调用的函数覆盖
进程退出
exit
void exit(int status);
功能:主动结束进程
status:进程退出时告诉别人一个状态(回收它的人)
_exit
void _exit(int status);
功能:主动结束进程
status:进程退出时告诉别人一个状态(回收它的人)
区别:exit退出时会清空缓冲,_exit不清空IO缓冲区
进程回收·
wait
pid_t wait(int *wstatus);
pid_t wait(int *wstatus);
//功能:回收已经处于僵尸态(已经结束)的进程
//返回值:成功返回回收的子进程ID号,失败返回-1
wstatus:子进程结束时要传递的状态。如果不在意子进程退出的状态就写NULL
父进程调用wait回收子进程的资源,子进程如果执行完了父进程才会从wait继续执行,否则wait会阻塞

waitpid
pid_t waitpid(pid_t pid, int *wstatus, int options);
//功能:回收已经结束的子进程
//返回值:如果成功回收返回子进程的ID,如果是非阻塞情况下没有子进程结束,返回0.失败返回-1
pid:指定要回收的进程号
> 0 表示一个具体的进程ID
-1 表示可以回收任意子进程
wstatus:子进程退出时的状态
options:waitpid的工作模式,可以是阻塞也可以非阻塞
0表示阻塞,WNOHANG表示非阻塞
守护进程
创建步骤
创建子进程,退出父进程使子进程被系统收养
- fork()
子进程创建新会话,目的在于脱离终端
- setsid()
改变工作目录以及文件权限掩码(可以不做,不做就是在本目录)
- chdir("/temp");
- umask(0);
关闭所有的文件描述符
for(int i = 0; i< getdtablesize();i++){ close(i); } //getdtablesize()是获取最大的文件标识符
例子
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
pid_t pt=fork();
if(pt<0){
perror("fork()");
}else if(pt>0){
exit(0);
}else{
setsid();
if(0!=chdir("/tmp")){
perror("chdir");
}
for(int i=0;i<getdtablesize();i++){
close(i);
}
umask(0);
time_t tt;
FILE *f=fopen("log_time.txt","a+");
while(1){
time(&tt);
fprintf(f,ctime(&tt));
sleep(1);
}
}
return 0;
}
线程
概念:轻量级的进程,任务调度的最小单位。
为了方便任务的切换,线程们共享同一个进程的空间。那么切换同一个进程下的任务时就不需要再切换内存了
线程的创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
//功能:在系统中创建出来一个线程(任务)
//返回值:成功返回0,失败返回非0
thread:线程对象
attr:线程属性,一般使用系统提供的默认属性,写NULL
start_routine:线程要去执行的函数名
arg:要传递给start_routine所指向函数的参数
线程退出
void pthread_exit(void *retval);
//功能:主动退出一个线程
//retval:退出时要传递的信息
线程回收(等待)
int pthread_join(pthread_t thread, void **retval);
//功能:阻塞等待子线程的退出
//返回值:成功返回0,失败返回错误号
thread:要回收的子线程
retval:用于存放子线程退出时指向的地址空间
线程分离
int pthread_detach(pthread_t thread);
//功能:使当前线程和thread表示的线程断开联系,让系统当养父
//返回值:成功返回0,失败返回错误号
thread0:要分离的线程
💀gcc编译与线程相关时需要加上-lpthread这个库
线程间通讯方式
同步和互斥
同步
- 多个线程按照约定好的顺序先后执行
互斥
- 相互排斥,只能存在一个
互斥锁
- 表示系统中的一种资源,主要用于保护代码,这种资源争抢到个数就会减一,释放出来就会加一
//初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
//申请锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
练习
#include <stdio.h>
#include <string.h>
#include <pthread.h>
int a=0;
int b=0;
pthread_mutex_t pmt;
void *compare(){
//void *compare(){
while(1){
#ifdef _LOCK_
pthread_mutex_lock(&pmt);
#endif
if(a!=b){
printf("a=%d , b=%d\n",a,b);
}
#ifdef _LOCK_
pthread_mutex_unlock(&pmt);
#endif
}
}
int main(int argc,char *argv[])
{
pthread_mutex_init(&pmt,NULL);
int count=0;
pthread_t pt;
int flag=pthread_create(&pt,NULL,compare,NULL);
if(flag!=0)
perror("pthread_create");
while(1){
#ifdef _LOCK_
pthread_mutex_lock(&pmt);
#endif
count++;
a=count;
b=count;
#ifdef _LOCK_
pthread_mutex_unlock(&pmt);
#endif
}
return 0;
}
信号量
用于表示系统中的某种资源,这种资源的个数可以不定。需要程序申请或者释放资源
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
//功能:在系统中创建出来一种资源并初始化个数
sem:信号量对象
pshared:0 表示信号量作用于线程之间,非0表示用于进程之间
value:信号量初始|个数
消费者(P操作):
#include <semaphore.h>
int sem_wait(sem_t *sem);
//功能:向系统申请某个资源的使用权,使系统中这种资源的个数-1,如果系统中这个资源的个数为0,程序会阻塞等待资源,直到可以申请到该资源
//返回值:成功返回0,失败返回-1
sem:要操作的信号量对象
生产者(V操作):
#include <semaphore.h>
int sem_post(sem_t *sem);
//功能:向系统提交一个资源,使系统中这种资源的个数+1
//返回值:成功返回0,失败返回-1
sem:要操作的对象
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>
char a[20]={-1};
sem_t st_w;
sem_t st_r;
void * count_length(){
while(1){
sem_wait(&st_w);
printf("length = %ld\n",strlen(a)-1);
sem_post(&st_r);
}
}
int main(int argc,char *argv[])
{
pthread_t pt;
sem_init(&st_w,0,0);
sem_init(&st_r,0,1);
pthread_create(&pt,NULL,count_length,NULL);
while(1){
sem_wait(&st_r);
fgets(a,sizeof(a)-1,stdin);
sem_post(&st_w);
}
return 0;
}
进程间的通讯
无名管道
struct fd_pair {
long fd[2];
};
int pipe(int fd[2]);
//功能:在系统中创建一个特殊的管道文件(文件没有实体),只能够用于亲缘间的进程
//返回值:成功返回0,失败返回-1
fd:用于存放管道读端和写端的文件描述符
⬅️fd[0]为读取端(读端)
➡️fd[1]为写入端(写端)
//一定要在fork()之前用pipe

上面例子中,父进程中的read,会卡住(如果不加sleep(5)),因为子进程中write写入后,马上就read(fd[0]),会把内容从fd[0]中读出,所以fd[0]中没有东西可读就会卡主。
读端
写段存在:
如果管道中有数据,那么read可以正常读取,并且返回读取到的字节数;如果管道中没有数据,read会阻塞等待管道中的数据(例子见上面)
写端不存在:
如果管道中有数据,read还是正常读取;管道中没有数据read直接返回0,表示已经关闭
写端
读端存在:
如果剩余空间大于要写入的字节数,write正常写入;如果管道剩余空间比写入的字节数小,那么write就会写一部分进去然后阻塞。
读端不存在:
往管道里面写入数据会使管道破裂
注意:管道中的数据是临时的,可以被读走,当读取了管道的内容时,管道中被读的内容会消失
//读端不存在,往管道里面写数据,管道就会破裂
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdbool.h>
int main(int argc,char *argv[])
{
int fd[2];
pipe(fd);
close(fd[0]);
pid_t pid=fork();
if(pid<0){
perror("fork");
return -1;
}else if(pid==0){
char ch =10;
write(fd[1],&ch,1);
}else{
int status;
wait(&status);
if(WIFEXITED(status)==true){
printf("normal\n");
}else{
printf("abnormal\n");
if(WIFSIGNALED(status)==true){
printf("sinal killed\n");
printf("signal no.%d\n",status & 0x7f);
}else{
printf("none signal\n");
}
}
}
return 0;
}

有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
//功能:创建一个管道文件
//返回值:成功返回0,失败返回-1
pathname:文件名(包含路径)
mode:创建文件时的权限 0777
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
//创建一个管道文件,然后再另外一个终端对该管道进行写入内容,然后该程序把内容读出来
int main(int argc,char *argv[])
{
char buf[50];
int mkfifo_o=mkfifo("/tmp/test_mkfifo.txt",0775);
if(mkfifo_o == -1){
if(errno != EEXIST){//errno表示错误号 EEXIST 表示是否存在
perror("mkfifo");
return -1;
}
}else{
printf("create success\n");
}
while(1){
memset(buf,0,sizeof(buf));
int fifo=open("/tmp/test_mkfifo.txt",O_RDWR); read(fifo,buf,sizeof(buf));
printf("%s\n",buf);
}
return 0;
}
信号
软件模拟的终端
kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
//功能:向一个进程发一个信号
//返回值:成功0,失败-1
pid:要接收信号的进程
>0:一个具体的进程号
-1:除了系统进程外的所有进程
sig:要发送的信号--->信号类型间signal中的列表
//向自己发送信号:kill(getpid(), 2);
raise
#include <signal.h>
int raise(int sig);
//功能:向自己发送一个信号
//返回值:成功返回0,失败返回-1
sig:要发送的信号--->信号类型间signal中的列表
alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//功能:给进程添加一个定时器开始计时
//返回值:第一次定时返回0,之后的定时都会返回上一次定时器还剩多少秒
seconds:定时多少秒
0取消定时器功能
注意:一个进程中只能存在1️⃣定时器
可以用pause等待
pause
#include <unistd.h>
int pause(void);
//功能:暂停进程,除非有新的信号(事件)产生程序才会被唤醒
//返回值:成功返回0,失败返回-1
⭐️ signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//功能:注册某个信号的处理方式
signum:要注册的信号
handler:要处理的方式
自定义:处理
SIG_IGN:忽略这个信号
SIG_DFL:使用这个信号默认的处理方式
🕐小栗子
//会等待5s然后输入sorry , the connection time_out ....
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void time_out(int signal){
if(signal ==SIGALRM){
printf("sorry , the connection time_out ....\n");
}
}
int main(){
alarm(5);
signal(SIGALRM,time_out);
while(1);
return 0;
}0
🐧大例子见本文最后一个例子
在Linux中,使用kill -l查看所有信号宏
常见的信号含义
- SIGHUP 终端关闭会发送时(挂起)
- SIGINT 终止当前进程时(ctrl + c)
- SIGQUIT 停止当前进程时(ctrl +
\)- SIGKILL 杀死进程时(并且不能被阻塞、处理和忽略)
- SIGILL 文件本身出错,或者数据段、堆栈溢出时,也不能忽略
- SIGFPE 算术运算错误时(溢出、除数为0)时
- SIGUSR2 用户自定信号
- SIGALRM 闹钟信号 由int alarm(int)发出
- SIGSTOP ctrl+z 暂停进程 转换成后台进程
- SIGCHLD 子进程改变状态时,父进程会收到这个信号
- SIGABORT 用于结束进程
waitpid
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
📝//pid的值
/*
The value of pid can be:
💬<-1 meaning wait for any child process whose process group ID is equal to theabsolute value of pid.
⏩-1 meaning wait for any child process.
⏩ 0 meaning wait for any child process whose process group ID is equal to that of the calling process at the ti me of the call to waitpid().
⏩>0 meaning wait for the child whose process ID is equal to the value of pid.
*/
📝//参数options
/*
The value of options is an OR of zero or more of the following constants:
⏩WNOHANG
return immediately if no child has exited.
⏩WUNTRACED
also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified.
⏩WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery of SIGCONT.
*/
练习
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
void signalhander(int signum){
if (signum==SIGCHLD){
int status;
waitpid(-1,&status,WNOHANG);
printf("type:%d\n",status & 0x7f);
}
}
int main(int argc,char *argv[])
{
int fd=fork();
if (fd==-1){
perror("fork");
return -1;
} else if(fd==0){
sleep(5);
printf("5s is over\n");
// fflush(stdout);
raise(SIGKILL);
}else{
signal(SIGCHLD,signalhander);
int i=1;
while(1){
printf("%d\n",i++);
sleep(1);
}
}
return 0;
}
