一,进程相关概念
1、什么是进程
程序:有一定逻辑的语句的集合,一般保存在文件中
运行程序时,系统会给程序映射一个虚拟的独立内存空间,程序就在该内存空间中运行,程序在内存空间中运行的过程,成为一个进程。
2、进程和程序的区别
程序:
是静态的
是一些保存在磁盘上的指令的有序集合
没有任何执行的概念
进程:
是一个动态的概念,
是程序执行的过程
包括创建、调度和消亡
3、进程的特征
动态性:
是程序的一次执行过程
并发性:
多个进程可以同时运行
独立性:
每个进程有自己的独立虚拟内存空间
异步性:
多个进程的运行是可以没有预先预定,各自运行。
4、进程的分类
交互进程
该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。
批处理进程
该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
例如:shell脚本程序:
//创建脚本文件:shell.sh
ls -l
cd ~
ls -l
cd 22071/process/
touch 1.txt
ls -l
//修改脚本文件权限
chmod a+x shell.sh
//运行脚本文件
$ ./shell.sh
守护进程
该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
二,进程系统调用(api函数)
1、创建进程
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
调用fork函数时,会做以下几件事:
1》映射新的虚拟内存空间,把原来的虚拟空间对应的进程称为父进程,新虚拟空间对应的进程称为子进程。
2》将父进程中的所有数据段中的数据拷贝到子进程对应的数据段中
3》父进程和子进程共享代码段
4》fork()给父进程返回子进程的ID号,给子进程返回:0
5》父子进程同时从fork()函数调用语句的下一条语句开始执行。
例如:
int main(int argc,char **argv)
{
pid_t pid;
//fork();
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if(pid > 0){
while(1){
printf("父进程:hello world\n");
sleep(1);
}
}else{
while(1){
printf("子进程:farsight\n");
sleep(1);
}
}
printf("welcome to farsight\n");
return 0;
}
2、获取进程的ID和父进程的ID
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); //获取当前进程的ID
pid_t getppid(void); //获取父进程ID
例如:
int main(int argc,char **argv)
{
pid_t pid;
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if(pid > 0){
printf("ppid = %d\n",getppid());
printf("父进程%d:hello world\n",getpid());
}else{
printf("ppid = %d\n",getppid());
printf("子进程%d:farsight\n",getpid());
}
return 0;
}
3、加载另一个程序在进程空间中执行
exec函数族:
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, 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[]);
//以上函数的参数含义参考: 5_加载另一个程序执行的方法.tif
例如:
int main(int argc,char **argv)
{
pid_t pid;
//创建子进程
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if(pid > 0){
while(1){
printf("我是拜登他爸爸!\n");
sleep(1);
}
}else{ //子进程执行ls -l ~/
#if 0
//execl("/bin/ls","ls","-l","/home/farsight/",NULL);
//execlp("ls","ls","-l",NULL);
char *argv[] = {"ls","-l",NULL};
//execv("/bin/ls",argv);
execvp("ls",argv);
#else
char *environ[] = {"name=rose","passwd=123","home=/home/rose",NULL};
//execle("/home/farsight/22071/process/day01_code/env","./env",NULL,environ);
char *argv[] = {"./env",NULL};
execve("/home/farsight/22071/process/day01_code/env",argv,environ);
#endif
}
return 0;
}
4、结束进程
#include <unistd.h>
void _exit(int status); //系统调用:直接结束进程
#include <stdlib.h>
void exit(int status); //库函数:在结束进程之前,先刷新缓冲区,释放缓冲空间,然后再结束进程
//参数 ---- 进程结束的状态:0 ----正常结束,非0 ---- 异常结束
例如:
int main(int argc,char **argv)
{
printf("hello world");
//_exit(0);
exit(0);
return 0; //在main函数中执行return语句时,会自动调用exit(0);
}
5、给子进程收尸,让父进程挂起(阻塞)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
//参数 ----- 获取子进程结束的状态
//返回值-----成功:收尸的子进程的ID,失败:-1
//给任意一个子进程收尸,如果有多个子进程,谁先结束先给谁收尸,如果子进程没有结束,父进程一直阻塞,
//如果当进程调用wait时,该进程没有子进程,则wait不阻塞,立即返回。
例如:
int main(int argc,char **argv)
{
pid_t pid1,pid2;
int i;
if((pid1 = fork()) < 0){
perror("fork");
exit(1);
}else if(!pid1){
//子进程1
for(i = 0; i < 5; i++){
printf("我是大儿子!\n");
sleep(1);
}
exit(0);
}
if((pid2 = fork()) < 0){
perror("fork");
exit(1);
}else if(!pid2){
//子进程2
for(i = 0; i < 10; i++){
printf("我是小儿子!\n");
sleep(1);
}
exit(0);
}
//父进程
wait(NULL); //给任意一个子进程收尸,如果子进程没有结束,则父进程阻塞
printf("我是爸爸!\n");
return 0;
}
pid_t waitpid(pid_t pid, int *wstatus, int options); //给指定的子进程收尸
//参数1 ----- pid :
pid>0:只等待进程ID等于pid的子进程,不管已经有其他子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1:等待任何一个子进程退出,此时和wait作用一样。
pid=0:等待其组ID等于调用进程的组ID的任一子进程。
pid<-1:等待其组ID等于pid的绝对值的任一子进程。
//参数2 ----- 获取子进程结束的状态
//参数3 ----- 表示waitpid的模式:
0 ---必须等待子进程结束,子进程没结束,则一直阻塞
WNOHANG:如果子进程没有结束,则waitpid不阻塞,此时返回值为0
例如:
int main(int argc,char **argv)
{
pid_t pid1,pid2;
int i;
if((pid1 = fork()) < 0){
perror("fork");
exit(1);
}else if(!pid1){
//子进程1
for(i = 0; i < 5; i++){
printf("我是大儿子!\n");
sleep(1);
}
exit(0);
}
if((pid2 = fork()) < 0){
perror("fork");
exit(1);
}else if(!pid2){
//子进程2
for(i = 0; i < 10; i++){
printf("我是小儿子!\n");
sleep(1);
}
exit(0);
}
//父进程
//wait(NULL);
waitpid(pid2,NULL,0); //给指定的子进程收尸
printf("我是爸爸!\n");
return 0;
}
三、守护进程
1、概念
1、守护进程,也就是通常所说的Daemon进程
是Linux中的后台服务进程。
它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件,守护进程常常在系统引导装入时启动,在系统关闭时终止,Linux系统有很多守护进程,大多数服务都是用守护进程实现的。
2、终端
在Linux中,每一个系统与用户进行交流的界面称为终端
每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端
当控制终端被关闭时,相应的进程都会被自动关闭。
2、守护进程创建步骤
void daemon_init(void)
{
pid_t pid;
//1,创建子进程
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if(pid > 0)
exit(0);
//2,创建新会话
if(setsid() < 0){
perror("setsid");
exit(1);
}
//3,创建子进程
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if(pid > 0)
exit(0);
//4,关闭所有的文件描述符
int i,max_fd = sysconf(_SC_OPEN_MAX);
for (i = 0; i < max_fd;i++)
close(i);
//5,消除umask影响
umask(0);
//6,设置守护进程的工作目录
chdir("/");
//7,将标准输入,标准输出,标准错误重定向到/dev/null
open("/dev/null",O_RDWR);
dup(0);
dup(0);
}
四、系统日志
1、概念
linux系统提供了记录进程运行过程中的错误的信息的方法,即将错误信息记录在一个日志文件,这个日志文件就是系统日志
日志文件在:/var/log/syslog
2、如何记录进程的错误信息到系统日志文件
打开系统日志
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
//参数1 ------- 字符串,表示进程信息的标记
//参数2 ------- 选项:
LOG_CONS 如果向日志中写信息时出错,将信息打印到控制台
LOG_NDELAY 立即发送信息
LOG_NOWAIT 不给子进程收尸,立即写信息到日志
LOG_PERROR 在写日志的同时,向标准错误写数据
LOG_PID 在日志信息中添加进程的ID号
//参数3 ------- 进程的类型
LOG_DAEMON ----守护进程
LOG_FTP ----ftp进程
LOG_KERN ----内核进程
LOG_LPR ----打印系统
LOG_MAIL ----邮件系统
3、向日志文件中记录信息
void syslog(int priority, const char *format, ...);
//参数1 ------ 消息的优先级:
LOG_EMERG 系统级的错误
LOG_ALERT 应当立即纠正的错误
LOG_CRIT 紧急错误
LOG_ERR 一般错误
LOG_WARNING 警告
LOG_NOTICE 需要注意的问题
LOG_INFO 正常信息
LOG_DEBUG 调试信息
//参数2 ----- 消息的格式控制串,类似于printf的第一个参数
//变参 ------ 类似于printf中的变参。
关闭系统日志
void closelog(void);
例如:
int main(int argc,char **argv)
{
//让守护进程每隔一秒钟向文件中写一条时间信息
FILE *fp;
time_t tm;
char buf[100];
daemon_init(); //初始化守护进程
//打开日志
openlog("jjj",LOG_PID,LOG_DAEMON);
if((fp = fopen("1.txt","r")) == NULL){
//perror("fopen");
syslog(LOG_ERR,"fopen:%s\n",strerror(errno)); //将进程中的错误信息记录到日志中
exit(1);
}
while(1){
time(&tm);
sprintf(buf,"%s",ctime(&tm));
fputs(buf,fp);
fflush(fp);
sleep(1);
}
closelog(); //关闭系统日志
return 0;
}
//查看日志中的进程信息
farsight@ubuntu:~/22071/process/day02_code$ grep jjj /var/log/syslog -rn
832:Aug 18 20:44:27 ubuntu jjj[7255]: fopen:No such file or directory
五、文件锁
1、概念
文件锁:利用给文件上锁来解决进程之间的互斥问题
2、给整个文件上锁
#include <sys/file.h>
int flock(int fd, int operation);
//参数1 ----- 文件描述符
//参数2 ----- 锁的类型:
LOCK_SH -----共享锁
LOCK_EX -----互斥锁
LOCK_UN -----解锁
//返回值 ---- 成功:0,失败:-1
例如:
int main(int argc,char **argv) int main(int argc,char **argv)
{ {
int fd; int fd;
int i; int i;
//打开某个用于上锁的文件 //打开某个用于上锁的文件
if((fd = open("1.txt",O_RDWR|O_CREAT,0666)) < 0){ if((fd = open("1.txt",O_RDWR|O_CREAT,0666)) < 0){
perror("open"); perror("open");
exit(1); exit(1);
} }
//先给文件上锁,才能上厕所
if(flock(fd,LOCK_EX) < 0){ //先给文件上锁,才能上厕所
perror("flock"); if(flock(fd,LOCK_EX) < 0){
exit(1); perror("flock");
} exit(1);
}
//要上一号坑
for(i = 0; i < 10; i++){ //要上一号坑
printf("我是鹿晗,我正在一号坑位上厕所...\n"); for(i =0 ; i < 10; i++){
sleep(1); printf("我是蔡徐坤,我正在一号坑位上厕所...\n");
} sleep(1);
}
//解锁
if(flock(fd,LOCK_UN) < 0){ //解锁
perror("flock"); if(flock(fd,LOCK_UN) < 0){
exit(1); perror("flock");
} exit(1);
printf("上完厕所真舒服!\n"); }
printf("上完厕所真舒服!\n");
return 0; return 0;
} }
3、给文件的某个区域上锁
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
//参数1 ------ 文件描述符
//参数2 ------ 功能:
复制文件描述符: F_DUPFD
设置或获取文件描述符flags F_GETFD F_SETFD
设置或获取文件状态flags F_GETFL F_SETFL
设置或获取文件锁: F_SETLK, F_SETLKW, and F_GETLK
struct flock {
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END 文件锁偏移的起始位置*/
off_t l_start; /* Starting offset for lock 相对l_whence偏移的字节数,也就是锁的起始位置*/
off_t l_len; /* Number of bytes to lock 锁的区域大小*/
pid_t l_pid; /* PID of process blocking our lock (set by F_GETLK and F_OFD_GETLK) 上锁的进程的ID*/
...
};
//变参 -----根据参数2,变参的类型会有所不同
例如:
//先上锁
struct flock fl = {
.l_type=F_WRLCK,
.l_whence=SEEK_SET, //文件锁偏移的起点
.l_start=1024, //相对上面成员的偏移量,也就是锁的起始位置
.l_len=2048, //锁的区域大小
};
if(fcntl(fd,F_SETLKW,&fl) < 0){
perror("fcntl");
exit(1);
}
//访问互斥资源的代码段
......
......
......
//解锁
fl.l_type = F_UNLCK;
if(fcntl(fd,F_SETLK,&fl) < 0){
perror("fcntl");
exit(1);
}