linux编程基础复习

库函数和系统调用的区别:
1.库函数可以理解为是对系统调用的一层封装。
2.系统调用作为内核提供给用户程序的接口,它的执行效率是比较高效而精简的。
3.但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数再提供给程序员,更方便于程序编码。

Linux系统中进程状态转换:
Linux操作系统有三个最基本的状态:
运行态:进程占用CPU,并在CPU上运行
就绪态:进程已经具备运行条件,但是CPU还没有分配过来
阻塞态:进程因等待某件事发生而暂时不能运行
进程在一生中,都处于上述3中状态之一。
三种状态之间转换分为四种情况:
运行---》就绪:这是调度引起的,主要是进程占用CPU的时间过长
就绪---》运行:运行进程的时间片用完,调度就转到就绪队列中选择合适的进程分配CPU
运行---》阻塞:发生了I/O请求或等待某件事的发生
阻塞---》就绪:进程所等待的事件发生,就进入就绪队列
以上4种情况可以相互正常转换。

文件

相关函数:

//创建  返回一个文件描述符 或者 -1:不成功
open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);
or
create(const char *pathname, mode_t mode);

//读   返回值有整数:请求读取的字节数 或者 0 或者 -1:出错
read(int fd, void *buf, size_t count);

//写  返回写入的字节数或者 -1
write(int fd, void *buf, size_t count);

//修改文件偏移量  返回新的偏移量 或者 -1:不成功
lseek(int fd, off_t offset, int whence);

//关闭文件  返回0:成功 或者 -1:不成功
int close(int fd);

//获取文件属性  返回0:成功 或者 -1:不成功
stat(const char *path, struct stat *buf);

//测试权限  返回0:成功 或者 -1:不成功
access(const char *pathname, int mode);

//修改权限  返回0:成功 或者 -1:不成功
chmod(const char *path, mode_t mode);

//修改文件大小 返回0:成功 或者 -1:不成功
truncate(const char *path, off_t length);

//从文件读取一行送到缓冲区的首地址s,size是缓冲区的长度
fgets(char *s,int size,FILE *stream)
 
//向指定文件写入一个字符串
fputs(const char *s,FILE *stream)

mode第一位默认0,和suid和guid相关特殊权限需要更改,后面三组权限,第一组是u所有者,第二组是g所属组,第三组是o其他人 ,r :读4,w: 写2 ,x: 执行1。

open函数创建一个新文件时,如果指定是0777,实际创建的文件权限是0775(rwxrwxr-x),因为收到文件权限掩码的限制,限制其它用户的写权限,防止文件被篡改。

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0、标准输出1、标准错误2,因此新创建的文件返回的文件描述符大于2。

读取时出现找不到命令,由于输入内容超过读取内容,剩余内容保存在内核的终端设备输入缓冲区
,读取进程结束,Shell进程继续从终端读走剩下的内容和换行符,把它当成一条命令,出现找不到命令。

进程

相关函数:

//创建进程 父进程的fork函数会返回子进程的pid,子进程的fork函数会返回0.不成功:若子进程创建失败,原程序不会复制,父进程的fork函数返回-1。
fork(void);

//exe函数族
execl(const char *path, const char *arg, ...);
execlp(const char *file, const char *arg, ...);
execle(const char *path, const char *arg, ..., char * const envp[]);
execv(const char *path, char * const argv[]);
execvp(const char *file, char * const argv[]);
execve(const char *path, char * const argv[], char * const envp[]);

//示例
char *arg[]={"-a","-l","test.c", NULL};	
execvp("ls", arg);
or
execlp("ls","-a","-l","test_exec.c",NULL);

//进程退出 
_exit(int status);
//对_exit进行了包装  先检查文件的打开情况,将缓冲区中的内容写回文件。相对来说exit比_exit更为安全
exit(int status);

//挂起进程 使其阻塞  返回子进程id:成功 或者 -1:不成功
wait(int *status);

//判断子进程是否正常退出,若是,返回非0值,否则返回0
WIFEXITED(int status);
//和WIFEXITED配合使用,WIFEXITED返回非0值,则使用该宏提取子进程的返回值。
WEXITSTATUS(int status);

//等待指定的子进程,也可以在父进程不阻塞的情况下获取子进程的状态。
//返回捕捉到的子进程id:成功 或者 0:options = WNOHANG, waitpid发现没有已退出的子进程可回收 或者-1:出错
waitpid(pid_t pid, int *status, int options);

在多次运行后出现终端出现后依旧输出进程信息,因为终端与其它进程公平竞争CPU因此终端出现后可能仍然有进程信息输出。

多次执行fork会发现,child process后输出的ppid不等于parent process的pid,而等于1。
出现这种情况,是因为父进程先于子进程终止,子进程变成“孤儿进程”,后面由init进程来接收。

在Linux系统中,子进程应由父进程回收,但是当子进程被创建后,它与它的父进程及其它进程共同竞争系统资源,所以父子进程执行的顺序是不确定的,终止的先后顺序也是不确定的。所以会导致子进程的编号不递增。

孤儿进程:
父进程负责回收子进程,如果父进程在子进程退出之前退出,子进程就会变成孤儿进程,此时init进程将代替父进程完成子进程的回收工作。

僵尸进程:
调用exit函数后,该进程不会马上消失,而是留下一个称为僵尸进程的数据结构。它几乎放弃进程退出前占用的所有内存,既没有可执行代码也不能被调度,只是在进程列表中保留一个位置,记载进程的退出状态等信息供父进程回收。若父进程没有回收子进程的代码,子进程将会一直处于僵尸态。

僵尸进程不能再次被运行,会占用一定的内存空间,并占据进程编号,当僵尸进程较多时,将会消耗系统内存,新进程可能因内存不足或无法获取pid而无法创建。

当僵尸进程的父进程被终止后,僵尸进程将作为孤儿进程被init进程接收,init进程会不断调用wait()函数获取子进程状态,对已处于僵尸态的进程进行处理。

孤儿进程永远不会成为僵尸进程。

终端、会话、进程组和进程之间的关系:
1、一个会话对应一个控制终端。
2、一个会话又可以包含多个进程组。
3、进程组中的这些进程之间不是孤立的,彼此之间或者存在者父子、兄弟关系,或者在功能有相近的联系。
4、每个进程必定属于一个进程组,也只能属于一个进程组。一个进程除了有进程ID外,还有一个进程组ID,每个进程组也有唯一的进程组ID。每个进程组有一个进程组组长,进程组组长的进程ID和组ID相同。

进程间通信(管道)

相关函数:

//创建匿名管道  返回0:成功 或者 -1:不成功 有读端、写端所以文件描述符为fd[2]
pipe(int pipefd[2]);

//重定向函数  返回newfd:成功 或者 -1:不成功
dup2(int oldfd, int newfd);

//创建管道,创建子进程,调用shell命令   返回管道文件的描述符:成功 或者 -1:不成功
popen(const char *command, const char *type);

//示例
Rfp = popen("ls","r");		//读取命令执行结果
Wfp = popen("wc -l","w");	//将管道中的数据传递给进程

//关闭popen打开的I/O流,等待子进程执行结束,返回终止状态   返回0:成功 或者 -1:不成功
pclose(FILE *stream);

//创建命名管道   返回0:成功 或者 -1:不成功
mkfifo(const char *pathname, mode_t mode);

可以将管道抽象成文件,进行文件相关操作。
匿名管道没有名字,只能用于有亲缘关系的进程间通信。
命名管道与系统中的路径名关联,以文件形式存在,系统中的不同进程可以通过路径名访问文件实现通信。

FIFO文件和普通文件的区别
1.FIFO文件是对内存进行操作
2.普通文件是存储在硬盘
3.对内存的读取会比硬盘的读写要快很多
4.两个进程通过普通文件也可以通信

匿名管道和命名管道的异同:
1、匿名管道是由pipe函数创建并打开的
2、命名管道是由mkfifo函数创建的 ,打开用open
3、命名管道和匿名管道唯一的区别就是它们创建和打开的方式不同,一旦这些工作完成后,它们有相同的语义

线程操作

相关函数:

// 创建线程 返回0:成功或者error:不成功
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

//获取线程ID
pthread_self

//示例
void *tfn(void *arg) {
	printf("tfn--pid=%d,tid=%lu\n", getpid(), pthread_self());
	return (void*)0;
}
pthread_t Tid;
int Ret = pthread_create(&Tid, NULL, tfn, NULL);

//线程退出
pthread_exit(void *retval);

//线程中止   返回0:成功或者error:不成功
pthread_cancel(pthread_t thread);

//挂起线程   返回0:成功或者error:不成功
pthread_join(pthread_t thread, void **retval);

//线程分离 返回0:成功或者error:不成功 被分离的线程在执行结束后将会自动释放,不再等待其它线程回收。
pthread_detach(pthread_t thread);

进程和线程的联系:
当一个进程创建一个线程时,原有的进程就会变成线程,两个线程共用一段地址空间。
对内核而言,线程和进程没有区别,CPU会为每个线程与进程分配时间片,通过进程控制块来调度不同的线程和进程。

进程和线程的区别:
进程拥有独立的地址空间,当使用fork函数创建新进程时,若其中一个进程要对fork之前的数据进行修改,进程会根据“写时复制”原则,先复制一份该数据到子进程的地址空间,再修改数据。即便是全局变量,在进程间也不是共享的。
线程间共享地址空间,一个线程对全局取的数据进行了修改,其它线程访问到的也是修改后的数据。

多线程运行未成功其它线程未执行原因:
线程与进程不同,若作为程序入口的原线程退出,系统内部会调用exit函数,导致同一进程中的所有线程都退出。解决方法:使用sleep函数使原线程阻塞,保证新创建的线程顺利执行。或者使用线程挂起函数。

原线程的退出之所以会导致其它线程退出,是因为原线程执行完毕后,main函数会隐式调用exit函数,而pthread_exit函数可以只使调用该函数的线程退出。若在原线程调用return之前调用pthread_exit,同样可以保证其它线程的正常运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值